All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1)
@ 2018-03-14  0:08 Jaegeuk Kim
  2018-03-14  0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim
  2018-03-16  8:26 ` [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Chao Yu
  0 siblings, 2 replies; 8+ messages in thread
From: Jaegeuk Kim @ 2018-03-14  0:08 UTC (permalink / raw)
  To: linux-f2fs-devel; +Cc: Jaegeuk Kim, Hyojun Kim

From: Hyojun Kim <hyojun@google.com>

It was reported that #pragma pack(1) could create unwanted
influences. pack(push, 1) and pack(pop) are used instead.

Signed-off-by: Hyojun Kim <hyojun@google.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
---
 include/f2fs_fs.h | 80 ++-----------------------------------------------------
 include/quota.h   | 11 ++------
 2 files changed, 4 insertions(+), 87 deletions(-)

diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
index 3121e0e..c460050 100644
--- a/include/f2fs_fs.h
+++ b/include/f2fs_fs.h
@@ -569,17 +569,12 @@ enum {
 /*
  * For superblock
  */
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
+#pragma pack(push, 1)
 struct f2fs_device {
 	__u8 path[MAX_PATH_LEN];
 	__le32 total_segments;
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_super_block {
 	__le32 magic;			/* Magic Number */
 	__le16 major_ver;		/* Major Version */
@@ -639,9 +634,6 @@ struct f2fs_super_block {
 #define CP_ORPHAN_PRESENT_FLAG	0x00000002
 #define CP_UMOUNT_FLAG		0x00000001
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_checkpoint {
 	__le64 checkpoint_ver;		/* checkpoint block version number */
 	__le64 user_block_count;	/* # of user blocks */
@@ -683,9 +675,6 @@ struct f2fs_checkpoint {
  */
 #define F2FS_ORPHANS_PER_BLOCK	1020
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_orphan_block {
 	__le32 ino[F2FS_ORPHANS_PER_BLOCK];	/* inode numbers */
 	__le32 reserved;	/* reserved */
@@ -698,9 +687,6 @@ struct f2fs_orphan_block {
 /*
  * For NODE structure
  */
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_extent {
 	__le32 fofs;		/* start file offset of the extent */
 	__le32 blk_addr;	/* start block address of the extent */
@@ -767,9 +753,6 @@ struct f2fs_extent {
 #define file_is_encrypt(fi)      ((fi)->i_advise & FADVISE_ENCRYPT_BIT)
 #define file_enc_name(fi)        ((fi)->i_advise & FADVISE_ENC_NAME_BIT)
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_inode {
 	__le16 i_mode;			/* file mode */
 	__u8 i_advise;			/* file hints */
@@ -813,16 +796,10 @@ struct f2fs_inode {
 } __attribute__((packed));
 
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct direct_node {
 	__le32 addr[ADDRS_PER_BLOCK];	/* array of data block address */
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct indirect_node {
 	__le32 nid[NIDS_PER_BLOCK];	/* array of data block address */
 } __attribute__((packed));
@@ -836,9 +813,6 @@ enum {
 
 #define XATTR_NODE_OFFSET	((((unsigned int)-1) << OFFSET_BIT_SHIFT) \
 				>> OFFSET_BIT_SHIFT)
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct node_footer {
 	__le32 nid;		/* node id */
 	__le32 ino;		/* inode nunmber */
@@ -847,9 +821,6 @@ struct node_footer {
 	__le32 next_blkaddr;	/* next node page block address */
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_node {
 	/* can be one of three types: inode, direct, and indirect types */
 	union {
@@ -868,18 +839,12 @@ struct f2fs_node {
 
 #define DEFAULT_NAT_ENTRY_RATIO		20
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_nat_entry {
 	__u8 version;		/* latest version of cached nat entry */
 	__le32 ino;		/* inode number */
 	__le32 block_addr;	/* block address */
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_nat_block {
 	struct f2fs_nat_entry entries[NAT_ENTRY_PER_BLOCK];
 } __attribute__((packed));
@@ -916,18 +881,12 @@ struct f2fs_nat_block {
 	((le16_to_cpu((raw_sit)->vblocks) & ~SIT_VBLOCKS_MASK)	\
 	 >> SIT_VBLOCKS_SHIFT)
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_sit_entry {
 	__le16 vblocks;				/* reference above */
 	__u8 valid_map[SIT_VBLOCK_MAP_SIZE];	/* bitmap for valid blocks */
 	__le64 mtime;				/* segment age for cleaning */
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_sit_block {
 	struct f2fs_sit_entry entries[SIT_ENTRY_PER_BLOCK];
 } __attribute__((packed));
@@ -953,16 +912,10 @@ struct f2fs_sit_block {
 #define SUM_ENTRIES_SIZE	(SUMMARY_SIZE * ENTRIES_IN_SUM)
 
 /* a summary entry for a 4KB-sized block in a segment */
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_summary {
 	__le32 nid;		/* parent node id */
 	union {
 		__u8 reserved[3];
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 		struct {
 			__u8 version;		/* node version number */
 			__le16 ofs_in_node;	/* block index in parent node */
@@ -974,9 +927,6 @@ struct f2fs_summary {
 #define SUM_TYPE_NODE		(1)
 #define SUM_TYPE_DATA		(0)
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct summary_footer {
 	unsigned char entry_type;	/* SUM_TYPE_XXX */
 	__le32 check_sum;		/* summary checksum */
@@ -1008,49 +958,31 @@ enum {
 	SIT_JOURNAL
 };
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct nat_journal_entry {
 	__le32 nid;
 	struct f2fs_nat_entry ne;
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct nat_journal {
 	struct nat_journal_entry entries[NAT_JOURNAL_ENTRIES];
 	__u8 reserved[NAT_JOURNAL_RESERVED];
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct sit_journal_entry {
 	__le32 segno;
 	struct f2fs_sit_entry se;
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct sit_journal {
 	struct sit_journal_entry entries[SIT_JOURNAL_ENTRIES];
 	__u8 reserved[SIT_JOURNAL_RESERVED];
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_extra_info {
 	__le64 kbytes_written;
 	__u8 reserved[EXTRA_INFO_RESERVED];
 } __attribute__((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_journal {
 	union {
 		__le16 n_nats;
@@ -1065,9 +997,6 @@ struct f2fs_journal {
 } __attribute__((packed));
 
 /* 4KB-sized summary block structure */
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_summary_block {
 	struct f2fs_summary entries[ENTRIES_IN_SUM];
 	struct f2fs_journal journal;
@@ -1107,9 +1036,6 @@ typedef __le32	f2fs_hash_t;
 				NR_DENTRY_IN_BLOCK + SIZE_OF_DENTRY_BITMAP))
 
 /* One directory entry slot representing F2FS_SLOT_LEN-sized file name */
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_dir_entry {
 	__le32 hash_code;	/* hash code of file name */
 	__le32 ino;		/* inode number */
@@ -1118,9 +1044,6 @@ struct f2fs_dir_entry {
 } __attribute__((packed));
 
 /* 4KB-sized directory entry block */
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct f2fs_dentry_block {
 	/* validity bitmap for directory entries in each block */
 	__u8 dentry_bitmap[SIZE_OF_DENTRY_BITMAP];
@@ -1128,6 +1051,7 @@ struct f2fs_dentry_block {
 	struct f2fs_dir_entry dentry[NR_DENTRY_IN_BLOCK];
 	__u8 filename[NR_DENTRY_IN_BLOCK][F2FS_SLOT_LEN];
 } __attribute__((packed));
+#pragma pack(pop)
 
 /* for inline stuff */
 #define DEF_INLINE_RESERVED_SIZE	1
diff --git a/include/quota.h b/include/quota.h
index cfb9861..f578621 100644
--- a/include/quota.h
+++ b/include/quota.h
@@ -44,18 +44,13 @@ enum quota_type {
 
 #define QT_TREEOFF	1	/* Offset of tree in file in blocks */
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
+#pragma pack(push, 1)
 struct v2_disk_dqheader {
 	u_int32_t dqh_magic;	/* Magic number identifying file */
 	u_int32_t dqh_version;	/* File version */
 } __attribute__ ((packed));
 
 /* Header with type and version specific information */
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct v2_disk_dqinfo {
 	u_int32_t dqi_bgrace;	/* Time before block soft limit becomes hard limit */
 	u_int32_t dqi_igrace;	/* Time before inode soft limit becomes hard limit */
@@ -65,9 +60,6 @@ struct v2_disk_dqinfo {
 	u_int32_t dqi_free_entry;	/* Number of block with at least one free entry */
 } __attribute__ ((packed));
 
-#ifdef ANDROID_WINDOWS_HOST
-#pragma pack(1)
-#endif
 struct v2r1_disk_dqblk {
 	__le32 dqb_id;  	/* id this quota applies to */
 	__le32 dqb_pad;
@@ -82,5 +74,6 @@ struct v2r1_disk_dqblk {
 	__le64 dqb_btime;       /* time limit for excessive disk use */
 	__le64 dqb_itime;       /* time limit for excessive inode use */
 } __attribute__ ((packed));
+#pragma pack(pop)
 
 #endif
-- 
2.15.0.531.g2ccb3012c9-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU
  2018-03-14  0:08 [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Jaegeuk Kim
@ 2018-03-14  0:08 ` Jaegeuk Kim
  2018-03-14  2:10   ` Junling Zheng
  2018-03-16  8:29   ` [PATCH 2/2] " Chao Yu
  2018-03-16  8:26 ` [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Chao Yu
  1 sibling, 2 replies; 8+ messages in thread
From: Jaegeuk Kim @ 2018-03-14  0:08 UTC (permalink / raw)
  To: linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim

From: Hyojun Kim <hyojun@google.com>

sg_write_buffer sources are added for FFU.

Signed-off-by: Hyojun Kim <hyojun@google.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@google.com>
---
 tools/sg_write_buffer/Android.bp                   |   27 +
 tools/sg_write_buffer/include/freebsd_nvme_ioctl.h |  156 +
 tools/sg_write_buffer/include/sg_cmds.h            |   21 +
 tools/sg_write_buffer/include/sg_cmds_basic.h      |  310 ++
 tools/sg_write_buffer/include/sg_cmds_extra.h      |  369 +++
 tools/sg_write_buffer/include/sg_cmds_mmc.h        |   52 +
 tools/sg_write_buffer/include/sg_io_linux.h        |  185 ++
 tools/sg_write_buffer/include/sg_lib.h             |  602 ++++
 tools/sg_write_buffer/include/sg_lib_data.h        |  121 +
 tools/sg_write_buffer/include/sg_linux_inc.h       |   56 +
 tools/sg_write_buffer/include/sg_pr2serr.h         |   30 +
 tools/sg_write_buffer/include/sg_pt.h              |  215 ++
 tools/sg_write_buffer/include/sg_pt_linux.h        |  171 +
 tools/sg_write_buffer/include/sg_pt_nvme.h         |  172 +
 tools/sg_write_buffer/include/sg_pt_win32.h        |  473 +++
 tools/sg_write_buffer/include/sg_unaligned.h       |  325 ++
 tools/sg_write_buffer/sg_cmds_basic.c              |  663 ++++
 tools/sg_write_buffer/sg_cmds_basic2.c             | 1069 ++++++
 tools/sg_write_buffer/sg_cmds_extra.c              | 2524 ++++++++++++++
 tools/sg_write_buffer/sg_cmds_mmc.c                |  382 +++
 tools/sg_write_buffer/sg_io_linux.c                |  256 ++
 tools/sg_write_buffer/sg_lib.c                     | 3494 ++++++++++++++++++++
 tools/sg_write_buffer/sg_lib_data.c                | 1684 ++++++++++
 tools/sg_write_buffer/sg_pt_common.c               |  141 +
 tools/sg_write_buffer/sg_pt_linux.c                |  964 ++++++
 tools/sg_write_buffer/sg_pt_linux_nvme.c           | 1185 +++++++
 tools/sg_write_buffer/sg_write_buffer.c            |  516 +++
 27 files changed, 16163 insertions(+)
 create mode 100644 tools/sg_write_buffer/Android.bp
 create mode 100644 tools/sg_write_buffer/include/freebsd_nvme_ioctl.h
 create mode 100644 tools/sg_write_buffer/include/sg_cmds.h
 create mode 100644 tools/sg_write_buffer/include/sg_cmds_basic.h
 create mode 100644 tools/sg_write_buffer/include/sg_cmds_extra.h
 create mode 100644 tools/sg_write_buffer/include/sg_cmds_mmc.h
 create mode 100644 tools/sg_write_buffer/include/sg_io_linux.h
 create mode 100644 tools/sg_write_buffer/include/sg_lib.h
 create mode 100644 tools/sg_write_buffer/include/sg_lib_data.h
 create mode 100644 tools/sg_write_buffer/include/sg_linux_inc.h
 create mode 100644 tools/sg_write_buffer/include/sg_pr2serr.h
 create mode 100644 tools/sg_write_buffer/include/sg_pt.h
 create mode 100644 tools/sg_write_buffer/include/sg_pt_linux.h
 create mode 100644 tools/sg_write_buffer/include/sg_pt_nvme.h
 create mode 100644 tools/sg_write_buffer/include/sg_pt_win32.h
 create mode 100644 tools/sg_write_buffer/include/sg_unaligned.h
 create mode 100644 tools/sg_write_buffer/sg_cmds_basic.c
 create mode 100644 tools/sg_write_buffer/sg_cmds_basic2.c
 create mode 100644 tools/sg_write_buffer/sg_cmds_extra.c
 create mode 100644 tools/sg_write_buffer/sg_cmds_mmc.c
 create mode 100644 tools/sg_write_buffer/sg_io_linux.c
 create mode 100644 tools/sg_write_buffer/sg_lib.c
 create mode 100644 tools/sg_write_buffer/sg_lib_data.c
 create mode 100644 tools/sg_write_buffer/sg_pt_common.c
 create mode 100644 tools/sg_write_buffer/sg_pt_linux.c
 create mode 100644 tools/sg_write_buffer/sg_pt_linux_nvme.c
 create mode 100644 tools/sg_write_buffer/sg_write_buffer.c

diff --git a/tools/sg_write_buffer/Android.bp b/tools/sg_write_buffer/Android.bp
new file mode 100644
index 0000000..5222a59
--- /dev/null
+++ b/tools/sg_write_buffer/Android.bp
@@ -0,0 +1,27 @@
+cc_defaults {
+    name: "sg3-utils-defaults",
+    cflags: [
+        "-Wno-unused-function"
+    ],
+    local_include_dirs: [
+        "include",
+    ],
+}
+
+cc_binary {
+    name: "sg_write_buffer",
+    defaults: [ "sg3-utils-defaults" ],
+    srcs: [
+        "sg_write_buffer.c",
+        "sg_cmds_basic.c",
+        "sg_cmds_basic2.c",
+        "sg_cmds_extra.c",
+        "sg_cmds_mmc.c",
+        "sg_io_linux.c",
+        "sg_lib.c",
+        "sg_lib_data.c",
+        "sg_pt_common.c",
+        "sg_pt_linux.c",
+        "sg_pt_linux_nvme.c",
+    ],
+}
diff --git a/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h b/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h
new file mode 100644
index 0000000..f5d2443
--- /dev/null
+++ b/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h
@@ -0,0 +1,156 @@
+PROPS-END
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+
+#include <sys/param.h>
+
+#define	NVME_PASSTHROUGH_CMD	_IOWR('n', 0, struct nvme_pt_command)
+
+#if __FreeBSD_version < 1100110
+struct nvme_command
+{
+	/* dword 0 */
+	uint16_t opc	:  8;	/* opcode */
+	uint16_t fuse	:  2;	/* fused operation */
+	uint16_t rsvd1	:  6;
+	uint16_t cid;		/* command identifier */
+
+	/* dword 1 */
+	uint32_t nsid;		/* namespace identifier */
+
+	/* dword 2-3 */
+	uint32_t rsvd2;
+	uint32_t rsvd3;
+
+	/* dword 4-5 */
+	uint64_t mptr;		/* metadata pointer */
+
+	/* dword 6-7 */
+	uint64_t prp1;		/* prp entry 1 */
+
+	/* dword 8-9 */
+	uint64_t prp2;		/* prp entry 2 */
+
+	/* dword 10-15 */
+	uint32_t cdw10;		/* command-specific */
+	uint32_t cdw11;		/* command-specific */
+	uint32_t cdw12;		/* command-specific */
+	uint32_t cdw13;		/* command-specific */
+	uint32_t cdw14;		/* command-specific */
+	uint32_t cdw15;		/* command-specific */
+} __packed;
+
+struct nvme_status {
+
+	uint16_t p	:  1;	/* phase tag */
+	uint16_t sc	:  8;	/* status code */
+	uint16_t sct	:  3;	/* status code type */
+	uint16_t rsvd2	:  2;
+	uint16_t m	:  1;	/* more */
+	uint16_t dnr	:  1;	/* do not retry */
+} __packed;
+
+struct nvme_completion {
+
+	/* dword 0 */
+	uint32_t		cdw0;	/* command-specific */
+
+	/* dword 1 */
+	uint32_t		rsvd1;
+
+	/* dword 2 */
+	uint16_t		sqhd;	/* submission queue head pointer */
+	uint16_t		sqid;	/* submission queue identifier */
+
+	/* dword 3 */
+	uint16_t		cid;	/* command identifier */
+	struct nvme_status	status;
+} __packed;
+
+struct nvme_pt_command {
+
+	/*
+	 * cmd is used to specify a passthrough command to a controller or
+	 *  namespace.
+	 *
+	 * The following fields from cmd may be specified by the caller:
+	 *	* opc  (opcode)
+	 *	* nsid (namespace id) - for admin commands only
+	 *	* cdw10-cdw15
+	 *
+	 * Remaining fields must be set to 0 by the caller.
+	 */
+	struct nvme_command	cmd;
+
+	/*
+	 * cpl returns completion status for the passthrough command
+	 *  specified by cmd.
+	 *
+	 * The following fields will be filled out by the driver, for
+	 *  consumption by the caller:
+	 *	* cdw0
+	 *	* status (except for phase)
+	 *
+	 * Remaining fields will be set to 0 by the driver.
+	 */
+	struct nvme_completion	cpl;
+
+	/* buf is the data buffer associated with this passthrough command. */
+	void *			buf;
+
+	/*
+	 * len is the length of the data buffer associated with this
+	 *  passthrough command.
+	 */
+	uint32_t		len;
+
+	/*
+	 * is_read = 1 if the passthrough command will read data into the
+	 *  supplied buffer from the controller.
+	 *
+	 * is_read = 0 if the passthrough command will write data from the
+	 *  supplied buffer to the controller.
+	 */
+	uint32_t		is_read;
+
+	/*
+	 * driver_lock is used by the driver only.  It must be set to 0
+	 *  by the caller.
+	 */
+	struct mtx *		driver_lock;
+};
+#else
+#include <dev/nvme/nvme.h>
+#endif
+
+#define nvme_completion_is_error(cpl)					\
+	((cpl)->status.sc != 0 || (cpl)->status.sct != 0)
+
+#define NVME_CTRLR_PREFIX	"/dev/nvme"
+#define NVME_NS_PREFIX		"ns"
diff --git a/tools/sg_write_buffer/include/sg_cmds.h b/tools/sg_write_buffer/include/sg_cmds.h
new file mode 100644
index 0000000..690f53a
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_cmds.h
@@ -0,0 +1,21 @@
+#ifndef SG_CMDS_H
+#define SG_CMDS_H
+
+/********************************************************************
+ * This header did contain wrapper declarations for many SCSI commands
+ * up until sg3_utils version 1.22 . In that version, the command
+ * wrappers were broken into two groups, the 'basic' ones found in the
+ * "sg_cmds_basic.h" header and the 'extra' ones found in the
+ * "sg_cmds_extra.h" header. This header now simply includes those two
+ * headers.
+ * In sg3_utils version 1.26 the sg_cmds_mmc.h header was added and
+ * contains some MMC specific commands.
+ * The corresponding function definitions are found in the sg_cmds_basic.c,
+ * sg_cmds_extra.c and sg_cmds_mmc.c files.
+ ********************************************************************/
+
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_cmds_mmc.h"
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_cmds_basic.h b/tools/sg_write_buffer/include/sg_cmds_basic.h
new file mode 100644
index 0000000..507effa
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_cmds_basic.h
@@ -0,0 +1,310 @@
+#ifndef SG_CMDS_BASIC_H
+#define SG_CMDS_BASIC_H
+
+/*
+ * Copyright (c) 2004-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.
+ */
+
+/*
+ * Error, warning and verbose output is sent to the file pointed to by
+ * sg_warnings_strm which is declared in sg_lib.h and can be set with
+ * the sg_set_warnings_strm() function. If not given sg_warnings_strm
+ * defaults to stderr.
+ * If 'noisy' is false and 'verbose' is zero then following functions should
+ * not output anything to sg_warnings_strm. If 'noisy' is true and
+ * 'verbose' is zero then Unit Attention, Recovered, Medium and Hardware
+ * errors (sense keys) send output to sg_warnings_strm. Increasing values
+ * of 'verbose' send increasing amounts of (debug) output to
+ * sg_warnings_strm.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Invokes a SCSI INQUIRY command and yields the response
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other errors */
+int sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+                  int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so
+ * 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 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
+ * bytes have been written. */
+int
+sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp,
+                 int mx_resp_len, int timeout_secs, int * residp,
+                 bool noisy, int verbose);
+
+/* Invokes a SCSI LOG SELECT command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Log Select not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ABORTED_COMMAND, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code,
+                     int subpg_code, unsigned char * paramp, int param_len,
+                     bool noisy, int verbose);
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Log Sense not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+                    int subpg_code, int paramp, unsigned char * resp,
+                    int mx_resp_len, bool noisy, int verbose);
+
+/* Same as sg_ll_log_sense() apart from timeout_secs and residp. See
+ * sg_ll_inquiry_v2() for their description */
+int sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+                       int subpg_code, int paramp, unsigned char * resp,
+                       int mx_resp_len, int timeout_secs, int * residp,
+                       bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SELECT (6) command.  Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp,
+                        int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SELECT (10) command.  Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp,
+                        int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code,
+                      int sub_pg_code, void * resp, int mx_resp_len,
+                      bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+                       int sub_pg_code, void * resp, int mx_resp_len,
+                       bool noisy, int verbose);
+
+/* Same as sg_ll_mode_sense10() apart from timeout_secs and residp. See
+ * sg_ll_inquiry_v2() for their description */
+int sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc,
+                          int pg_code, int sub_pg_code, void * resp,
+                          int mx_resp_len, int timeout_secs, int * residp,
+                          bool noisy, int verbose);
+
+/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command (SPC-3)
+ * prevent==0 allows removal, prevent==1 prevents removal ...
+ * Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> command not supported
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose);
+
+/* Invokes a SCSI READ CAPACITY (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION
+ * -> perhaps media changed, SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp,
+                     int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success,
+ * SG_LIB_CAT_UNIT_ATTENTION -> media changed??, SG_LIB_CAT_INVALID_OP
+ *  -> cdb not supported, SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp,
+                     int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Luns not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */
+int sg_ll_report_luns(int sg_fd, int select_report, void * resp,
+                      int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI REQUEST SENSE command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Request Sense not supported??,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len,
+                        bool noisy, int verbose);
+
+/* Invokes a SCSI START STOP UNIT command (SBC + MMC).
+ * Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Start stop unit not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure
+ * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and
+ * format_layer_number(mmc) fields. They also overlap on the noflush(sbc)
+ * and fl(mmc) one bit field. This is the cause of the awkardly named
+ * pc_mod__fl_num and noflush__fl arguments to this function.  */
+int sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
+                          int power_cond, bool noflush__fl, bool loej,
+                          bool start, bool noisy, int verbose);
+
+/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_INVALID_OP -> cdb not supported,
+ * SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */
+int sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group,
+                        unsigned int lba, unsigned int count, bool noisy,
+                        int verbose);
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other failure */
+int sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose);
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Looks for progress indicator if 'progress' non-NULL;
+ * if found writes value [0..65535] else write -1.
+ * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ABORTED_COMMAND, SG_LIB_CAT_NOT_READY ->
+ * device not ready, -1 -> other failure */
+int sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress,
+                                   bool noisy, int verbose);
+
+
+struct sg_simple_inquiry_resp {
+    unsigned char peripheral_qualifier;
+    unsigned char peripheral_type;
+    unsigned char byte_1;       /* was 'rmb' prior to version 1.39 */
+                                /* now rmb == !!(0x80 & byte_1) */
+    unsigned char version;      /* as per recent drafts: whole of byte 2 */
+    unsigned char byte_3;
+    unsigned char byte_5;
+    unsigned char byte_6;
+    unsigned char byte_7;
+    char vendor[9];             /* T10 field is 8 bytes, NUL char appended */
+    char product[17];
+    char revision[5];
+};
+
+/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response.
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other errors */
+int sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data,
+                      bool noisy, int verbose);
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. In most cases users are
+ * interested in the first mode page. This function returns the (byte)
+ * offset of the start of the first mode page. Set mode_sense_6 to true for
+ * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful
+ * or -1 if failure. If there is a failure a message is written to err_buff
+ * if it is non-NULL and err_buff_len > 0. */
+int sg_mode_page_offset(const unsigned char * resp, int resp_len,
+                        bool mode_sense_6, char * err_buff, int err_buff_len);
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. This functions returns the
+ * length (in bytes) of those three components. Note that the return value
+ * can exceed resp_len in which case the MODE SENSE command should be
+ * re-issued with a larger response buffer. If bd_lenp is non-NULL and if
+ * successful the block descriptor length (in bytes) is written to *bd_lenp.
+ * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10)
+ * responses. Returns -1 if there is an error (e.g. response too short). */
+int sg_msense_calc_length(const unsigned char * resp, int resp_len,
+                          bool mode_sense_6, int * bd_lenp);
+
+/* Fetches current, changeable, default and/or saveable modes pages as
+ * indicated by pcontrol_arr for given pg_code and sub_pg_code. If
+ * mode6==0 then use MODE SENSE (10) else use MODE SENSE (6). If
+ * flexible set and mode data length seems wrong then try and
+ * fix (compensating hack for bad device or driver). pcontrol_arr
+ * should have 4 elements for output of current, changeable, default
+ * and saved values respectively. Each element should be NULL or
+ * at least mx_mpage_len bytes long.
+ * Return of 0 -> overall success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_MALFORMED -> bad response, -1 -> other failure.
+ * If success_mask pointer is not NULL then first zeros it. Then set bits
+ * 0, 1, 2 and/or 3 if the current, changeable, default and saved values
+ * respectively have been fetched. If error on current page
+ * then stops and returns that error; otherwise continues if an error is
+ * detected but returns the first error encountered.  */
+int sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code,
+                              int sub_pg_code, bool dbd, bool flexible,
+                              int mx_mpage_len, int * success_mask,
+                              void * pcontrol_arr[], int * reported_lenp,
+                              int verbose);
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+   negated errno. Implementation calls scsi_pt_open_device(). */
+int sg_cmds_open_device(const char * device_name, bool read_only, int verbose);
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+   negated errno. Implementation calls scsi_pt_open_flags(). */
+int sg_cmds_open_flags(const char * device_name, int flags, int verbose);
+
+/* Returns 0 if successful. If error in Unix returns negated errno.
+   Implementation calls scsi_pt_close_device(). */
+int sg_cmds_close_device(int device_fd);
+
+const char * sg_cmds_version();
+
+#define SG_NO_DATA_IN 0
+
+struct sg_pt_base;
+
+/* This is a helper function used by sg_cmds_* implementations after the
+ * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid
+ * sense data is found it is decoded and output to sg_warnings_strm (def:
+ * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for
+ * sense data (may not be fatal), -1 for failed, 0, or a positive number. If
+ * 'mx_di_len > 0' then asks pass-through for resid and returns
+ * (mx_di_len - resid); otherwise returns 0. So for data-in it should return
+ * the actual number of bytes received. For data-out (to device) or no data
+ * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category
+ * output via 'o_sense_cat' pointer (if not NULL). Note that several sense
+ * categories also have data in bytes received; -2 is still returned. */
+int sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin,
+                         int pt_res, int mx_di_len,
+                         const unsigned char * sense_b, bool noisy,
+                         int verbose, int * o_sense_cat);
+
+/* NVMe devices use a different command set. This function will return true
+ * if the device associated with 'pvtp' is a NVME device, else it will
+ * return false (e.g. for SCSI devices). */
+bool sg_cmds_is_nvme(const struct sg_pt_base * ptvp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_cmds_extra.h b/tools/sg_write_buffer/include/sg_cmds_extra.h
new file mode 100644
index 0000000..fbc2377
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_cmds_extra.h
@@ -0,0 +1,369 @@
+#ifndef SG_CMDS_EXTRA_H
+#define SG_CMDS_EXTRA_H
+
+/*
+ * Copyright (c) 2004-2018 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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Note: all functions that have an 'int timeout_secs' argument will use
+ * that value if it is > 0. Otherwise they will set an internal default
+ * which is currently 60 seconds. This timeout is typically applied in the
+ * SCSI stack above the initiator. If it goes off then the SCSI command is
+ * aborted and there can be other unwelcome side effects. Note that some
+ * commands (e.g. FORMAT UNIT and the Third Party copy commands) can take
+ * a lot longer than the default timeout. */
+
+
+/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is
+ * selected by the cdb_len argument that can take values of 12, 16 or 32
+ * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9
+ * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from
+ * the control byte, the rest is copied into an internal cdb which is then
+ * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15
+ * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the
+ * timeout is set to 60 seconds. For data in or out transfers set dinp or
+ * doutp, and dlen to the number of bytes to transfer. If dlen is zero then
+ * no data transfer is assumed. If sense buffer obtained then it is written
+ * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is
+ * obtained then written to ata_return_dp, else ata_return_dp[0] is set to
+ * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers.
+ * Returns SCSI status value (>= 0) or -1 if other error. Users are
+ * expected to check the sense buffer themselves. If available the data in
+ * resid is written to residp. Note in SAT-2 and later, fixed format sense
+ * data may be placed in *sensep in which case sensep[0]==0x70, prior to
+ * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72).
+ */
+int sg_ll_ata_pt(int sg_fd, const unsigned char * cdbp, int cdb_len,
+                 int timeout_secs,  void * dinp, void * doutp, int dlen,
+                 unsigned char * sensep, int max_sense_len,
+                 unsigned char * ata_return_dp, int max_ata_return_len,
+                 int * residp, int verbose);
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Format unit not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure. Note that sg_ll_format_unit2() and
+ * sg_ll_format_unit_v2() are the same, both add the ffmt argument. */
+int sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                      bool cmplist, int dlist_format, int timeout_secs,
+                      void * paramp, int param_len, bool noisy, int verbose);
+int sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                       bool cmplist, int dlist_format, int ffmt,
+                       int timeout_secs, void * paramp, int param_len,
+                       bool noisy, int verbose);
+int sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                         bool cmplist, int dlist_format, int ffmt,
+                         int timeout_secs, void * paramp, int param_len,
+                         bool noisy, int verbose);
+
+/* Invokes a SCSI GET LBA STATUS(16) or GET LBA STATUS(32) command (SBC).
+ * Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> GET LBA STATUS(16 or 32) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure.
+ * sg_ll_get_lba_status() calls the 16 byte variant with rt=0 . */
+int sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp,
+                         int alloc_len, bool noisy, int verbose);
+int sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt,
+                           void * resp, int alloc_len, bool noisy,
+                           int verbose);
+int sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len,
+                           uint32_t element_id, uint8_t rt,
+                           void * resp, int alloc_len, bool noisy,
+                           int verbose);
+
+/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0
+ * when successful, SG_LIB_CAT_INVALID_OP if command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp,
+                                int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0
+ * when successful, SG_LIB_CAT_INVALID_OP if command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope,
+                                 unsigned int rq_type, void * paramp,
+                                 int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ BLOCK LIMITS not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */
+int sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len,
+                            bool noisy, int verbose);
+
+/* Invokes a SCSI READ BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+                      void * resp, int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist,
+                        int dl_format, void * resp, int mx_resp_len,
+                        bool noisy, int verbose);
+
+/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ LONG(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba,
+                      void * resp, int xfer_len, int * offsetp, bool noisy,
+                      int verbose);
+
+/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ LONG(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ *  -1 -> other failure */
+int sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba,
+                      void * resp, int xfer_len, int * offsetp, bool noisy,
+                      int verbose);
+
+/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Read media serial number not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len,
+                                bool noisy, int verbose);
+
+/* Invokes a SCSI REASSIGN BLOCKS command.  Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */
+int sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist,
+                          void * paramp, int param_len, bool noisy,
+                          int verbose);
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Receive diagnostic results not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp,
+                       int mx_resp_len, bool noisy, int verbose);
+
+/* 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 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
+ * bytes have been written. */
+int sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp,
+                          int mx_resp_len, int timeout_secs, int * residp,
+                          bool noisy, int verbose);
+
+/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was
+ * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report identifying information not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len,
+                         bool noisy, int verbose);
+
+/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
+int sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len,
+                             bool noisy, int verbose);
+int sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len,
+                              bool extended, bool noisy, int verbose);
+
+/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
+int sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy,
+                          int verbose);
+
+/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Referrals not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
+int sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg,
+                           void * resp, int mx_resp_len, bool noisy,
+                           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 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,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit,
+                    bool devofl_bit, bool unitofl_bit, int long_duration,
+                    void * paramp, int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was
+ * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set identifying information not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len,
+                      bool noisy, int verbose);
+
+/* Invokes a SCSI UNMAP (SBC-3) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
+int sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp,
+                int param_len, bool noisy, int verbose);
+/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field
+ * (sbc3r22). Otherwise same as sg_ll_unmap() . */
+int sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs,
+                   void * paramp, int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI VERIFY (10) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Verify(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info,
+ * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */
+int sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytechk,
+                   unsigned int lba, int veri_len, void * data_out,
+                   int data_out_len, unsigned int * infop, bool noisy,
+                   int verbose);
+
+/* Invokes a SCSI VERIFY (16) command (SBC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Verify(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info,
+ * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */
+int sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytechk,
+                   uint64_t llba, int veri_len, int group_num,
+                   void * data_out, int data_out_len, uint64_t * infop,
+                   bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+                       void * paramp, int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure. Adds mode specific field (spc4r32) and timeout
+ *  to command abort to override default of 60 seconds. If timeout_secs is
+ *  0 or less then the default timeout is used instead. */
+int
+sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id,
+                      uint32_t buffer_offset, void * paramp,
+                      uint32_t param_len, int timeout_secs, bool noisy,
+                      int verbose);
+
+/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> WRITE LONG(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+                       unsigned int lba, void * data_out, int xfer_len,
+                       int * offsetp, bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> WRITE LONG(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+                       uint64_t llba, void * data_out, int xfer_len,
+                       int * offsetp, bool noisy, int verbose);
+
+/* Invokes a SPC-3 SCSI RECEIVE COPY RESULTS command. In SPC-4 this function
+ * supports all service action variants of the THIRD-PARTY COPY IN opcode.
+ * SG_LIB_CAT_INVALID_OP -> Receive copy results not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp,
+                               int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI EXTENDED COPY(LID1) command. For EXTENDED COPY(LID4)
+ * including POPULATE TOKEN and WRITE USING TOKEN use
+ * sg_ll_3party_copy_out().  Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Extended copy not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy,
+                        int verbose);
+
+/* Handles various service actions associated with opcode 0x83 which is
+ * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID4),
+ * POPULATE TOKEN and WRITE USING TOKEN commands. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> opcode 0x83 not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id,
+                          int group_num, int timeout_secs, void * paramp,
+                          int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC).
+ * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_*
+ * positive values or -1 -> other errors. Note that CONDITION MET status
+ * is returned when immed=true and num_blocks can fit in device's cache,
+ * somewaht strangely, GOOD status (return 0) is returned if num_blocks
+ * cannot fit in device's cache. If do_seek10==true then does a SEEK(10)
+ * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10)
+ * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then
+ * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */
+int sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed,
+                      uint64_t lba, uint32_t num_blocks, int group_num,
+                      int timeout_secs, bool noisy, int verbose);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_cmds_mmc.h b/tools/sg_write_buffer/include/sg_cmds_mmc.h
new file mode 100644
index 0000000..3988b1d
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_cmds_mmc.h
@@ -0,0 +1,52 @@
+#ifndef SG_CMDS_MMC_H
+#define SG_CMDS_MMC_H
+
+/*
+ * Copyright (c) 2008-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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Invokes a SCSI GET CONFIGURATION command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_get_config(int sg_fd, int rt, int starting, void * resp,
+                     int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba,
+                          int max_num_desc, int type, void * resp,
+                          int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI SET CD SPEED command (MMC).
+ * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed,
+                       int drv_write_speed, bool noisy, int verbose);
+
+/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len,
+                        bool noisy, int verbose);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_io_linux.h b/tools/sg_write_buffer/include/sg_io_linux.h
new file mode 100644
index 0000000..bd44bd6
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_io_linux.h
@@ -0,0 +1,185 @@
+#ifndef SG_IO_LINUX_H
+#define SG_IO_LINUX_H
+
+/*
+ * Copyright (c) 2004-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.
+ */
+
+/*
+ * Version 1.05 [20171009]
+ */
+
+/*
+ * This header file contains linux specific information related to the SCSI
+ * command pass through in the SCSI generic (sg) driver and the linux
+ * block layer.
+ */
+
+#include "sg_lib.h"
+#include "sg_linux_inc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The following are 'host_status' codes */
+#ifndef DID_OK
+#define DID_OK 0x00
+#endif
+#ifndef DID_NO_CONNECT
+#define DID_NO_CONNECT 0x01     /* Unable to connect before timeout */
+#define DID_BUS_BUSY 0x02       /* Bus remain busy until timeout */
+#define DID_TIME_OUT 0x03       /* Timed out for some other reason */
+#define DID_BAD_TARGET 0x04     /* Bad target (id?) */
+#define DID_ABORT 0x05          /* Told to abort for some other reason */
+#define DID_PARITY 0x06         /* Parity error (on SCSI bus) */
+#define DID_ERROR 0x07          /* Internal error */
+#define DID_RESET 0x08          /* Reset by somebody */
+#define DID_BAD_INTR 0x09       /* Received an unexpected interrupt */
+#define DID_PASSTHROUGH 0x0a    /* Force command past mid-level */
+#define DID_SOFT_ERROR 0x0b     /* The low-level driver wants a retry */
+#endif
+#ifndef DID_IMM_RETRY
+#define DID_IMM_RETRY 0x0c      /* Retry without decrementing retry count  */
+#endif
+#ifndef DID_REQUEUE
+#define DID_REQUEUE 0x0d        /* Requeue command (no immediate retry) also
+                                 * without decrementing the retry count    */
+#endif
+#ifndef DID_TRANSPORT_DISRUPTED
+#define DID_TRANSPORT_DISRUPTED 0xe
+#endif
+#ifndef DID_TRANSPORT_FAILFAST
+#define DID_TRANSPORT_FAILFAST 0xf
+#endif
+#ifndef DID_TARGET_FAILURE
+#define DID_TARGET_FAILURE 0x10
+#endif
+#ifndef DID_NEXUS_FAILURE
+#define DID_NEXUS_FAILURE 0x11
+#endif
+
+/* These defines are to isolate applications from kernel define changes */
+#define SG_LIB_DID_OK           DID_OK
+#define SG_LIB_DID_NO_CONNECT   DID_NO_CONNECT
+#define SG_LIB_DID_BUS_BUSY     DID_BUS_BUSY
+#define SG_LIB_DID_TIME_OUT     DID_TIME_OUT
+#define SG_LIB_DID_BAD_TARGET   DID_BAD_TARGET
+#define SG_LIB_DID_ABORT        DID_ABORT
+#define SG_LIB_DID_PARITY       DID_PARITY
+#define SG_LIB_DID_ERROR        DID_ERROR
+#define SG_LIB_DID_RESET        DID_RESET
+#define SG_LIB_DID_BAD_INTR     DID_BAD_INTR
+#define SG_LIB_DID_PASSTHROUGH  DID_PASSTHROUGH
+#define SG_LIB_DID_SOFT_ERROR   DID_SOFT_ERROR
+#define SG_LIB_DID_IMM_RETRY    DID_IMM_RETRY
+#define SG_LIB_DID_REQUEUE      DID_REQUEUE
+#define SG_LIB_TRANSPORT_DISRUPTED      DID_TRANSPORT_DISRUPTED
+#define SG_LIB_DID_TRANSPORT_FAILFAST   DID_TRANSPORT_FAILFAST
+#define SG_LIB_DID_TARGET_FAILURE       DID_TARGET_FAILURE
+#define SG_LIB_DID_NEXUS_FAILURE        DID_NEXUS_FAILURE
+
+/* The following are 'driver_status' codes */
+#ifndef DRIVER_OK
+#define DRIVER_OK 0x00
+#endif
+#ifndef DRIVER_BUSY
+#define DRIVER_BUSY 0x01
+#define DRIVER_SOFT 0x02
+#define DRIVER_MEDIA 0x03
+#define DRIVER_ERROR 0x04
+#define DRIVER_INVALID 0x05
+#define DRIVER_TIMEOUT 0x06
+#define DRIVER_HARD 0x07
+#define DRIVER_SENSE 0x08       /* Sense_buffer has been set */
+
+/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept
+ * to stop compilation breakages.
+ * Following "suggests" are "or-ed" with one of previous 8 entries */
+#define SUGGEST_RETRY 0x10
+#define SUGGEST_ABORT 0x20
+#define SUGGEST_REMAP 0x30
+#define SUGGEST_DIE 0x40
+#define SUGGEST_SENSE 0x80
+#define SUGGEST_IS_OK 0xff
+#endif
+
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0
+#endif
+
+/* These defines are to isolate applications from kernel define changes */
+#define SG_LIB_DRIVER_OK        DRIVER_OK
+#define SG_LIB_DRIVER_BUSY      DRIVER_BUSY
+#define SG_LIB_DRIVER_SOFT      DRIVER_SOFT
+#define SG_LIB_DRIVER_MEDIA     DRIVER_MEDIA
+#define SG_LIB_DRIVER_ERROR     DRIVER_ERROR
+#define SG_LIB_DRIVER_INVALID   DRIVER_INVALID
+#define SG_LIB_DRIVER_TIMEOUT   DRIVER_TIMEOUT
+#define SG_LIB_DRIVER_HARD      DRIVER_HARD
+#define SG_LIB_DRIVER_SENSE     DRIVER_SENSE
+
+
+/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept
+ * to stop compilation breakages. */
+#define SG_LIB_SUGGEST_RETRY    SUGGEST_RETRY
+#define SG_LIB_SUGGEST_ABORT    SUGGEST_ABORT
+#define SG_LIB_SUGGEST_REMAP    SUGGEST_REMAP
+#define SG_LIB_SUGGEST_DIE      SUGGEST_DIE
+#define SG_LIB_SUGGEST_SENSE    SUGGEST_SENSE
+#define SG_LIB_SUGGEST_IS_OK    SUGGEST_IS_OK
+#define SG_LIB_DRIVER_MASK      DRIVER_MASK
+#define SG_LIB_SUGGEST_MASK     SUGGEST_MASK
+
+void sg_print_masked_status(int masked_status);
+void sg_print_host_status(int host_status);
+void sg_print_driver_status(int driver_status);
+
+/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings
+   else it prints errors/warnings (prefixed by 'leadin') to
+   'sg_warnings_fd' and returns 0. raw_sinfo indicates whether the
+   raw sense buffer (in ASCII hex) should be printed. */
+int sg_chk_n_print(const char * leadin, int masked_status, int host_status,
+                   int driver_status, const unsigned char * sense_buffer,
+                   int sb_len, bool raw_sinfo);
+
+/* The following function declaration is for the sg version 3 driver. */
+struct sg_io_hdr;
+/* sg_chk_n_print3() returns 1 quietly if there are no errors/warnings;
+   else it prints errors/warnings (prefixed by 'leadin') to
+   'sg_warnings_fd' and returns 0. */
+int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp,
+                    bool raw_sinfo);
+
+/* Calls sg_scsi_normalize_sense() after obtaining the sense buffer and
+   its length from the struct sg_io_hdr pointer. If these cannot be
+   obtained, false is returned. */
+bool sg_normalize_sense(const struct sg_io_hdr * hp,
+                        struct sg_scsi_sense_hdr * sshp);
+
+int sg_err_category(int masked_status, int host_status, int driver_status,
+                    const unsigned char * sense_buffer, int sb_len);
+
+int sg_err_category_new(int scsi_status, int host_status, int driver_status,
+                        const unsigned char * sense_buffer, int sb_len);
+
+/* The following function declaration is for the sg version 3 driver. */
+int sg_err_category3(struct sg_io_hdr * hp);
+
+
+/* Note about SCSI status codes found in older versions of Linux.
+   Linux has traditionally used a 1 bit right shifted and masked
+   version of SCSI standard status codes. Now CHECK_CONDITION
+   and friends (in <scsi/scsi.h>) are deprecated. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_lib.h b/tools/sg_write_buffer/include/sg_lib.h
new file mode 100644
index 0000000..bc93d49
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_lib.h
@@ -0,0 +1,602 @@
+#ifndef SG_LIB_H
+#define SG_LIB_H
+
+/*
+ * Copyright (c) 2004-2018 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.
+ */
+
+/*
+ *
+ * On 5th October 2004 a FreeBSD license was added to this file.
+ * The intention is to keep this file and the related sg_lib.c file
+ * as open source and encourage their unencumbered use.
+ *
+ * Current version number is in the sg_lib.c file and can be accessed
+ * with the sg_lib_version() function.
+ */
+
+
+/*
+ * This header file contains defines and function declarations that may
+ * be useful to applications that communicate with devices that use a
+ * SCSI command set. These command sets have names like SPC-4, SBC-3,
+ * SSC-3, SES-2 and draft standards defining them can be found at
+ * http://www.t10.org . Virtually all devices in the Linux SCSI subsystem
+ * utilize SCSI command sets. Many devices in other Linux device subsystems
+ * utilize SCSI command sets either natively or via emulation (e.g. a
+ * parallel ATA disk in a USB enclosure).
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* SCSI Peripheral Device Types (PDT) [5 bit field] */
+#define PDT_DISK 0x0    /* direct access block device (disk) */
+#define PDT_TAPE 0x1    /* sequential access device (magnetic tape) */
+#define PDT_PRINTER 0x2 /* printer device (see SSC-1) */
+#define PDT_PROCESSOR 0x3       /* processor device (e.g. SAFTE device) */
+#define PDT_WO 0x4      /* write once device (some optical disks) */
+#define PDT_MMC 0x5     /* CD/DVD/BD (multi-media) */
+#define PDT_SCANNER 0x6 /* obsolete */
+#define PDT_OPTICAL 0x7 /* optical memory device (some optical disks) */
+#define PDT_MCHANGER 0x8        /* media changer device (e.g. tape robot) */
+#define PDT_COMMS 0x9   /* communications device (obsolete) */
+#define PDT_SAC 0xc     /* storage array controller device */
+#define PDT_SES 0xd     /* SCSI Enclosure Services (SES) device */
+#define PDT_RBC 0xe     /* Reduced Block Commands (simplified PDT_DISK) */
+#define PDT_OCRW 0xf    /* optical card read/write device */
+#define PDT_BCC 0x10    /* bridge controller commands */
+#define PDT_OSD 0x11    /* Object Storage Device (OSD) */
+#define PDT_ADC 0x12    /* Automation/drive commands (ADC) */
+#define PDT_SMD 0x13    /* Security Manager Device (SMD) */
+#define PDT_ZBC 0x14    /* Zoned Block Commands (ZBC) */
+#define PDT_WLUN 0x1e   /* Well known logical unit (WLUN) */
+#define PDT_UNKNOWN 0x1f        /* Unknown or no device type */
+
+#ifndef SAM_STAT_GOOD
+/* The SCSI status codes as found in SAM-4 at www.t10.org */
+#define SAM_STAT_GOOD 0x0
+#define SAM_STAT_CHECK_CONDITION 0x2
+#define SAM_STAT_CONDITION_MET 0x4
+#define SAM_STAT_BUSY 0x8
+#define SAM_STAT_INTERMEDIATE 0x10              /* obsolete in SAM-4 */
+#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14  /* obsolete in SAM-4 */
+#define SAM_STAT_RESERVATION_CONFLICT 0x18
+#define SAM_STAT_COMMAND_TERMINATED 0x22        /* obsolete in SAM-3 */
+#define SAM_STAT_TASK_SET_FULL 0x28
+#define SAM_STAT_ACA_ACTIVE 0x30
+#define SAM_STAT_TASK_ABORTED 0x40
+#endif
+
+/* The SCSI sense key codes as found in SPC-4 at www.t10.org */
+#define SPC_SK_NO_SENSE 0x0
+#define SPC_SK_RECOVERED_ERROR 0x1
+#define SPC_SK_NOT_READY 0x2
+#define SPC_SK_MEDIUM_ERROR 0x3
+#define SPC_SK_HARDWARE_ERROR 0x4
+#define SPC_SK_ILLEGAL_REQUEST 0x5
+#define SPC_SK_UNIT_ATTENTION 0x6
+#define SPC_SK_DATA_PROTECT 0x7
+#define SPC_SK_BLANK_CHECK 0x8
+#define SPC_SK_VENDOR_SPECIFIC 0x9
+#define SPC_SK_COPY_ABORTED 0xa
+#define SPC_SK_ABORTED_COMMAND 0xb
+#define SPC_SK_RESERVED 0xc
+#define SPC_SK_VOLUME_OVERFLOW 0xd
+#define SPC_SK_MISCOMPARE 0xe
+#define SPC_SK_COMPLETED 0xf
+
+/* Transport protocol identifiers or just Protocol identifiers */
+#define TPROTO_FCP 0
+#define TPROTO_SPI 1
+#define TPROTO_SSA 2
+#define TPROTO_1394 3
+#define TPROTO_SRP 4            /* SCSI over RDMA */
+#define TPROTO_ISCSI 5
+#define TPROTO_SAS 6
+#define TPROTO_ADT 7
+#define TPROTO_ATA 8
+#define TPROTO_UAS 9            /* USB attached SCSI */
+#define TPROTO_SOP 0xa          /* SCSI over PCIe */
+#define TPROTO_PCIE 0xb         /* includes NVMe */
+#define TPROTO_NONE 0xf
+
+/* SCSI Feature Sets (sfs) */
+#define SCSI_FS_SPC_DISCOVERY_2016 0x1
+#define SCSI_FS_SBC_BASE_2010 0x102
+#define SCSI_FS_SBC_BASE_2016 0x101
+#define SCSI_FS_SBC_BASIC_PROV_2016 0x103
+#define SCSI_FS_SBC_DRIVE_MAINT_2016 0x104
+
+/* Often SCSI responses use the highest integer that can fit in a field
+ * to indicate "unbounded" or limit does not apply. Sometimes represented
+ * in output as "-1" for brevity */
+#define SG_LIB_UNBOUNDED_16BIT 0xffff
+#define SG_LIB_UNBOUNDED_32BIT 0xffffffffU
+#define SG_LIB_UNBOUNDED_64BIT 0xffffffffffffffffULL
+
+#if (__STDC_VERSION__ >= 199901L)  /* C99 or later */
+    typedef uintptr_t sg_uintptr_t;
+#else
+    typedef unsigned long sg_uintptr_t;
+#endif
+
+
+/* The format of the version string is like this: "2.26 20170906" */
+const char * sg_lib_version();
+
+/* Returns length of SCSI command given the opcode (first byte).
+ * Yields the wrong answer for variable length commands (opcode=0x7f)
+ * and potentially some vendor specific commands. */
+int sg_get_command_size(unsigned char cdb_byte0);
+
+/* Command name given pointer to the cdb. Certain command names
+ * depend on peripheral type (give 0 or -1 if unknown). Places command
+ * name into buff and will write no more than buff_len bytes. */
+void sg_get_command_name(const unsigned char * cdbp, int peri_type,
+                         int buff_len, char * buff);
+
+/* Command name given only the first byte (byte 0) of a cdb and
+ * peripheral type (give 0 or -1 if unknown). */
+void sg_get_opcode_name(unsigned char cdb_byte0, int peri_type, int buff_len,
+                        char * buff);
+
+/* Command name given opcode (byte 0), service action and peripheral type.
+ * If no service action give 0, if unknown peripheral type give 0 or -1 . */
+void sg_get_opcode_sa_name(unsigned char cdb_byte0, int service_action,
+                           int peri_type, int buff_len, char * buff);
+
+/* Fetch scsi status string. */
+void sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff);
+
+/* This is a slightly stretched SCSI sense "descriptor" format header.
+ * The addition is to allow the 0x70 and 0x71 response codes. The idea
+ * is to place the salient data of both "fixed" and "descriptor" sense
+ * format into one structure to ease application processing.
+ * The original sense buffer should be kept around for those cases
+ * in which more information is required (e.g. the LBA of a MEDIUM ERROR). */
+struct sg_scsi_sense_hdr {
+    unsigned char response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */
+    unsigned char sense_key;
+    unsigned char asc;
+    unsigned char ascq;
+    unsigned char byte4;
+    unsigned char byte5;
+    unsigned char byte6;
+    unsigned char additional_length;
+};
+
+/* Maps the salient data from a sense buffer which is in either fixed or
+ * descriptor format into a structure mimicking a descriptor format
+ * header (i.e. the first 8 bytes of sense descriptor format).
+ * If zero response code returns false. Otherwise returns true and if 'sshp'
+ * is non-NULL then zero all fields and then set the appropriate fields in
+ * that structure. sshp::additional_length is always 0 for response
+ * codes 0x70 and 0x71 (fixed format). */
+bool sg_scsi_normalize_sense(const unsigned char * sensep, int sense_len,
+                             struct sg_scsi_sense_hdr * sshp);
+
+/* Attempt to find the first SCSI sense data descriptor that matches the
+ * given 'desc_type'. If found return pointer to start of sense data
+ * descriptor; otherwise (including fixed format sense data) returns NULL. */
+const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
+                                              int sense_len, int desc_type);
+
+/* Get sense key from sense buffer. If successful returns a sense key value
+ * between 0 and 15. If sense buffer cannot be decode, returns -1 . */
+int sg_get_sense_key(const unsigned char * sensep, int sense_len);
+
+/* Yield string associated with sense_key value. Returns 'buff'. */
+char * sg_get_sense_key_str(int sense_key, int buff_len, char * buff);
+
+/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
+char * sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff);
+
+/* Returns true if valid bit set, false if valid bit clear. Irrespective the
+ * information field is written out via 'info_outp' (except when it is
+ * NULL). Handles both fixed and descriptor sense formats. */
+bool sg_get_sense_info_fld(const unsigned char * sensep, int sb_len,
+                           uint64_t * info_outp);
+
+/* Returns true if fixed format or command specific information descriptor
+ * is found in the descriptor sense; else false. If available the command
+ * specific information field (4 byte integer in fixed format, 8 byte
+ * integer in descriptor format) is written out via 'cmd_spec_outp'.
+ * Handles both fixed and descriptor sense formats. */
+bool sg_get_sense_cmd_spec_fld(const unsigned char * sensep, int sb_len,
+                               uint64_t * cmd_spec_outp);
+
+/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set.
+ * In descriptor format if the stream commands descriptor not found
+ * then returns false. Writes true or false corresponding to these bits to
+ * the last three arguments if they are non-NULL. */
+bool sg_get_sense_filemark_eom_ili(const unsigned char * sensep, int sb_len,
+                                   bool * filemark_p, bool * eom_p,
+                                   bool * ili_p);
+
+/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also
+ * returns true if progress indication sense data descriptor found. Places
+ * progress field from sense data where progress_outp points. If progress
+ * field is not available returns false. Handles both fixed and descriptor
+ * sense formats. N.B. App should multiply by 100 and divide by 65536
+ * to get percentage completion from given value. */
+bool sg_get_sense_progress_fld(const unsigned char * sensep, int sb_len,
+                               int * progress_outp);
+
+/* Closely related to sg_print_sense(). Puts decoded sense data in 'buff'.
+ * Usually multiline with multiple '\n' including one trailing. If
+ * 'raw_sinfo' set appends sense buffer in hex. 'leadin' is string prepended
+ * to each line written to 'buff', NULL treated as "". Returns the number of
+ * bytes written to 'buff' excluding the trailing '\0'.
+ * N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the first
+ * line output. Also this function returned type void. */
+int sg_get_sense_str(const char * leadin, const unsigned char * sense_buffer,
+                     int sb_len, bool raw_sinfo, int buff_len, char * buff);
+
+/* Decode descriptor format sense descriptors (assumes sense buffer is
+ * in descriptor format). 'leadin' is string prepended to each line written
+ * to 'b', NULL treated as "". Returns the number of bytes written to 'b'
+ * excluding the trailing '\0'. */
+int sg_get_sense_descriptors_str(const char * leadin,
+                                 const unsigned char * sense_buffer,
+                                 int sb_len, int blen, char * b);
+
+/* Decodes a designation descriptor (e.g. as found in the Device
+ * Identification VPD page (0x83)) into string 'b' whose maximum length is
+ * blen. 'leadin' is string prepended to each line written to 'b', NULL
+ * treated as "". Returns the number of bytes written to 'b' excluding the
+ * trailing '\0'. */
+int sg_get_designation_descriptor_str(const char * leadin,
+                                      const unsigned char * ddp, int dd_len,
+                                      bool print_assoc, bool do_long,
+                                      int blen, char * b);
+
+/* Yield string associated with peripheral device type (pdt). Returns
+ * 'buff'. If 'pdt' out of range yields "bad pdt" string. */
+char * sg_get_pdt_str(int pdt, int buff_len, char * buff);
+
+/* Some lesser used PDTs share a lot in common with a more used PDT.
+ * Examples are PDT_ADC decaying to PDT_TAPE and PDT_ZBC to PDT_DISK.
+ * If such a lesser used 'pdt' is given to this function, then it will
+ * return the more used PDT (i.e. "decays to"); otherwise 'pdt' is returned.
+ * Valid for 'pdt' 0 to 31, for other values returns 0. */
+int sg_lib_pdt_decay(int pdt);
+
+/* Yield string associated with transport protocol identifier (tpi). Returns
+ * 'buff'. If 'tpi' out of range yields "bad tpi" string. */
+char * sg_get_trans_proto_str(int tpi, int buff_len, char * buff);
+
+/* Decode TransportID pointed to by 'bp' of length 'bplen'. Place decoded
+ * string output in 'buff' which is also the return value. Each new line
+ * is prefixed by 'leadin'. If leadin NULL treat as "". */
+char * sg_decode_transportid_str(const char * leadin, unsigned char * bp,
+                                 int bplen, bool only_one, int buff_len,
+                                 char * buff);
+
+/* Returns a designator's type string given 'val' (0 to 15 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_type_str(int val);
+
+/* Returns a designator's code_set string given 'val' (0 to 15 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_code_set_str(int val);
+
+/* Returns a designator's association string given 'val' (0 to 3 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_assoc_str(int val);
+
+/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31)
+ * returns pointer to string (same as 'buff') associated with 'sfs_code'.
+ * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match
+ * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL
+ * then where it points is set to true if a match is found else it is set to
+ * false. If 'buff' is not NULL then in the case of a match a descriptive
+ * string is written to 'buff' while if there is not a not then a string
+ * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC
+ * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL.
+ * Example:
+ *    char b[64];
+ *    ...
+ *    printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0));
+ */
+const char * sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len,
+                            char * buff, bool * foundp, int verbose);
+
+/* This is a heuristic that takes into account the command bytes and length
+ * to decide whether the presented unstructured sequence of bytes could be
+ * a SCSI command. If so it returns true otherwise false. Vendor specific
+ * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed
+ * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The
+ * only SCSI commands considered above 16 bytes of length 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.
+ * No NVMe commands (64 bytes long plus some extra added by some OSes) have
+ * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS
+ * structures that are sent across the wire. The 'FIS register' structure is
+ * used to move a command from a SATA host to device, but the ATA 'command'
+ * is not the first byte. So it is harder to say what will happen if a
+ * FIS structure is presented as a SCSI command, hopfully there is a low
+ * probability this function will yield true in that case. */
+bool sg_is_scsi_cdb(const uint8_t * cdbp, int clen);
+
+/* Yield string associated with NVMe command status value in sct_sc. It
+ * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25
+ * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC).
+ * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found
+ * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated.
+ * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/
+char * sg_get_nvme_cmd_status_str(uint16_t sct_sc, int buff_len, char * buff);
+
+/* Attempts to map NVMe status value ((SCT << 8) | SC) n sct_sc to a SCSI
+ * status, sense_key, asc and ascq tuple. If successful returns true and
+ * writes to non-NULL pointer arguments; otherwise returns false. */
+bool sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p,
+                         uint8_t * asc_p, uint8_t * ascq_p);
+
+extern FILE * sg_warnings_strm;
+
+void sg_set_warnings_strm(FILE * warnings_strm);
+
+/* The following "print" functions send ACSII to 'sg_warnings_strm' file
+ * descriptor (default value is stderr). 'leadin' is string prepended to
+ * each line printed out, NULL treated as "". */
+void sg_print_command(const unsigned char * command);
+void sg_print_scsi_status(int scsi_status);
+
+/* 'leadin' is string prepended to each line printed out, NULL treated as
+ * "". N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the
+ * first line printed. */
+void sg_print_sense(const char * leadin, const unsigned char * sense_buffer,
+                    int sb_len, bool raw_info);
+
+/* Following examines exit_status and outputs a clear error message to
+ * warnings_strm (usually stderr) if one is known and returns true.
+ * Otherwise it doesn't print anything and returns false. Note that if
+ * exit_status==0 then returns true but prints nothing and if
+ * exit_status<0 ("some error occurred") false is returned. If leadin is
+ * non-NULL is will be printed before error message. */
+bool sg_if_can2stderr(const char * leadin, int exit_status);
+
+/* Utilities can use these exit status values for syntax errors and
+ * file (device node) problems (e.g. not found or permissions). */
+#define SG_LIB_SYNTAX_ERROR 1   /* command line syntax problem */
+#define SG_LIB_FILE_ERROR 15    /* device or other file problem */
+
+/* The sg_err_category_sense() function returns one of the following.
+ * These may be used as exit status values (from a process). Notice that
+ * some of the lower values correspond to SCSI sense key values. */
+#define SG_LIB_CAT_CLEAN 0      /* No errors or other information */
+/* Value 1 left unused for utilities to use SG_LIB_SYNTAX_ERROR */
+#define SG_LIB_CAT_NOT_READY 2  /* sense key, unit stopped? */
+                                /*       [sk,asc,ascq: 0x2,*,*] */
+#define SG_LIB_CAT_MEDIUM_HARD 3 /* medium or hardware error, blank check */
+                                /*       [sk,asc,ascq: 0x3/0x4/0x8,*,*] */
+#define SG_LIB_CAT_ILLEGAL_REQ 5 /* Illegal request (other than invalid */
+                                /* opcode):   [sk,asc,ascq: 0x5,*,*] */
+#define SG_LIB_CAT_UNIT_ATTENTION 6 /* sense key, device state changed */
+                                /*       [sk,asc,ascq: 0x6,*,*] */
+        /* was SG_LIB_CAT_MEDIA_CHANGED earlier [sk,asc,ascq: 0x6,0x28,*] */
+#define SG_LIB_CAT_DATA_PROTECT 7 /* sense key, media write protected? */
+                                /*       [sk,asc,ascq: 0x7,*,*] */
+#define SG_LIB_CAT_INVALID_OP 9 /* (Illegal request,) Invalid opcode: */
+                                /*       [sk,asc,ascq: 0x5,0x20,0x0] */
+#define SG_LIB_CAT_COPY_ABORTED 10 /* sense key, some data transferred */
+                                /*       [sk,asc,ascq: 0xa,*,*] */
+#define SG_LIB_CAT_ABORTED_COMMAND 11 /* interpreted from sense buffer */
+                                /*       [sk,asc,ascq: 0xb,! 0x10,*] */
+#define SG_LIB_CAT_MISCOMPARE 14 /* sense key, probably verify */
+                                /*       [sk,asc,ascq: 0xe,*,*] */
+#define SG_LIB_CAT_NO_SENSE 20  /* sense data with key of "no sense" */
+                                /*       [sk,asc,ascq: 0x0,*,*] */
+#define SG_LIB_CAT_RECOVERED 21 /* Successful command after recovered err */
+                                /*       [sk,asc,ascq: 0x1,*,*] */
+#define SG_LIB_CAT_RES_CONFLICT SAM_STAT_RESERVATION_CONFLICT
+                                /* 24: this is a SCSI status, not sense. */
+                                /* It indicates reservation by another */
+                                /* machine blocks this command */
+#define SG_LIB_CAT_CONDITION_MET 25 /* SCSI status, not sense key. */
+                                    /* Only from PRE-FETCH (SBC-4) */
+#define SG_LIB_CAT_BUSY       26 /* SCSI status, not sense. Invites retry */
+#define SG_LIB_CAT_TS_FULL    27 /* SCSI status, not sense. Wait then retry */
+#define SG_LIB_CAT_ACA_ACTIVE 28 /* SCSI status; ACA seldom used */
+#define SG_LIB_CAT_TASK_ABORTED 29 /* SCSI status, this command aborted by? */
+#define SG_LIB_CAT_PROTECTION 40 /* subset of aborted command (for PI, DIF) */
+                                /*       [sk,asc,ascq: 0xb,0x10,*] */
+#define SG_LIB_NVME_STATUS 48   /* NVMe Status Field (SF) other than 0 */
+#define SG_LIB_WILD_RESID 49    /* Residual value for data-in transfer of a */
+                                /* SCSI command is nonsensical */
+#define SG_LIB_OS_BASE_ERR 50   /* in Linux: values found in: */
+                                /* include/uapi/asm-generic/errno-base.h */
+                                /* Example: ENOMEM reported as 62 (=50+12) */
+#define SG_LIB_CAT_MALFORMED 97 /* Response to SCSI command malformed */
+#define SG_LIB_CAT_SENSE 98     /* Something else is in the sense buffer */
+#define SG_LIB_CAT_OTHER 99     /* Some other error/warning has occurred */
+                                /* (e.g. a transport or driver error) */
+
+/* Returns a SG_LIB_CAT_* value. If cannot decode sense_buffer or a less
+ * common sense key then return SG_LIB_CAT_SENSE .*/
+int sg_err_category_sense(const unsigned char * sense_buffer, int sb_len);
+
+/* Here are some additional sense data categories that are not returned
+ * by sg_err_category_sense() but are returned by some related functions. */
+#define SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO 17 /* Illegal request (other than */
+                                /* invalid opcode) plus 'info' field: */
+                                /*  [sk,asc,ascq: 0x5,*,*] */
+#define SG_LIB_CAT_MEDIUM_HARD_WITH_INFO 18 /* medium or hardware error */
+                                /* sense key plus 'info' field: */
+                                /*       [sk,asc,ascq: 0x3/0x4,*,*] */
+#define SG_LIB_CAT_PROTECTION_WITH_INFO 41 /* aborted command sense key, */
+                                /* protection plus 'info' field: */
+                                /*  [sk,asc,ascq: 0xb,0x10,*] */
+#define SG_LIB_CAT_TIMEOUT 33
+
+/* Yield string associated with sense category. Returns 'buff' (or pointer
+ * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then
+ * yield "Sense category: <sense_cat>" string. */
+const char * sg_get_category_sense_str(int sense_cat, int buff_len,
+                                       char * buff, int verbose);
+
+
+/* Iterates to next designation descriptor in the device identification
+ * VPD page. The 'initial_desig_desc' should point to start of first
+ * descriptor with 'page_len' being the number of valid bytes in that
+ * and following descriptors. To start, 'off' should point to a negative
+ * value, thereafter it should point to the value yielded by the previous
+ * call. If 0 returned then 'initial_desig_desc + *off' should be a valid
+ * descriptor; returns -1 if normal end condition and -2 for an abnormal
+ * termination. Matches association, designator_type and/or code_set when
+ * any of those values are greater than or equal to zero. */
+int sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len,
+                       int * off, int m_assoc, int m_desig_type,
+                       int m_code_set);
+
+
+/* <<< General purpose (i.e. not SCSI specific) utility functions >>> */
+
+/* Always returns valid string even if errnum is wild (or library problem).
+ * If errnum is negative, flip its sign. */
+char * safe_strerror(int errnum);
+
+
+/* Print (to stdout) 'str' of bytes in hex, 16 bytes per line optionally
+ * followed at the right hand side of the line with an ASCII interpretation.
+ * Each line is prefixed with an address, starting at 0 for str[0]..str[15].
+ * All output numbers are in hex. 'no_ascii' allows for 3 output types:
+ *     > 0     each line has address then up to 16 ASCII-hex bytes
+ *     = 0     in addition, the bytes are listed in ASCII to the right
+ *     < 0     only the ASCII-hex bytes are listed (i.e. without address)
+*/
+void dStrHex(const char * str, int len, int no_ascii);
+
+/* Print (to sg_warnings_strm (stderr)) 'str' of bytes in hex, 16 bytes per
+ * line optionally followed at right by its ASCII interpretation. Same
+ * logic as dStrHex() with different output stream (i.e. stderr). */
+void dStrHexErr(const char * str, int len, int no_ascii);
+
+/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space
+ * separated) to 'b' not to exceed 'b_len' characters. Each line
+ * starts with 'leadin' (NULL for no leadin) and there are 16 bytes
+ * per line with an extra space between the 8th and 9th bytes. 'format'
+ * is 0 for repeat in printable ASCII ('.' for non printable chars) to
+ * right of each line; 1 don't (so just output ASCII hex). Returns
+ * number of bytes written to 'b' excluding the trailing '\0'. */
+int dStrHexStr(const char * str, int len, const char * leadin, int format,
+               int cb_len, char * cbp);
+
+/* The following 3 functions are equivalent to dStrHex(), dStrHexErr() and
+ * dStrHexStr() respectively. The difference is the type of the first of
+ * argument: uint8_t instead of char. The name of the argument is changed
+ * to b_str to stress it is a pointer to the start of a binary string. */
+void hex2stdout(const uint8_t * b_str, int len, int no_ascii);
+void hex2stderr(const uint8_t * b_str, int len, int no_ascii);
+int hex2str(const uint8_t * b_str, int len, const char * leadin, int format,
+            int cb_len, char * cbp);
+
+/* Returns true when executed on big endian machine; else returns false.
+ * Useful for displaying ATA identify words (which need swapping on a
+ * big endian machine). */
+bool sg_is_big_endian();
+
+/* Returns true if byte sequence starting at bp with a length of b_len is
+ * all zeros (for sg_all_zeros()) or all 0xff_s (for sg_all_ffs());
+ * otherwise returns false. If bp is NULL ir b_len <= 0 returns false. */
+bool sg_all_zeros(const uint8_t * bp, int b_len);
+bool sg_all_ffs(const uint8_t * bp, int b_len);
+
+/* Extract character sequence from ATA words as in the model string
+ * in a IDENTIFY DEVICE response. Returns number of characters
+ * written to 'ochars' before 0 character is found or 'num' words
+ * are processed. */
+int sg_ata_get_chars(const uint16_t * word_arr, int start_word,
+                     int num_words, bool is_big_endian, char * ochars);
+
+/* Print (to stdout) 16 bit 'words' in hex, 8 words per line optionally
+ * followed at the right hand side of the line with an ASCII interpretation
+ * (pairs of ASCII characters in big endian order (upper first)).
+ * Each line is prefixed with an address, starting at 0.
+ * All output numbers are in hex. 'no_ascii' allows for 3 output types:
+ *     > 0     each line has address then up to 8 ASCII-hex words
+ *     = 0     in addition, the words are listed in ASCII pairs to the right
+ *     = -1    only the ASCII-hex words are listed (i.e. without address)
+ *     = -2    only the ASCII-hex words, formatted for "hdparm --Istdin"
+ *     < -2    same as -1
+ * If 'swapb' is true then bytes in each word swapped. Needs to be set
+ * for ATA IDENTIFY DEVICE response on big-endian machines.
+*/
+void dWordHex(const uint16_t * words, int num, int no_ascii, bool swapb);
+
+/* If the number in 'buf' can not be decoded or the multiplier is unknown
+ * then -1 is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H')
+ * suffix. Otherwise a decimal multiplier suffix may be given. Recognised
+ * multipliers: c C  *1;  w W  *2; b  B *512;  k K KiB  *1,024;
+ * KB  *1,000;  m M MiB  *1,048,576; MB *1,000,000; g G GiB *1,073,741,824;
+ * GB *1,000,000,000 and <n>x<m> which multiplies <n> by <m> . Ignore leading
+ * spaces and tabs; accept comma, hyphen, space, tab and hash as terminator.
+ */
+int sg_get_num(const char * buf);
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * negative numbers and '-1' must be treated separately. */
+int sg_get_num_nomult(const char * buf);
+
+/* If the number in 'buf' can not be decoded or the multiplier is unknown
+ * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H')
+ * suffix. Otherwise a decimal multiplier suffix may be given. In addition
+ * to supporting the multipliers of sg_get_num(), this function supports:
+ * t T TiB  *(2**40); TB *(10**12); p P PiB  *(2**50); PB  *(10**15) .
+ * Ignore leading spaces and tabs; accept comma, hyphen, space, tab and hash
+ * as terminator. */
+int64_t sg_get_llnum(const char * buf);
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * 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);
+
+/* If os_err_num is within bounds then the returned value is 'os_err_num +
+ * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0
+ * is returned. */
+int sg_convert_errno(int os_err_num);
+
+
+/* <<< Architectural support functions [is there a better place?] >>> */
+
+/* Non Unix OSes distinguish between text and binary files.
+ * Set text mode on fd. Does nothing in Unix. Returns negative number on
+ * failure. */
+int sg_set_text_mode(int fd);
+
+/* Set binary mode on fd. Does nothing in Unix. Returns negative number on
+ * failure. */
+int sg_set_binary_mode(int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif          /* SG_LIB_H */
diff --git a/tools/sg_write_buffer/include/sg_lib_data.h b/tools/sg_write_buffer/include/sg_lib_data.h
new file mode 100644
index 0000000..09cd53c
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_lib_data.h
@@ -0,0 +1,121 @@
+#ifndef SG_LIB_DATA_H
+#define SG_LIB_DATA_H
+
+/*
+ * Copyright (c) 2007-2018 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.
+ */
+
+/*
+ * This header file contains some structure declarations and array name
+ * declarations which are defined in the sg_lib_data.c .
+ * Typically this header does not need to be exposed to users of the
+ * sg_lib interface declared in sg_libs.h .
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Operation codes with associated service actions that change or qualify
+ * the command name */
+#define SG_EXTENDED_COPY 0x83 /* since spc4r34 became next entry */
+#define SG_3PARTY_COPY_OUT 0x83 /* new in spc4r34: Third party copy out */
+#define SG_RECEIVE_COPY 0x84  /* since spc4r34 became next entry */
+#define SG_3PARTY_COPY_IN 0x84 /* new in spc4r34: Third party copy in */
+#define SG_MAINTENANCE_IN 0xa3
+#define SG_MAINTENANCE_OUT 0xa4
+#define SG_PERSISTENT_RESERVE_IN 0x5e
+#define SG_PERSISTENT_RESERVE_OUT 0x5f
+#define SG_READ_ATTRIBUTE 0x8c
+#define SG_READ_BUFFER 0x3c     /* now READ BUFFER(10) */
+#define SG_READ_BUFFER_16 0x9b
+#define SG_READ_POSITION 0x34   /* SSC command with service actions */
+#define SG_SANITIZE 0x48
+#define SG_SERVICE_ACTION_BIDI 0x9d
+#define SG_SERVICE_ACTION_IN_12 0xab
+#define SG_SERVICE_ACTION_IN_16 0x9e
+#define SG_SERVICE_ACTION_OUT_12 0xa9
+#define SG_SERVICE_ACTION_OUT_16 0x9f
+#define SG_VARIABLE_LENGTH_CMD 0x7f
+#define SG_WRITE_BUFFER 0x3b
+#define SG_ZONING_OUT 0x94
+#define SG_ZONING_IN 0x95
+
+
+
+struct sg_lib_simple_value_name_t {
+    int value;
+    const char * name;
+};
+
+struct sg_lib_value_name_t {
+    int value;
+    int peri_dev_type; /* 0 -> SPC and/or PDT_DISK, >0 -> PDT */
+    const char * name;
+};
+
+struct sg_lib_asc_ascq_t {
+    unsigned char asc;          /* additional sense code */
+    unsigned char ascq;         /* additional sense code qualifier */
+    const char * text;
+};
+
+struct sg_lib_asc_ascq_range_t {
+    unsigned char asc;  /* additional sense code (ASC) */
+    unsigned char ascq_min;     /* ASCQ minimum in range */
+    unsigned char ascq_max;     /* ASCQ maximum in range */
+    const char * text;
+};
+
+/* First use: SCSI status, sense_key, asc, ascq tuple */
+struct sg_lib_4tuple_u8 {
+    uint8_t t1;
+    uint8_t t2;
+    uint8_t t3;
+    uint8_t t4;
+};
+
+
+extern const char * sg_lib_version_str;
+
+extern struct sg_lib_value_name_t sg_lib_normal_opcodes[];
+extern struct sg_lib_value_name_t sg_lib_read_buff_arr[];
+extern struct sg_lib_value_name_t sg_lib_write_buff_arr[];
+extern struct sg_lib_value_name_t sg_lib_maint_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_maint_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_pr_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_pr_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_in12_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_out12_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_in16_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_out16_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_bidi_arr[];
+extern struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_variable_length_arr[];
+extern struct sg_lib_value_name_t sg_lib_zoning_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_zoning_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_read_attr_arr[];
+extern struct sg_lib_value_name_t sg_lib_read_pos_arr[];
+extern struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[];
+extern struct sg_lib_asc_ascq_t sg_lib_asc_ascq[];
+extern struct sg_lib_value_name_t sg_lib_scsi_feature_sets[];
+extern const char * sg_lib_sense_key_desc[];
+extern const char * sg_lib_pdt_strs[];
+extern const char * sg_lib_transport_proto_strs[];
+extern int sg_lib_pdt_decay_arr[];
+
+extern struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[];
+extern struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_linux_inc.h b/tools/sg_write_buffer/include/sg_linux_inc.h
new file mode 100644
index 0000000..b587c6c
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_linux_inc.h
@@ -0,0 +1,56 @@
+#ifndef SG_LINUX_INC_H
+#define SG_LINUX_INC_H
+
+#ifdef SG_KERNEL_INCLUDES
+  #define __user
+  typedef unsigned char u8;
+  #include "/usr/src/linux/include/scsi/sg.h"
+  #include "/usr/src/linux/include/scsi/scsi.h"
+#else
+  #ifdef SG_TRICK_GNU_INCLUDES
+    #include <linux/../scsi/sg.h>
+    #include <linux/../scsi/scsi.h>
+  #else
+    #include <scsi/sg.h>
+    #include <scsi/scsi.h>
+  #endif
+#endif
+
+#ifdef BLKGETSIZE64
+  #ifndef u64
+    #include <stdint.h>   /* C99 header for exact integer types */
+    typedef uint64_t u64; /* problems with BLKGETSIZE64 ioctl in lk 2.4 */
+  #endif
+#endif
+
+/*
+  Getting the correct include files for the sg interface can be an ordeal.
+  In a perfect world, one would just write:
+    #include <scsi/sg.h>
+    #include <scsi/scsi.h>
+  This would include the files found in the /usr/include/scsi directory.
+  Those files are maintained with the GNU library which may or may not
+  agree with the kernel and version of sg driver that is running. Any
+  many cases this will not matter. However in some it might, for example
+  glibc 2.1's include files match the sg driver found in the lk 2.2
+  series. Hence if glibc 2.1 is used with lk 2.4 then the additional
+  sg v3 interface will not be visible.
+  If this is a problem then defining SG_KERNEL_INCLUDES will access the
+  kernel supplied header files (assuming they are in the normal place).
+  The GNU library maintainers and various kernel people don't like
+  this approach (but it does work).
+  The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and
+  was used) prior to glibc 2.2 . Prior to that version /usr/include/linux
+  was a symbolic link to /usr/src/linux/include/linux .
+
+  There are other approaches if this include "mixup" causes pain. These
+  would involve include files being copied or symbolic links being
+  introduced.
+
+  Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES
+  nor SG_TRICK_GNU_INCLUDES is defined.
+
+  dpg 20010415, 20030522
+*/
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_pr2serr.h b/tools/sg_write_buffer/include/sg_pr2serr.h
new file mode 100644
index 0000000..4419087
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pr2serr.h
@@ -0,0 +1,30 @@
+#ifndef SG_PR2SERR_H
+#define SG_PR2SERR_H
+
+/*
+ * Copyright (c) 2004-2018 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 <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#if defined(__GNUC__) || defined(__clang__)
+int pr2serr(const char * fmt, ...)
+        __attribute__ ((format (printf, 1, 2)));
+#else
+int pr2serr(const char * fmt, ...);
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_pt.h b/tools/sg_write_buffer/include/sg_pt.h
new file mode 100644
index 0000000..b01215c
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pt.h
@@ -0,0 +1,215 @@
+#ifndef SG_PT_H
+#define SG_PT_H
+
+/*
+ * Copyright (c) 2005-2018 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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This declaration hides the fact that each implementation has its own
+ * structure "derived" (using a C++ term) from this one. It compiles
+ * because 'struct sg_pt_base' is only referenced (by pointer: 'objp')
+ * in this interface. An instance of this structure represents the
+ * context of one SCSI command. */
+struct sg_pt_base;
+
+
+/* The format of the version string is like this: "2.01 20090201".
+ * The leading digit will be incremented if this interface changes
+ * in a way that may impact backward compatibility. */
+const char * scsi_pt_version();
+
+
+/* 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);
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
+ * together. Returns valid file descriptor( >= 0 ) if successful, otherwise
+ * returns -1 or a negated errno.
+ * In Win32 O_EXCL translated to equivalent. */
+int scsi_pt_open_flags(const char * device_name, int flags, int verbose);
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int scsi_pt_close_device(int device_fd);
+
+/* Assumes dev_fd is an "open" file handle associated with device_name. If
+ * the implementation (possibly for one OS) cannot determine from dev_fd if
+ * a SCSI or NVMe pass-through is referenced, then it might guess based on
+ * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if
+ * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is
+ * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0.
+ * If error, returns negated errno (operating system) value. */
+int check_pt_file_handle(int dev_fd, const char * device_name, int verbose);
+
+
+/* Creates an object that can be used to issue one or more SCSI commands
+ * (or task management functions). Returns NULL if problem.
+ * Once this object has been created it should be destroyed with
+ * destruct_scsi_pt_obj() when it is no longer needed. */
+struct sg_pt_base * construct_scsi_pt_obj(void);
+
+/* An alternate way to create an object that can be used to issue one or
+ * more SCSI commands (or task management functions). This variant
+ * associate a device file descriptor (handle) with the object and a
+ * verbose argument that causes error messages if errors occur. The
+ * reason for this is to optionally allow the detection of NVMe devices
+ * that will cause pt_device_is_nvme() to return true. Set dev_fd to
+ * -1 if no open device file descriptor is available. 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);
+
+/* 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 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);
+
+/* 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 * objp);
+
+/* Clear state information held in *objp . This allows this object to be
+ * used to issue more than one SCSI command. The dev_fd is remembered.
+ * Use set_pt_file_handle() to change dev_fd. */
+void clear_scsi_pt_obj(struct sg_pt_base * objp);
+
+/* Set the CDB (command descriptor block) */
+void set_scsi_pt_cdb(struct sg_pt_base * objp, const unsigned char * cdb,
+                     int cdb_len);
+/* Set the sense buffer and the maximum length that it can handle */
+void set_scsi_pt_sense(struct sg_pt_base * objp, unsigned char * sense,
+                       int max_sense_len);
+/* Set a pointer and length to be used for data transferred from device */
+void set_scsi_pt_data_in(struct sg_pt_base * objp,   /* from device */
+                         unsigned char * dxferp, int dxfer_ilen);
+/* Set a pointer and length to be used for data transferred to device */
+void set_scsi_pt_data_out(struct sg_pt_base * objp,    /* to device */
+                          const unsigned char * dxferp, int dxfer_olen);
+/* 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);
+/* 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);
+void set_scsi_pt_task_management(struct sg_pt_base * objp, int tmf_code);
+void set_scsi_pt_task_attr(struct sg_pt_base * objp, int attribute,
+                           int priority);
+
+/* Following is a guard which is defined when set_scsi_pt_flags() is
+ * present. Older versions of this library may not have this function. */
+#define SCSI_PT_FLAGS_FUNCTION 1
+/* If neither QUEUE_AT_HEAD nor QUEUE_AT_TAIL are given, or both
+ * 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 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);
+
+#define SCSI_PT_DO_START_OK 0
+#define SCSI_PT_DO_BAD_PARAMS 1
+#define SCSI_PT_DO_TIMEOUT 2
+#define SCSI_PT_DO_NVME_STATUS 48       /* == SG_LIB_NVME_STATUS */
+/* If OS error prior to or during command submission then returns negated
+ * error value (e.g. Unix '-errno'). This includes interrupted system calls
+ * (e.g. by a signal) in which case -EINTR would be returned. Note that
+ * system call errors also can be fetched with get_scsi_pt_os_err().
+ * Return 0 if okay (i.e. at the very least: command sent). Positive
+ * return values are errors (see SCSI_PT_DO_* defines). If a file descriptor
+ * has already been provided by construct_scsi_pt_obj_with_fd() then the
+ * given 'fd' can be -1 or the same value as given to the constructor. */
+int do_scsi_pt(struct sg_pt_base * objp, int fd, int timeout_secs,
+               int verbose);
+
+#define SCSI_PT_RESULT_GOOD 0
+#define SCSI_PT_RESULT_STATUS 1 /* other than GOOD and CHECK CONDITION */
+#define SCSI_PT_RESULT_SENSE 2
+#define SCSI_PT_RESULT_TRANSPORT_ERR 3
+#define SCSI_PT_RESULT_OS_ERR 4
+/* highest numbered applicable category returned */
+int get_scsi_pt_result_category(const struct sg_pt_base * objp);
+
+/* If not available return 0 which implies there is no residual
+ * value. If supported the number of bytes actually sent back by
+ * the device is 'dxfer_ilen - get_scsi_pt_len()' bytes.  */
+int get_scsi_pt_resid(const struct sg_pt_base * objp);
+
+/* Returns SCSI status value (from device that received the command). If an
+ * NVMe command was issued directly (i.e. through do_scsi_pt() then return
+ * NVMe status (i.e. ((SCT << 8) | SC)) */
+int get_scsi_pt_status_response(const struct sg_pt_base * objp);
+
+/* Returns SCSI status value or, if NVMe command given to do_scsi_pt(),
+ * then returns NVMe result (i.e. DWord(0) from completion queue). If
+ * 'objp' is NULL then returns 0xffffffff. */
+uint32_t get_pt_result(const struct sg_pt_base * objp);
+
+/* Actual sense length returned. If sense data is present but
+   actual sense length is not known, return 'max_sense_len' */
+int get_scsi_pt_sense_len(const struct sg_pt_base * objp);
+
+/* If not available return 0 (for success). */
+int get_scsi_pt_os_err(const struct sg_pt_base * objp);
+char * get_scsi_pt_os_err_str(const struct sg_pt_base * objp, int max_b_len,
+                              char * b);
+
+/* If not available return 0 (for success) */
+int get_scsi_pt_transport_err(const struct sg_pt_base * objp);
+void set_scsi_pt_transport_err(struct sg_pt_base * objp, int err);
+char * get_scsi_pt_transport_err_str(const struct sg_pt_base * objp,
+                                     int max_b_len, char * b);
+
+/* If not available return -1 */
+int get_scsi_pt_duration_ms(const struct sg_pt_base * objp);
+
+/* Return true if device associated with 'objp' uses NVMe command set. To
+ * be useful (in modifying the type of command sent (SCSI or NVMe) then
+ * construct_scsi_pt_obj_with_fd() should be used followed by an invocation
+ * of this function. */
+bool pt_device_is_nvme(const struct sg_pt_base * objp);
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'objp', 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 * objp);
+
+
+/* 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 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() ).  */
+void destruct_scsi_pt_obj(struct sg_pt_base * objp);
+
+#ifdef SG_LIB_WIN32
+#define SG_LIB_WIN32_DIRECT 1
+
+/* Request SPT direct interface when state_direct is 1, state_direct set
+ * to 0 for the SPT indirect interface. Default setting selected by build
+ * (i.e. library compile time) and is usually indirect. */
+void scsi_pt_win32_direct(int state_direct);
+
+/* Returns current SPT interface state, 1 for direct, 0 for indirect */
+int scsi_pt_win32_spt_state(void);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_pt_linux.h b/tools/sg_write_buffer/include/sg_pt_linux.h
new file mode 100644
index 0000000..5e22fd7
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pt_linux.h
@@ -0,0 +1,171 @@
+#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 */
+    /* Leave io_hdr in first place of this structure */
+    bool is_sg;
+    bool is_bsg;
+    bool is_nvme;	/* OS device type, if false ignore nvme_direct */
+    bool nvme_direct;	/* false: our SNTL; true: received NVMe command */
+    bool mdxfer_out;    /* direction of metadata xfer, true->data-out */
+    bool scsi_dsense;   /* SCSI descriptor sense active when true */
+    int dev_fd;                 /* -1 if not given (yet) */
+    int in_err;
+    int os_err;
+    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;       /* DW0 from completion queue */
+    uint32_t nvme_status;       /* SCT|SC: DW3 27:17 from completion queue,
+                                 * note: the DNR+More bit are not there.
+                                 * The whole 16 byte completion q entry is
+                                 * sent back as sense data */
+    uint32_t mdxfer_len;
+    void * mdxferp;
+    uint8_t * nvme_id_ctlp;     /* cached response to controller IDENTIFY */
+    uint8_t * free_nvme_id_ctlp;
+    unsigned char tmf_request[4];
+};
+
+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);
+
+/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5)
+ * to the name of its associated char device (e.g. /dev/nvme0). If this
+ * occurs true is returned and the char device name is placed in 'b' (as
+ * long as b_len is sufficient). Otherwise false is returned. */
+bool sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len,
+                              char * b);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif          /* end of SG_PT_LINUX_H */
diff --git a/tools/sg_write_buffer/include/sg_pt_nvme.h b/tools/sg_write_buffer/include/sg_pt_nvme.h
new file mode 100644
index 0000000..3df98b4
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pt_nvme.h
@@ -0,0 +1,172 @@
+#ifndef SG_PT_NVME_H
+#define SG_PT_NVME_H
+
+/*
+ * Copyright (c) 2017-2018 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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* structures copied and slightly modified from <linux/nvme_ioctl.h> which
+ * is Copyright (c) 2011-2014, Intel Corporation.  */
+
+
+/* Note that the command input structure is in (packed) "cpu" format. That
+ * means, for example, if the CPU is little endian (most are) then so is the
+ * structure. However what comes out in the data-in buffer (e.g. for the
+ * Admin Identify command response) is almost all little endian following ATA
+ * (but no SCSI and IP which are big endian) and Intel's preference. There
+ * are exceptions, for example the EUI-64 identifiers in the Admin Identify
+ * response are big endian.
+ *
+ * Code online (e.g. nvme-cli at github.com) seems to like packed strcutures,
+ * the author prefers byte offset plus a range of unaligned integer builders
+ * such as those in sg_unaligned.h .
+ */
+
+#ifdef __GNUC__
+#ifndef __clang__
+  struct __attribute__((__packed__)) sg_nvme_user_io
+#else
+  struct sg_nvme_user_io
+#endif
+#else
+struct sg_nvme_user_io
+#endif
+{
+        uint8_t opcode;
+        uint8_t flags;
+        uint16_t control;
+        uint16_t nblocks;
+        uint16_t rsvd;
+        uint64_t metadata;
+        uint64_t addr;
+        uint64_t slba;
+        uint32_t dsmgmt;
+        uint32_t reftag;
+        uint16_t apptag;
+        uint16_t appmask;
+}
+#ifdef SG_LIB_FREEBSD
+__packed;
+#else
+;
+#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
+#else
+  struct sg_nvme_passthru_cmd
+#endif
+#else
+struct sg_nvme_passthru_cmd
+#endif
+{
+        uint8_t opcode;
+        uint8_t flags;
+        uint16_t rsvd1;
+        uint32_t nsid;
+        uint32_t cdw2;
+        uint32_t cdw3;
+        uint64_t metadata;
+        uint64_t addr;
+        uint32_t metadata_len;
+        uint32_t data_len;
+        uint32_t cdw10;
+        uint32_t cdw11;
+        uint32_t cdw12;
+        uint32_t cdw13;
+        uint32_t cdw14;
+        uint32_t cdw15;
+#ifdef SG_LIB_LINUX
+        uint32_t timeout_ms;
+        uint32_t result;        /* out: DWord(0) from completion queue */
+#endif
+}
+#ifdef SG_LIB_FREEBSD
+__packed;
+#else
+;
+#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             /* length: 1 byte */
+#define SG_NVME_PT_FLAGS 1              /* length: 1 byte */
+#define SG_NVME_PT_RSVD1 2              /* length: 2 bytes */
+#define SG_NVME_PT_NSID 4               /* length: 4 bytes */
+#define SG_NVME_PT_CDW2 8               /* length: 4 bytes */
+#define SG_NVME_PT_CDW3 12              /* length: 4 bytes */
+#define SG_NVME_PT_METADATA 16          /* length: 8 bytes */
+#define SG_NVME_PT_ADDR 24              /* length: 8 bytes */
+#define SG_NVME_PT_METADATA_LEN 32      /* length: 4 bytes */
+#define SG_NVME_PT_DATA_LEN 36          /* length: 4 bytes */
+#define SG_NVME_PT_CDW10 40             /* length: 4 bytes */
+#define SG_NVME_PT_CDW11 44             /* length: 4 bytes */
+#define SG_NVME_PT_CDW12 48             /* length: 4 bytes */
+#define SG_NVME_PT_CDW13 52             /* length: 4 bytes */
+#define SG_NVME_PT_CDW14 56             /* length: 4 bytes */
+#define SG_NVME_PT_CDW15 60             /* length: 4 bytes */
+
+#ifdef SG_LIB_LINUX
+/* General references state that "all NVMe commands are 64 bytes long". If
+ * so then the following are add-ons by Linux, go to the OS and not the
+ * the NVMe device. */
+#define SG_NVME_PT_TIMEOUT_MS 64        /* length: 4 bytes */
+#define SG_NVME_PT_RESULT 68            /* length: 4 bytes */
+#endif
+
+/* Byte offset of Result and Status (plus phase bit) in CQ */
+#define SG_NVME_PT_CQ_RESULT 0          /* CDW0, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW0 0             /* CDW0, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW1 4             /* CDW1, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW2 8             /* CDW2, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW3 12            /* CDW3, length: 4 bytes */
+#define SG_NVME_PT_CQ_STATUS_P 14       /* CDW3 31:16, length: 2 bytes */
+
+
+/* Valid namespace IDs (nsid_s) range from 1 to 0xfffffffe, leaving: */
+#define SG_NVME_BROADCAST_NSID 0xffffffff       /* all namespaces */
+#define SG_NVME_CTL_NSID 0x0            /* the "controller's" namespace */
+
+/* Given the NVMe Identify Controller response and optionally the NVMe
+ * Identify Namespace response (NULL otherwise), generate the SCSI VPD
+ * page 0x83 (device identification) descriptor(s) in dop. Return the
+ * number of bytes written which will not exceed max_do_len. Probably use
+ * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport
+ * protocol (tproto) should be -1 if not known, else SCSI value.
+ * N.B. Does not write total VPD page length into dop[2:3] . */
+int sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p,
+                               const uint8_t * nvme_id_ns_p, int pdt,
+                               int tproto, uint8_t * dop, int max_do_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif          /* SG_PT_NVME_H */
diff --git a/tools/sg_write_buffer/include/sg_pt_win32.h b/tools/sg_write_buffer/include/sg_pt_win32.h
new file mode 100644
index 0000000..b49437f
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pt_win32.h
@@ -0,0 +1,473 @@
+#ifndef SG_PT_WIN32_H
+#define SG_PT_WIN32_H
+/*
+ * The information in this file was obtained from scsi-wnt.h by
+ * Richard Stemmer, rs@epost.de . He in turn gives credit to
+ * Jay A. Key (for scsipt.c).
+ * The plscsi program (by Pat LaVarre <p.lavarre@ieee.org>) has
+ * also been used as a reference.
+ * Much of the information in this header can also be obtained
+ * from msdn.microsoft.com .
+ * Updated for cygwin version 1.7.17 changes 20121026
+ */
+
+/* WIN32_LEAN_AND_MEAN may be required to prevent inclusion of <winioctl.h> */
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SCSI_MAX_SENSE_LEN 64
+#define SCSI_MAX_CDB_LEN 16
+#define SCSI_MAX_INDIRECT_DATA 16384
+
+typedef struct {
+    USHORT          Length;
+    UCHAR           ScsiStatus;
+    UCHAR           PathId;
+    UCHAR           TargetId;
+    UCHAR           Lun;
+    UCHAR           CdbLength;
+    UCHAR           SenseInfoLength;
+    UCHAR           DataIn;
+    ULONG           DataTransferLength;
+    ULONG           TimeOutValue;
+    ULONG_PTR       DataBufferOffset;  /* was ULONG; problem in 64 bit */
+    ULONG           SenseInfoOffset;
+    UCHAR           Cdb[SCSI_MAX_CDB_LEN];
+} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH;
+
+
+typedef struct {
+    USHORT          Length;
+    UCHAR           ScsiStatus;
+    UCHAR           PathId;
+    UCHAR           TargetId;
+    UCHAR           Lun;
+    UCHAR           CdbLength;
+    UCHAR           SenseInfoLength;
+    UCHAR           DataIn;
+    ULONG           DataTransferLength;
+    ULONG           TimeOutValue;
+    PVOID           DataBuffer;
+    ULONG           SenseInfoOffset;
+    UCHAR           Cdb[SCSI_MAX_CDB_LEN];
+} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;
+
+
+typedef struct {
+    SCSI_PASS_THROUGH spt;
+    /* plscsi shows a follow on 16 bytes allowing 32 byte cdb */
+    ULONG           Filler;
+    UCHAR           ucSenseBuf[SCSI_MAX_SENSE_LEN];
+    UCHAR           ucDataBuf[SCSI_MAX_INDIRECT_DATA];
+} SCSI_PASS_THROUGH_WITH_BUFFERS, *PSCSI_PASS_THROUGH_WITH_BUFFERS;
+
+
+typedef struct {
+    SCSI_PASS_THROUGH_DIRECT spt;
+    ULONG           Filler;
+    UCHAR           ucSenseBuf[SCSI_MAX_SENSE_LEN];
+} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;
+
+
+
+typedef struct {
+    UCHAR           NumberOfLogicalUnits;
+    UCHAR           InitiatorBusId;
+    ULONG           InquiryDataOffset;
+} SCSI_BUS_DATA, *PSCSI_BUS_DATA;
+
+
+typedef struct {
+    UCHAR           NumberOfBusses;
+    SCSI_BUS_DATA   BusData[1];
+} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO;
+
+
+typedef struct {
+    UCHAR           PathId;
+    UCHAR           TargetId;
+    UCHAR           Lun;
+    BOOLEAN         DeviceClaimed;
+    ULONG           InquiryDataLength;
+    ULONG           NextInquiryDataOffset;
+    UCHAR           InquiryData[1];
+} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA;
+
+
+typedef struct {
+    ULONG           Length;
+    UCHAR           PortNumber;
+    UCHAR           PathId;
+    UCHAR           TargetId;
+    UCHAR           Lun;
+} SCSI_ADDRESS, *PSCSI_ADDRESS;
+
+/*
+ * Standard IOCTL define
+ */
+#ifndef CTL_CODE
+#define CTL_CODE(DevType, Function, Method, Access)             \
+        (((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
+#endif
+
+/*
+ * file access values
+ */
+#ifndef FILE_ANY_ACCESS
+#define FILE_ANY_ACCESS         0
+#endif
+#ifndef FILE_READ_ACCESS
+#define FILE_READ_ACCESS        0x0001
+#endif
+#ifndef FILE_WRITE_ACCESS
+#define FILE_WRITE_ACCESS       0x0002
+#endif
+
+// IOCTL_STORAGE_QUERY_PROPERTY
+
+#define FILE_DEVICE_MASS_STORAGE    0x0000002d
+#define IOCTL_STORAGE_BASE          FILE_DEVICE_MASS_STORAGE
+#define FILE_ANY_ACCESS             0
+
+// #define METHOD_BUFFERED             0
+
+#define IOCTL_STORAGE_QUERY_PROPERTY \
+    CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+
+#ifndef _DEVIOCTL_
+typedef enum _STORAGE_BUS_TYPE {
+    BusTypeUnknown      = 0x00,
+    BusTypeScsi         = 0x01,
+    BusTypeAtapi        = 0x02,
+    BusTypeAta          = 0x03,
+    BusType1394         = 0x04,
+    BusTypeSsa          = 0x05,
+    BusTypeFibre        = 0x06,
+    BusTypeUsb          = 0x07,
+    BusTypeRAID         = 0x08,
+    BusTypeiScsi        = 0x09,
+    BusTypeSas          = 0x0A,
+    BusTypeSata         = 0x0B,
+    BusTypeSd           = 0x0C,
+    BusTypeMmc          = 0x0D,
+    BusTypeVirtual             = 0xE,
+    BusTypeFileBackedVirtual   = 0xF,
+    BusTypeSpaces       = 0x10,
+    BusTypeNvme         = 0x11,
+    BusTypeSCM          = 0x12,
+    BusTypeUfs          = 0x13,
+    BusTypeMax          = 0x14,
+    BusTypeMaxReserved  = 0x7F
+} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
+
+typedef enum _STORAGE_PROTOCOL_TYPE {
+    ProtocolTypeUnknown = 0,
+    ProtocolTypeScsi,
+    ProtocolTypeAta,
+    ProtocolTypeNvme,
+    ProtocolTypeSd
+} STORAGE_PROTOCOL_TYPE;
+
+typedef enum _STORAGE_PROTOCOL_NVME_DATA_TYPE {
+    NVMeDataTypeUnknown = 0,
+    NVMeDataTypeIdentify,
+    NVMeDataTypeLogPage,
+    NVMeDataTypeFeature
+} STORAGE_PROTOCOL_NVME_DATA_TYPE;
+
+typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA {
+    STORAGE_PROTOCOL_TYPE ProtocolType;
+    ULONG DataType;
+    ULONG ProtocolDataRequestValue;
+    ULONG ProtocolDataRequestSubValue;
+    ULONG ProtocolDataOffset;
+    ULONG ProtocolDataLength;
+    ULONG FixedProtocolReturnData;
+    ULONG Reserved[3];
+} STORAGE_PROTOCOL_SPECIFIC_DATA;
+
+
+typedef struct _STORAGE_DEVICE_DESCRIPTOR {
+    ULONG Version;
+    ULONG Size;
+    UCHAR DeviceType;
+    UCHAR DeviceTypeModifier;
+    BOOLEAN RemovableMedia;
+    BOOLEAN CommandQueueing;
+    ULONG VendorIdOffset;       /* 0 if not available */
+    ULONG ProductIdOffset;      /* 0 if not available */
+    ULONG ProductRevisionOffset;/* 0 if not available */
+    ULONG SerialNumberOffset;   /* -1 if not available ?? */
+    STORAGE_BUS_TYPE BusType;
+    ULONG RawPropertiesLength;
+    UCHAR RawDeviceProperties[1];
+} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
+
+#define STORAGE_PROTOCOL_STRUCTURE_VERSION 0x1
+
+#define IOCTL_STORAGE_PROTOCOL_COMMAND \
+        CTL_CODE(IOCTL_STORAGE_BASE, 0x04F0, METHOD_BUFFERED, \
+                FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+typedef struct _STORAGE_PROTOCOL_COMMAND {
+    DWORD         Version;        /* STORAGE_PROTOCOL_STRUCTURE_VERSION */
+    DWORD         Length;
+    STORAGE_PROTOCOL_TYPE   ProtocolType;
+    DWORD         Flags;
+    DWORD         ReturnStatus;
+    DWORD         ErrorCode;
+    DWORD         CommandLength;
+    DWORD         ErrorInfoLength;
+    DWORD         DataToDeviceTransferLength;
+    DWORD         DataFromDeviceTransferLength;
+    DWORD         TimeOutValue;
+    DWORD         ErrorInfoOffset;
+    DWORD         DataToDeviceBufferOffset;
+    DWORD         DataFromDeviceBufferOffset;
+    DWORD         CommandSpecific;
+    DWORD         Reserved0;
+    DWORD         FixedProtocolReturnData;
+    DWORD         Reserved1[3];
+    BYTE          Command[1];     /* has CommandLength elements */
+} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND;
+
+#endif          /* _DEVIOCTL_ */
+
+typedef struct _STORAGE_DEVICE_UNIQUE_IDENTIFIER {
+    ULONG  Version;
+    ULONG  Size;
+    ULONG  StorageDeviceIdOffset;
+    ULONG  StorageDeviceOffset;
+    ULONG  DriveLayoutSignatureOffset;
+} STORAGE_DEVICE_UNIQUE_IDENTIFIER, *PSTORAGE_DEVICE_UNIQUE_IDENTIFIER;
+
+// Use CompareStorageDuids(PSTORAGE_DEVICE_UNIQUE_IDENTIFIER duid1, duid2)
+// to test for equality
+
+#ifndef _DEVIOCTL_
+typedef enum _STORAGE_QUERY_TYPE {
+    PropertyStandardQuery = 0,
+    PropertyExistsQuery,
+    PropertyMaskQuery,
+    PropertyQueryMaxDefined
+} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
+
+typedef enum _STORAGE_PROPERTY_ID {
+    StorageDeviceProperty = 0,
+    StorageAdapterProperty,
+    StorageDeviceIdProperty,
+    StorageDeviceUniqueIdProperty,
+    StorageDeviceWriteCacheProperty,
+    StorageMiniportProperty,
+    StorageAccessAlignmentProperty,
+    /* Identify controller goes to adapter; Identify namespace to device */
+    StorageAdapterProtocolSpecificProperty = 49,
+    StorageDeviceProtocolSpecificProperty = 50
+} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
+
+typedef struct _STORAGE_PROPERTY_QUERY {
+    STORAGE_PROPERTY_ID PropertyId;
+    STORAGE_QUERY_TYPE QueryType;
+    UCHAR AdditionalParameters[1];
+} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
+
+typedef struct _STORAGE_PROTOCOL_DATA_DESCRIPTOR {
+    DWORD  Version;
+    DWORD  Size;
+    STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecificData;
+} STORAGE_PROTOCOL_DATA_DESCRIPTOR, *PSTORAGE_PROTOCOL_DATA_DESCRIPTOR;
+
+// Command completion status
+// The "Phase Tag" field and "Status Field" are separated in spec. We define
+// them in the same data structure to ease the memory access from software.
+//
+typedef union {
+    struct {
+        USHORT  P           : 1;        // Phase Tag (P)
+
+        USHORT  SC          : 8;        // Status Code (SC)
+        USHORT  SCT         : 3;        // Status Code Type (SCT)
+        USHORT  Reserved    : 2;
+        USHORT  M           : 1;        // More (M)
+        USHORT  DNR         : 1;        // Do Not Retry (DNR)
+    } DUMMYSTRUCTNAME;
+    USHORT AsUshort;
+} NVME_COMMAND_STATUS, *PNVME_COMMAND_STATUS;
+
+// Information of log: NVME_LOG_PAGE_ERROR_INFO. Size: 64 bytes
+//
+typedef struct {
+    ULONGLONG  ErrorCount;
+    USHORT     SQID;           // Submission Queue ID
+    USHORT     CMDID;          // Command ID
+    NVME_COMMAND_STATUS Status; // Status Field: This field indicates the
+                                // Status Field for the command that
+                                // completed. The Status Field is located in
+                                // bits 15:01, bit 00 corresponds to the Phase
+                                // Tag posted for the command.
+    struct {
+        USHORT  Byte        : 8;   // Byte in command that contained error
+        USHORT  Bit         : 3;   // Bit in command that contained error
+        USHORT  Reserved    : 5;
+    } ParameterErrorLocation;
+
+    ULONGLONG  Lba;            // LBA: This field indicates the first LBA
+                               // that experienced the error condition, if
+                               // applicable.
+    ULONG      NameSpace;      // Namespace: This field indicates the nsid
+                               // that the error is associated with, if
+                               // applicable.
+    UCHAR      VendorInfoAvailable;    // Vendor Specific Information Available
+    UCHAR      Reserved0[3];
+    ULONGLONG  CommandSpecificInfo;    // This field contains command specific
+                                       // information. If used, the command
+                                       // definition specifies the information
+                                       // returned.
+    UCHAR      Reserved1[24];
+} NVME_ERROR_INFO_LOG, *PNVME_ERROR_INFO_LOG;
+
+typedef struct {
+
+    ULONG   DW0;
+    ULONG   Reserved;
+
+    union {
+        struct {
+            USHORT  SQHD;               // SQ Head Pointer (SQHD)
+            USHORT  SQID;               // SQ Identifier (SQID)
+        } DUMMYSTRUCTNAME;
+
+        ULONG   AsUlong;
+    } DW2;
+
+    union {
+        struct {
+            USHORT              CID;    // Command Identifier (CID)
+            NVME_COMMAND_STATUS Status;
+        } DUMMYSTRUCTNAME;
+
+        ULONG   AsUlong;
+    } DW3;
+
+} NVME_COMPLETION_ENTRY, *PNVME_COMPLETION_ENTRY;
+
+
+// Bit-mask values for STORAGE_PROTOCOL_COMMAND - "Flags" field.
+//
+// Flag indicates the request targeting to adapter instead of device.
+#define STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST    0x80000000
+
+//
+// Status values for STORAGE_PROTOCOL_COMMAND - "ReturnStatus" field.
+//
+#define STORAGE_PROTOCOL_STATUS_PENDING                 0x0
+#define STORAGE_PROTOCOL_STATUS_SUCCESS                 0x1
+#define STORAGE_PROTOCOL_STATUS_ERROR                   0x2
+#define STORAGE_PROTOCOL_STATUS_INVALID_REQUEST         0x3
+#define STORAGE_PROTOCOL_STATUS_NO_DEVICE               0x4
+#define STORAGE_PROTOCOL_STATUS_BUSY                    0x5
+#define STORAGE_PROTOCOL_STATUS_DATA_OVERRUN            0x6
+#define STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES  0x7
+
+#define STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED           0xFF
+
+// Command Length for Storage Protocols.
+//
+// NVMe commands are always 64 bytes.
+#define STORAGE_PROTOCOL_COMMAND_LENGTH_NVME            0x40
+
+// Command Specific Information for Storage Protocols - CommandSpecific field
+//
+#define STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND    0x01
+#define STORAGE_PROTOCOL_SPECIFIC_NVME_NVM_COMMAND 0x02
+
+#endif          /* _DEVIOCTL_ */
+
+
+// NVME_PASS_THROUGH
+
+#ifndef STB_IO_CONTROL
+typedef struct _SRB_IO_CONTROL {
+    ULONG HeaderLength;
+    UCHAR Signature[8];
+    ULONG Timeout;
+    ULONG ControlCode;
+    ULONG ReturnCode;
+    ULONG Length;
+} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
+#endif
+
+#ifndef NVME_PASS_THROUGH_SRB_IO_CODE
+
+#define NVME_SIG_STR "NvmeMini"
+#define NVME_STORPORT_DRIVER 0xe000
+
+#define NVME_PASS_THROUGH_SRB_IO_CODE \
+  CTL_CODE(NVME_STORPORT_DRIVER, 0x0800, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#pragma pack(1)
+
+/* Following is pre-Win10; used with DeviceIoControl(IOCTL_SCSI_MINIPORT),
+ * in Win10 need DeviceIoControl(IOCTL_STORAGE_PROTOCOL_COMMAND) for pure
+ * pass-through. Win10 also has "Protocol specific queries" for things like
+ * Identify and Get feature. */
+typedef struct _NVME_PASS_THROUGH_IOCTL
+{
+    SRB_IO_CONTROL SrbIoCtrl;
+    ULONG VendorSpecific[6];
+    ULONG NVMeCmd[16];      /* Command DW[0...15] */
+    ULONG CplEntry[4];      /* Completion DW[0...3] */
+    ULONG Direction;        /* 0=None, 1=Out, 2=In, 3=I/O */
+    ULONG QueueId;          /* 0=AdminQ */
+    ULONG DataBufferLen;    /* sizeof(DataBuffer) if Data In */
+    ULONG MetaDataLen;
+    ULONG ReturnBufferLen;  /* offsetof(DataBuffer), plus
+                             * sizeof(DataBuffer) if Data Out */
+    UCHAR DataBuffer[1];
+} NVME_PASS_THROUGH_IOCTL;
+#pragma pack()
+
+#endif // NVME_PASS_THROUGH_SRB_IO_CODE
+
+
+/*
+ * method codes
+ */
+#define METHOD_BUFFERED         0
+#define METHOD_IN_DIRECT        1
+#define METHOD_OUT_DIRECT       2
+#define METHOD_NEITHER          3
+
+
+#define IOCTL_SCSI_BASE    0x00000004
+
+/*
+ * constants for DataIn member of SCSI_PASS_THROUGH* structures
+ */
+#define SCSI_IOCTL_DATA_OUT             0
+#define SCSI_IOCTL_DATA_IN              1
+#define SCSI_IOCTL_DATA_UNSPECIFIED     2
+
+#define IOCTL_SCSI_PASS_THROUGH         CTL_CODE(IOCTL_SCSI_BASE, 0x0401, \
+        METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_MINIPORT             CTL_CODE(IOCTL_SCSI_BASE, 0x0402, \
+        METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_GET_INQUIRY_DATA     CTL_CODE(IOCTL_SCSI_BASE, 0x0403, \
+        METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define IOCTL_SCSI_GET_CAPABILITIES     CTL_CODE(IOCTL_SCSI_BASE, 0x0404, \
+        METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define IOCTL_SCSI_PASS_THROUGH_DIRECT  CTL_CODE(IOCTL_SCSI_BASE, 0x0405, \
+        METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_GET_ADDRESS          CTL_CODE(IOCTL_SCSI_BASE, 0x0406, \
+        METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_unaligned.h b/tools/sg_write_buffer/include/sg_unaligned.h
new file mode 100644
index 0000000..3b2c70a
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_unaligned.h
@@ -0,0 +1,325 @@
+#ifndef SG_UNALIGNED_H
+#define SG_UNALIGNED_H
+
+/*
+ * Copyright (c) 2014-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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Borrowed from the Linux kernel, via mhvtl */
+
+/* In the first section below, functions that copy unsigned integers in a
+ * computer's native format, to and from an unaligned big endian sequence of
+ * bytes. Big endian byte format "on the wire" is the default used by SCSI
+ * standards (www.t10.org). Big endian is also the network byte order. */
+
+static inline uint16_t __get_unaligned_be16(const uint8_t *p)
+{
+        return p[0] << 8 | p[1];
+}
+
+static inline uint32_t __get_unaligned_be32(const uint8_t *p)
+{
+        return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline uint64_t __get_unaligned_be48(const uint8_t *p)
+{
+        return (uint64_t)__get_unaligned_be16(p) << 32 |
+               __get_unaligned_be32(p + 2);
+}
+
+static inline uint64_t __get_unaligned_be64(const uint8_t *p)
+{
+        return (uint64_t)__get_unaligned_be32(p) << 32 |
+               __get_unaligned_be32(p + 4);
+}
+
+static inline void __put_unaligned_be16(uint16_t val, uint8_t *p)
+{
+        *p++ = val >> 8;
+        *p++ = val;
+}
+
+static inline void __put_unaligned_be32(uint32_t val, uint8_t *p)
+{
+        __put_unaligned_be16(val >> 16, p);
+        __put_unaligned_be16(val, p + 2);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline void __put_unaligned_be48(uint64_t val, uint8_t *p)
+{
+        __put_unaligned_be16(val >> 32, p);
+        __put_unaligned_be32(val, p + 2);
+}
+
+static inline void __put_unaligned_be64(uint64_t val, uint8_t *p)
+{
+        __put_unaligned_be32(val >> 32, p);
+        __put_unaligned_be32(val, p + 4);
+}
+
+static inline uint16_t sg_get_unaligned_be16(const void *p)
+{
+        return __get_unaligned_be16((const uint8_t *)p);
+}
+
+static inline uint32_t sg_get_unaligned_be24(const void *p)
+{
+        return ((const uint8_t *)p)[0] << 16 | ((const uint8_t *)p)[1] << 8 |
+               ((const uint8_t *)p)[2];
+}
+
+static inline uint32_t sg_get_unaligned_be32(const void *p)
+{
+        return __get_unaligned_be32((const uint8_t *)p);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline uint64_t sg_get_unaligned_be48(const void *p)
+{
+        return __get_unaligned_be48((const uint8_t *)p);
+}
+
+static inline uint64_t sg_get_unaligned_be64(const void *p)
+{
+        return __get_unaligned_be64((const uint8_t *)p);
+}
+
+/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than
+ * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is
+ * an 8 byte unsigned integer. */
+static inline uint64_t sg_get_unaligned_be(int num_bytes, const void *p)
+{
+        if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t)))
+                return 0;
+        else {
+                const uint8_t * xp = (const uint8_t *)p;
+                uint64_t res = *xp;
+
+                for (++xp; num_bytes > 1; ++xp, --num_bytes)
+                        res = (res << 8) | *xp;
+                return res;
+        }
+}
+
+static inline void sg_put_unaligned_be16(uint16_t val, void *p)
+{
+        __put_unaligned_be16(val, (uint8_t *)p);
+}
+
+static inline void sg_put_unaligned_be24(uint32_t val, void *p)
+{
+        ((uint8_t *)p)[0] = (val >> 16) & 0xff;
+        ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+        ((uint8_t *)p)[2] = val & 0xff;
+}
+
+static inline void sg_put_unaligned_be32(uint32_t val, void *p)
+{
+        __put_unaligned_be32(val, (uint8_t *)p);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline void sg_put_unaligned_be48(uint64_t val, void *p)
+{
+        __put_unaligned_be48(val, (uint8_t *)p);
+}
+
+static inline void sg_put_unaligned_be64(uint64_t val, void *p)
+{
+        __put_unaligned_be64(val, (uint8_t *)p);
+}
+
+/* Since cdb and parameter blocks are often memset to zero before these
+ * unaligned function partially fill them, then check for a val of zero
+ * and ignore if it is with these variants. */
+static inline void sg_nz_put_unaligned_be16(uint16_t val, void *p)
+{
+        if (val)
+                __put_unaligned_be16(val, (uint8_t *)p);
+}
+
+static inline void sg_nz_put_unaligned_be24(uint32_t val, void *p)
+{
+        if (val) {
+                ((uint8_t *)p)[0] = (val >> 16) & 0xff;
+                ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+                ((uint8_t *)p)[2] = val & 0xff;
+        }
+}
+
+static inline void sg_nz_put_unaligned_be32(uint32_t val, void *p)
+{
+        if (val)
+                __put_unaligned_be32(val, (uint8_t *)p);
+}
+
+static inline void sg_nz_put_unaligned_be64(uint64_t val, void *p)
+{
+        if (val)
+            __put_unaligned_be64(val, (uint8_t *)p);
+}
+
+
+/* Below are the little endian equivalents of the big endian functions
+ * above. Little endian is used by ATA, PCI and NVMe.
+ */
+
+static inline uint16_t __get_unaligned_le16(const uint8_t *p)
+{
+        return p[1] << 8 | p[0];
+}
+
+static inline uint32_t __get_unaligned_le32(const uint8_t *p)
+{
+        return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0];
+}
+
+static inline uint64_t __get_unaligned_le64(const uint8_t *p)
+{
+        return (uint64_t)__get_unaligned_le32(p + 4) << 32 |
+               __get_unaligned_le32(p);
+}
+
+static inline void __put_unaligned_le16(uint16_t val, uint8_t *p)
+{
+        *p++ = val;
+        *p++ = val >> 8;
+}
+
+static inline void __put_unaligned_le32(uint32_t val, uint8_t *p)
+{
+        __put_unaligned_le16(val >> 16, p + 2);
+        __put_unaligned_le16(val, p);
+}
+
+static inline void __put_unaligned_le64(uint64_t val, uint8_t *p)
+{
+        __put_unaligned_le32(val >> 32, p + 4);
+        __put_unaligned_le32(val, p);
+}
+
+static inline uint16_t sg_get_unaligned_le16(const void *p)
+{
+        return __get_unaligned_le16((const uint8_t *)p);
+}
+
+static inline uint32_t sg_get_unaligned_le24(const void *p)
+{
+        return (uint32_t)__get_unaligned_le16((const uint8_t *)p) |
+               ((const uint8_t *)p)[2] << 16;
+}
+
+static inline uint32_t sg_get_unaligned_le32(const void *p)
+{
+        return __get_unaligned_le32((const uint8_t *)p);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline uint64_t sg_get_unaligned_le48(const void *p)
+{
+        return (uint64_t)__get_unaligned_le16((const uint8_t *)p + 4) << 32 |
+               __get_unaligned_le32((const uint8_t *)p);
+}
+
+static inline uint64_t sg_get_unaligned_le64(const void *p)
+{
+        return __get_unaligned_le64((const uint8_t *)p);
+}
+
+/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than
+ * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is
+ * an 8 byte unsigned integer. */
+static inline uint64_t sg_get_unaligned_le(int num_bytes, const void *p)
+{
+        if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t)))
+                return 0;
+        else {
+                const uint8_t * xp = (const uint8_t *)p + (num_bytes - 1);
+                uint64_t res = *xp;
+
+                for (--xp; num_bytes > 1; --xp, --num_bytes)
+                        res = (res << 8) | *xp;
+                return res;
+        }
+}
+
+static inline void sg_put_unaligned_le16(uint16_t val, void *p)
+{
+        __put_unaligned_le16(val, (uint8_t *)p);
+}
+
+static inline void sg_put_unaligned_le24(uint32_t val, void *p)
+{
+        ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+        ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+        ((uint8_t *)p)[0] = val & 0xff;
+}
+
+static inline void sg_put_unaligned_le32(uint32_t val, void *p)
+{
+        __put_unaligned_le32(val, (uint8_t *)p);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline void sg_put_unaligned_le48(uint64_t val, void *p)
+{
+        ((uint8_t *)p)[5] = (val >> 40) & 0xff;
+        ((uint8_t *)p)[4] = (val >> 32) & 0xff;
+        ((uint8_t *)p)[3] = (val >> 24) & 0xff;
+        ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+        ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+        ((uint8_t *)p)[0] = val & 0xff;
+}
+
+static inline void sg_put_unaligned_le64(uint64_t val, void *p)
+{
+        __put_unaligned_le64(val, (uint8_t *)p);
+}
+
+/* Since cdb and parameter blocks are often memset to zero before these
+ * unaligned function partially fill them, then check for a val of zero
+ * and ignore if it is with these variants. */
+static inline void sg_nz_put_unaligned_le16(uint16_t val, void *p)
+{
+        if (val)
+                __put_unaligned_le16(val, (uint8_t *)p);
+}
+
+static inline void sg_nz_put_unaligned_le24(uint32_t val, void *p)
+{
+        if (val) {
+                ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+                ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+                ((uint8_t *)p)[0] = val & 0xff;
+        }
+}
+
+static inline void sg_nz_put_unaligned_le32(uint32_t val, void *p)
+{
+        if (val)
+                __put_unaligned_le32(val, (uint8_t *)p);
+}
+
+static inline void sg_nz_put_unaligned_le64(uint64_t val, void *p)
+{
+        if (val)
+            __put_unaligned_le64(val, (uint8_t *)p);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SG_UNALIGNED_H */
diff --git a/tools/sg_write_buffer/sg_cmds_basic.c b/tools/sg_write_buffer/sg_cmds_basic.c
new file mode 100644
index 0000000..35a4991
--- /dev/null
+++ b/tools/sg_write_buffer/sg_cmds_basic.c
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) 1999-2018 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.
+ */
+
+/*
+ * CONTENTS
+ *    Some SCSI commands are executed in many contexts and hence began
+ *    to appear in several sg3_utils utilities. This files centralizes
+ *    some of the low level command execution code. In most cases the
+ *    interpretation of the command response is left to the each
+ *    utility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+/* Needs to be after config.h */
+#ifdef SG_LIB_LINUX
+#include <errno.h>
+#endif
+
+
+static const char * const version_str = "1.83 20180204";
+
+
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+#define EBUFF_SZ 256
+
+#define DEF_PT_TIMEOUT 60       /* 60 seconds */
+#define START_PT_TIMEOUT 120    /* 120 seconds == 2 minutes */
+#define LONG_PT_TIMEOUT 7200    /* 7,200 seconds == 120 minutes */
+
+#define INQUIRY_CMD     0x12
+#define INQUIRY_CMDLEN  6
+#define REQUEST_SENSE_CMD 0x3
+#define REQUEST_SENSE_CMDLEN 6
+#define REPORT_LUNS_CMD 0xa0
+#define REPORT_LUNS_CMDLEN 12
+#define TUR_CMD  0x0
+#define TUR_CMDLEN  6
+
+#define SAFE_STD_INQ_RESP_LEN 36 /* other lengths lock up some devices */
+
+
+const char *
+sg_cmds_version()
+{
+    return version_str;
+}
+
+#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;
+}
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+   negated errno. */
+int
+sg_cmds_open_device(const char * device_name, bool read_only, int verbose)
+{
+    /* 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;
+
+    return scsi_pt_open_device(device_name, read_only, verbose);
+}
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+   negated errno. */
+int
+sg_cmds_open_flags(const char * device_name, int flags, int verbose)
+{
+    return scsi_pt_open_flags(device_name, flags, verbose);
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+sg_cmds_close_device(int device_fd)
+{
+    return scsi_pt_close_device(device_fd);
+}
+
+static const char * const pass_through_s = "pass-through";
+
+static int
+sg_cmds_process_helper(const char * leadin, int mx_di_len, int resid,
+                       const unsigned char * sbp, int slen, bool noisy,
+                       int verbose, int * o_sense_cat)
+{
+    int scat, got;
+    bool n = false;
+    bool check_data_in = false;
+    char b[512];
+
+    scat = sg_err_category_sense(sbp, slen);
+    switch (scat) {
+    case SG_LIB_CAT_NOT_READY:
+    case SG_LIB_CAT_INVALID_OP:
+    case SG_LIB_CAT_ILLEGAL_REQ:
+    case SG_LIB_CAT_ABORTED_COMMAND:
+    case SG_LIB_CAT_COPY_ABORTED:
+    case SG_LIB_CAT_DATA_PROTECT:
+    case SG_LIB_CAT_PROTECTION:
+    case SG_LIB_CAT_NO_SENSE:
+    case SG_LIB_CAT_MISCOMPARE:
+        n = false;
+        break;
+    case SG_LIB_CAT_RECOVERED:
+    case SG_LIB_CAT_MEDIUM_HARD:
+        check_data_in = true;
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+        __attribute__((fallthrough));
+        /* FALL THROUGH */
+#endif
+#endif
+    case SG_LIB_CAT_UNIT_ATTENTION:
+    case SG_LIB_CAT_SENSE:
+    default:
+        n = noisy;
+        break;
+    }
+    if (verbose || n) {
+        if (leadin && (strlen(leadin) > 0))
+            pr2ws("%s:\n", leadin);
+        sg_get_sense_str(NULL, sbp, slen, (verbose > 1),
+                         sizeof(b), b);
+        pr2ws("%s", b);
+        if ((mx_di_len > 0) && (resid > 0)) {
+            got = mx_di_len - resid;
+            if ((verbose > 2) || check_data_in || (got > 0))
+                pr2ws("    %s requested %d bytes (data-in) but got %d "
+                      "bytes\n", pass_through_s, mx_di_len, got);
+        }
+    }
+    if (o_sense_cat)
+        *o_sense_cat = scat;
+    return -2;
+}
+
+/* This is a helper function used by sg_cmds_* implementations after the
+ * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid
+ * sense data is found it is decoded and output to sg_warnings_strm (def:
+ * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for
+ * "sense" category (may not be fatal), -1 for failed, 0, or a positive
+ * number. If 'mx_di_len > 0' then asks pass-through for resid and returns
+ * (mx_di_len - resid); otherwise returns 0. So for data-in it should return
+ * the actual number of bytes received. For data-out (to device) or no data
+ * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category
+ * output via 'o_sense_cat' pointer (if not NULL). Note that several sense
+ * categories also have data in bytes received; -2 is still returned. */
+int
+sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin,
+                     int pt_res, int mx_di_len, const unsigned char * sbp,
+                     bool noisy, int verbose, int * o_sense_cat)
+{
+    int got, cat, duration, slen, resid, resp_code, sstat;
+    bool transport_sense;
+    char b[1024];
+
+    if (NULL == leadin)
+        leadin = "";
+    if (pt_res < 0) {
+#ifdef SG_LIB_LINUX
+        if (verbose)
+            pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+                  safe_strerror(-pt_res));
+        if ((-ENXIO == pt_res) && o_sense_cat) {
+            if (verbose > 2)
+                pr2ws("map ENXIO to SG_LIB_CAT_NOT_READY\n");
+            *o_sense_cat = SG_LIB_CAT_NOT_READY;
+            return -2;
+        } else if (noisy && (0 == verbose))
+            pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+                  safe_strerror(-pt_res));
+#else
+        if (noisy || verbose)
+            pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+                  safe_strerror(-pt_res));
+#endif
+        return -1;
+    } else if (SCSI_PT_DO_BAD_PARAMS == pt_res) {
+        pr2ws("%s: bad %s setup\n", leadin, pass_through_s);
+        return -1;
+    } else if (SCSI_PT_DO_TIMEOUT == pt_res) {
+        pr2ws("%s: %s timeout\n", leadin, pass_through_s);
+        return -1;
+    }
+    if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0))
+        pr2ws("      duration=%d ms\n", duration);
+    resid = (mx_di_len > 0) ? get_scsi_pt_resid(ptvp) : 0;
+    slen = get_scsi_pt_sense_len(ptvp);
+    switch ((cat = get_scsi_pt_result_category(ptvp))) {
+    case SCSI_PT_RESULT_GOOD:
+        if (sbp && (slen > 7)) {
+            resp_code = sbp[0] & 0x7f;
+            /* SBC referrals can have status=GOOD and sense_key=COMPLETED */
+            if (resp_code >= 0x70) {
+                if (resp_code < 0x72) {
+                    if (SPC_SK_NO_SENSE != (0xf & sbp[2]))
+                        sg_err_category_sense(sbp, slen);
+                } else if (resp_code < 0x74) {
+                    if (SPC_SK_NO_SENSE != (0xf & sbp[1]))
+                        sg_err_category_sense(sbp, slen);
+                }
+            }
+        }
+        if (mx_di_len > 0) {
+            got = mx_di_len - resid;
+            if ((verbose > 1) && (resid != 0))
+                pr2ws("    %s: %s requested %d bytes (data-in) but got %d "
+                      "bytes\n", leadin, pass_through_s, mx_di_len, got);
+            if (got >= 0)
+                return got;
+            else {
+                if (verbose)
+                    pr2ws("    %s: %s can't get negative bytes, say it got "
+                          "none\n", leadin, pass_through_s);
+                return 0;
+            }
+        } else
+            return 0;
+    case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+        sstat = get_scsi_pt_status_response(ptvp);
+        if (o_sense_cat) {
+            switch (sstat) {
+            case SAM_STAT_RESERVATION_CONFLICT:
+                *o_sense_cat = SG_LIB_CAT_RES_CONFLICT;
+                return -2;
+            case SAM_STAT_CONDITION_MET:
+                *o_sense_cat = SG_LIB_CAT_CONDITION_MET;
+                return -2;
+            case SAM_STAT_BUSY:
+                *o_sense_cat = SG_LIB_CAT_BUSY;
+                return -2;
+            case SAM_STAT_TASK_SET_FULL:
+                *o_sense_cat = SG_LIB_CAT_TS_FULL;
+                return -2;
+            case SAM_STAT_ACA_ACTIVE:
+                *o_sense_cat = SG_LIB_CAT_ACA_ACTIVE;
+                return -2;
+            case SAM_STAT_TASK_ABORTED:
+                *o_sense_cat = SG_LIB_CAT_TASK_ABORTED;
+                return -2;
+            default:
+                break;
+            }
+        }
+        if (verbose || noisy) {
+            sg_get_scsi_status_str(sstat, sizeof(b), b);
+            pr2ws("%s: scsi status: %s\n", leadin, b);
+        }
+        return -1;
+    case SCSI_PT_RESULT_SENSE:
+        return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp, slen,
+                                      noisy, verbose, o_sense_cat);
+    case SCSI_PT_RESULT_TRANSPORT_ERR:
+        if (verbose || noisy) {
+            get_scsi_pt_transport_err_str(ptvp, sizeof(b), b);
+            pr2ws("%s: transport: %s\n", leadin, b);
+        }
+#ifdef SG_LIB_LINUX
+        transport_sense = (slen > 0);
+#else
+        transport_sense = ((SAM_STAT_CHECK_CONDITION ==
+                            get_scsi_pt_status_response(ptvp)) && (slen > 0));
+#endif
+        if (transport_sense)
+            return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp,
+                                          slen, noisy, verbose, o_sense_cat);
+        else
+            return -1;
+    case SCSI_PT_RESULT_OS_ERR:
+        if (verbose || noisy) {
+            get_scsi_pt_os_err_str(ptvp, sizeof(b), b);
+            pr2ws("%s: os: %s\n", leadin, b);
+        }
+        return -1;
+    default:
+        pr2ws("%s: unknown %s result category (%d)\n", leadin, pass_through_s,
+               cat);
+        return -1;
+    }
+}
+
+bool
+sg_cmds_is_nvme(const struct sg_pt_base * ptvp)
+{
+    return pt_device_is_nvme(ptvp);
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+    struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp)
+        pr2ws("%s: out of memory\n", cname);
+    return ptvp;
+}
+
+static const char * const inquiry_s = "inquiry";
+
+static int
+sg_ll_inquiry_com(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+                  int mx_resp_len, int timeout_secs, int * residp,
+                  bool noisy, int verbose)
+{
+    int res, ret, k, sense_cat, resid;
+    unsigned char inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    unsigned char * up;
+    struct sg_pt_base * ptvp;
+
+    if (cmddt)
+        inq_cdb[1] |= 0x2;
+    if (evpd)
+        inq_cdb[1] |= 0x1;
+    inq_cdb[2] = (unsigned char)pg_op;
+    /* 16 bit allocation length (was 8, increased in spc3r09, 200209) */
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3);
+    if (verbose) {
+        pr2ws("    %s cdb: ", inquiry_s);
+        for (k = 0; k < INQUIRY_CMDLEN; ++k)
+            pr2ws("%02x ", inq_cdb[k]);
+        pr2ws("\n");
+    }
+    if (resp && (mx_resp_len > 0)) {
+        up = (unsigned char *)resp;
+        up[0] = 0x7f;   /* defensive prefill */
+        if (mx_resp_len > 4)
+            up[4] = 0;
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+    ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp) {
+        pr2ws("%s: out of memory\n", __func__);
+        if (residp)
+            *residp = 0;
+        return -1;
+    }
+    set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, inquiry_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (residp)
+        *residp = resid;
+    if (-1 == ret)
+        ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+    else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else if (ret < 4) {
+        if (verbose)
+            pr2ws("%s: got too few bytes (%d)\n", __func__, ret);
+        ret = SG_LIB_CAT_MALFORMED;
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+
+    if (resid > 0) {
+        if (resid > mx_resp_len) {
+            pr2ws("%s resid (%d) should never exceed requested "
+                    "len=%d\n", inquiry_s, resid, mx_resp_len);
+            return ret ? ret : SG_LIB_CAT_MALFORMED;
+        }
+        /* zero unfilled section of response buffer, based on resid */
+        memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
+    }
+    return ret;
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb. */
+int
+sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+              int mx_resp_len, bool noisy, int verbose)
+{
+    return sg_ll_inquiry_com(sg_fd, cmddt, evpd, pg_op, resp, mx_resp_len,
+                             0 /* timeout_sec */, NULL, noisy, verbose);
+}
+
+/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data,
+                  bool noisy, int verbose)
+{
+    int ret;
+    unsigned char inq_resp[SAFE_STD_INQ_RESP_LEN];
+
+    if (inq_data) {
+        memset(inq_data, 0, sizeof(* inq_data));
+        inq_data->peripheral_qualifier = 0x3;
+        inq_data->peripheral_type = 0x1f;
+    }
+    ret = sg_ll_inquiry_com(sg_fd, false, false, 0, inq_resp,
+                            sizeof(inq_resp), 0, NULL, noisy, verbose);
+
+    if (inq_data && (0 == ret)) {
+        inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7;
+        inq_data->peripheral_type = inq_resp[0] & 0x1f;
+        inq_data->byte_1 = inq_resp[1];
+        inq_data->version = inq_resp[2];
+        inq_data->byte_3 = inq_resp[3];
+        inq_data->byte_5 = inq_resp[5];
+        inq_data->byte_6 = inq_resp[6];
+        inq_data->byte_7 = inq_resp[7];
+        memcpy(inq_data->vendor, inq_resp + 8, 8);
+        memcpy(inq_data->product, inq_resp + 16, 16);
+        memcpy(inq_data->revision, inq_resp + 32, 4);
+    }
+    return ret;
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so
+ * 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 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
+ * bytes have been written. */
+int
+sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp,
+                 int mx_resp_len, int timeout_secs, int * residp,
+                 bool noisy, int verbose)
+{
+    return sg_ll_inquiry_com(sg_fd, false, evpd, pg_op, resp, mx_resp_len,
+                             timeout_secs, residp, noisy, verbose);
+}
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Looks for progress indicator if 'progress' non-NULL;
+ * if found writes value [0..65535] else write -1.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress,
+                               bool noisy, int verbose)
+{
+    static const char * const tur_s = "test unit ready";
+    int res, ret, k, sense_cat;
+    unsigned char tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", tur_s);
+        for (k = 0; k < TUR_CMDLEN; ++k)
+            pr2ws("%02x ", tur_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(tur_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_packet_id(ptvp, pack_id);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, tur_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        if (progress) {
+            int slen = get_scsi_pt_sense_len(ptvp);
+
+            if (! sg_get_sense_progress_fld(sense_b, slen, progress))
+                *progress = -1;
+        }
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose)
+{
+    return sg_ll_test_unit_ready_progress(sg_fd, pack_id, NULL, noisy,
+                                          verbose);
+}
+
+/* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various
+ * SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len,
+                    bool noisy, int verbose)
+{
+    static const char * const rq_s = "request sense";
+    int k, ret, res, sense_cat;
+    unsigned char rs_cdb[REQUEST_SENSE_CMDLEN] =
+        {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (desc)
+        rs_cdb[1] |= 0x1;
+    if (mx_resp_len > 0xff) {
+        pr2ws("mx_resp_len cannot exceed 255\n");
+        return -1;
+    }
+    rs_cdb[4] = mx_resp_len & 0xff;
+    if (verbose) {
+        pr2ws("    %s cmd: ", rq_s);
+        for (k = 0; k < REQUEST_SENSE_CMDLEN; ++k)
+            pr2ws("%02x ", rs_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(rq_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, rq_s, res, mx_resp_len, sense_b, noisy,
+                               verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((mx_resp_len >= 8) && (ret < 8)) {
+            if (verbose)
+                pr2ws("    %s: got %d bytes in response, too short\n", rq_s,
+                      ret);
+            ret = -1;
+        } else
+            ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len,
+                  bool noisy, int verbose)
+{
+    static const char * const report_luns_s = "report luns";
+    int k, ret, res, sense_cat;
+    unsigned char rl_cdb[REPORT_LUNS_CMDLEN] =
+                         {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    rl_cdb[2] = select_report & 0xff;
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", report_luns_s);
+        for (k = 0; k < REPORT_LUNS_CMDLEN; ++k)
+            pr2ws("%02x ", rl_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(report_luns_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, report_luns_s, res, mx_resp_len,
+                               sense_b, noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
diff --git a/tools/sg_write_buffer/sg_cmds_basic2.c b/tools/sg_write_buffer/sg_cmds_basic2.c
new file mode 100644
index 0000000..18b6cd7
--- /dev/null
+++ b/tools/sg_write_buffer/sg_cmds_basic2.c
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (c) 1999-2018 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.
+ */
+
+/*
+ * CONTENTS
+ *    Some SCSI commands are executed in many contexts and hence began
+ *    to appear in several sg3_utils utilities. This files centralizes
+ *    some of the low level command execution code. In most cases the
+ *    interpretation of the command response is left to the each
+ *    utility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+
+
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+#define EBUFF_SZ 256
+
+#define DEF_PT_TIMEOUT 60       /* 60 seconds */
+#define START_PT_TIMEOUT 120    /* 120 seconds == 2 minutes */
+#define LONG_PT_TIMEOUT 7200    /* 7,200 seconds == 120 minutes */
+
+#define SYNCHRONIZE_CACHE_CMD     0x35
+#define SYNCHRONIZE_CACHE_CMDLEN  10
+#define SERVICE_ACTION_IN_16_CMD 0x9e
+#define SERVICE_ACTION_IN_16_CMDLEN 16
+#define READ_CAPACITY_16_SA 0x10
+#define READ_CAPACITY_10_CMD 0x25
+#define READ_CAPACITY_10_CMDLEN 10
+#define MODE_SENSE6_CMD      0x1a
+#define MODE_SENSE6_CMDLEN   6
+#define MODE_SENSE10_CMD     0x5a
+#define MODE_SENSE10_CMDLEN  10
+#define MODE_SELECT6_CMD   0x15
+#define MODE_SELECT6_CMDLEN   6
+#define MODE_SELECT10_CMD   0x55
+#define MODE_SELECT10_CMDLEN  10
+#define LOG_SENSE_CMD     0x4d
+#define LOG_SENSE_CMDLEN  10
+#define LOG_SELECT_CMD     0x4c
+#define LOG_SELECT_CMDLEN  10
+#define START_STOP_CMD          0x1b
+#define START_STOP_CMDLEN       6
+#define PREVENT_ALLOW_CMD    0x1e
+#define PREVENT_ALLOW_CMDLEN   6
+
+#define MODE6_RESP_HDR_LEN 4
+#define MODE10_RESP_HDR_LEN 8
+#define MODE_RESP_ARB_LEN 1024
+
+#define INQUIRY_RESP_INITIAL_LEN 36
+
+
+#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;
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+    struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp)
+        pr2ws("%s: out of memory\n", cname);
+    return ptvp;
+}
+
+/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group,
+                    unsigned int lba, unsigned int count, bool noisy,
+                    int verbose)
+{
+    static const char * const cdb_name_s = "synchronize cache(10)";
+    int res, ret, k, sense_cat;
+    unsigned char sc_cdb[SYNCHRONIZE_CACHE_CMDLEN] =
+                {SYNCHRONIZE_CACHE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (sync_nv)
+        sc_cdb[1] |= 4;
+    if (immed)
+        sc_cdb[1] |= 2;
+    sg_put_unaligned_be32((uint32_t)lba, sc_cdb + 2);
+    sc_cdb[6] = group & 0x1f;
+    if (count > 0xffff) {
+        pr2ws("count too big\n");
+        return -1;
+    }
+    sg_put_unaligned_be16((int16_t)count, sc_cdb + 7);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SYNCHRONIZE_CACHE_CMDLEN; ++k)
+            pr2ws("%02x ", sc_cdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, sc_cdb, sizeof(sc_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp,
+                 int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "read capacity(16)";
+    int k, ret, res, sense_cat;
+    unsigned char rc_cdb[SERVICE_ACTION_IN_16_CMDLEN] =
+                        {SERVICE_ACTION_IN_16_CMD, READ_CAPACITY_16_SA,
+                         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (pmi) { /* lbs only valid when pmi set */
+        rc_cdb[14] |= 1;
+        sg_put_unaligned_be64(llba, rc_cdb + 2);
+    }
+    /* Allocation length, no guidance in SBC-2 rev 15b */
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rc_cdb + 10);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
+            pr2ws("%02x ", rc_cdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ CAPACITY (10) command. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp,
+                 int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "read capacity(10)";
+    int k, ret, res, sense_cat;
+    unsigned char rc_cdb[READ_CAPACITY_10_CMDLEN] =
+                         {READ_CAPACITY_10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (pmi) { /* lbs only valid when pmi set */
+        rc_cdb[8] |= 1;
+        sg_put_unaligned_be32((uint32_t)lba, rc_cdb + 2);
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_CAPACITY_10_CMDLEN; ++k)
+            pr2ws("%02x ", rc_cdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, int sub_pg_code,
+                  void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "mode sense(6)";
+    int res, ret, k, sense_cat, resid;
+    unsigned char modes_cdb[MODE_SENSE6_CMDLEN] =
+        {MODE_SENSE6_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    modes_cdb[1] = (unsigned char)(dbd ? 0x8 : 0);
+    modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+    modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff);
+    modes_cdb[4] = (unsigned char)(mx_resp_len & 0xff);
+    if (mx_resp_len > 0xff) {
+        pr2ws("mx_resp_len too big\n");
+        return -1;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MODE_SENSE6_CMDLEN; ++k)
+            pr2ws("%02x ", modes_cdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+
+    if (resid > 0) {
+        if (resid > mx_resp_len) {
+            pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+                  cdb_name_s, resid, mx_resp_len);
+            return ret ? ret : SG_LIB_CAT_MALFORMED;
+        }
+        /* zero unfilled section of response buffer */
+        memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
+    }
+    return ret;
+}
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+                   int sub_pg_code, void * resp, int mx_resp_len,
+                   bool noisy, int verbose)
+{
+    return sg_ll_mode_sense10_v2(sg_fd, llbaa, dbd, pc, pg_code, sub_pg_code,
+                                 resp, mx_resp_len, 0, NULL, noisy, verbose);
+}
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * 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 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
+ * bytes have been written. */
+int
+sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+                      int sub_pg_code, void * resp, int mx_resp_len,
+                      int timeout_secs, int * residp, bool noisy, int verbose)
+{
+    int res, ret, k, sense_cat, resid;
+    static const char * const cdb_name_s = "mode sense(10)";
+    struct sg_pt_base * ptvp;
+    unsigned char modes_cdb[MODE_SENSE10_CMDLEN] =
+        {MODE_SENSE10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+
+    modes_cdb[1] = (unsigned char)((dbd ? 0x8 : 0) | (llbaa ? 0x10 : 0));
+    modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+    modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff);
+    sg_put_unaligned_be16((int16_t)mx_resp_len, modes_cdb + 7);
+    if (mx_resp_len > 0xffff) {
+        pr2ws("mx_resp_len too big\n");
+        goto gen_err;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MODE_SENSE10_CMDLEN; ++k)
+            pr2ws("%02x ", modes_cdb[k]);
+        pr2ws("\n");
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        goto gen_err;
+    set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (residp)
+        *residp = resid;
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+
+    if (resid > 0) {
+        if (resid > mx_resp_len) {
+            pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+                  cdb_name_s, resid, mx_resp_len);
+            return ret ? ret : SG_LIB_CAT_MALFORMED;
+        }
+        /* zero unfilled section of response buffer */
+        memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
+    }
+    return ret;
+gen_err:
+    if (residp)
+        *residp = 0;
+    return -1;
+}
+
+/* Invokes a SCSI MODE SELECT (6) command.  Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, int param_len,
+                   bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "mode select(6)";
+    int res, ret, k, sense_cat;
+    unsigned char modes_cdb[MODE_SELECT6_CMDLEN] =
+        {MODE_SELECT6_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0));
+    modes_cdb[4] = (unsigned char)(param_len & 0xff);
+    if (param_len > 0xff) {
+        pr2ws("%s: param_len too big\n", cdb_name_s);
+        return -1;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MODE_SELECT6_CMDLEN; ++k)
+            pr2ws("%02x ", modes_cdb[k]);
+        pr2ws("\n");
+    }
+    if (verbose > 1) {
+        pr2ws("    %s parameter list\n", cdb_name_s);
+        hex2stderr((const uint8_t *)paramp, param_len, -1);
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI MODE SELECT (10) command.  Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, int param_len,
+                    bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "mode select(10)";
+    int res, ret, k, sense_cat;
+    unsigned char modes_cdb[MODE_SELECT10_CMDLEN] =
+        {MODE_SELECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0));
+    sg_put_unaligned_be16((int16_t)param_len, modes_cdb + 7);
+    if (param_len > 0xffff) {
+        pr2ws("%s: param_len too big\n", cdb_name_s);
+        return -1;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MODE_SELECT10_CMDLEN; ++k)
+            pr2ws("%02x ", modes_cdb[k]);
+        pr2ws("\n");
+    }
+    if (verbose > 1) {
+        pr2ws("    %s parameter list\n", cdb_name_s);
+        hex2stderr((const uint8_t *)paramp, param_len, -1);
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. In most cases users are
+ * interested in the first mode page. This function returns the (byte)
+ * offset of the start of the first mode page. Set mode_sense_6 to true for
+ * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful
+ * or -1 if failure. If there is a failure a message is written to err_buff
+ * if it is non-NULL and err_buff_len > 0. */
+int
+sg_mode_page_offset(const unsigned char * resp, int resp_len,
+                    bool mode_sense_6, char * err_buff, int err_buff_len)
+{
+    int bd_len, calc_len, offset;
+    bool err_buff_ok = ((err_buff_len > 0) && err_buff);
+
+    if ((NULL == resp) || (resp_len < 4))
+            goto too_short;
+    if (mode_sense_6) {
+        calc_len = resp[0] + 1;
+        bd_len = resp[3];
+        offset = bd_len + MODE6_RESP_HDR_LEN;
+    } else {    /* Mode sense(10) */
+        if (resp_len < 8)
+            goto too_short;
+        calc_len = sg_get_unaligned_be16(resp) + 2;
+        bd_len = sg_get_unaligned_be16(resp + 6);
+        /* LongLBA doesn't change this calculation */
+        offset = bd_len + MODE10_RESP_HDR_LEN;
+    }
+    if ((offset + 2) > calc_len) {
+        if (err_buff_ok)
+            snprintf(err_buff, err_buff_len, "calculated response "
+                     "length too small, offset=%d calc_len=%d bd_len=%d\n",
+                     offset, calc_len, bd_len);
+        offset = -1;
+    }
+    return offset;
+too_short:
+    if (err_buff_ok)
+        snprintf(err_buff, err_buff_len, "given MS(%d) response length (%d) "
+                 "too short\n", (mode_sense_6 ? 6 : 10), resp_len);
+    return -1;
+}
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. This functions returns the
+ * length (in bytes) of those three components. Note that the return value
+ * can exceed resp_len in which case the MODE SENSE command should be
+ * re-issued with a larger response buffer. If bd_lenp is non-NULL and if
+ * successful the block descriptor length (in bytes) is written to *bd_lenp.
+ * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10)
+ * responses. Returns -1 if there is an error (e.g. response too short). */
+int
+sg_msense_calc_length(const unsigned char * resp, int resp_len,
+                      bool mode_sense_6, int * bd_lenp)
+{
+    int calc_len;
+
+    if (NULL == resp)
+        goto an_err;
+    if (mode_sense_6) {
+        if (resp_len < 4)
+            goto an_err;
+        calc_len = resp[0] + 1;
+    } else {
+        if (resp_len < 8)
+            goto an_err;
+        calc_len = sg_get_unaligned_be16(resp + 0) + 2;
+    }
+    if (bd_lenp)
+        *bd_lenp = mode_sense_6 ? resp[3] : sg_get_unaligned_be16(resp + 6);
+    return calc_len;
+an_err:
+    if (bd_lenp)
+        *bd_lenp = 0;
+    return -1;
+}
+
+/* Fetches current, changeable, default and/or saveable modes pages as
+ * indicated by pcontrol_arr for given pg_code and sub_pg_code. If
+ * mode6==false then use MODE SENSE (10) else use MODE SENSE (6). If
+ * flexible set and mode data length seems wrong then try and
+ * fix (compensating hack for bad device or driver). pcontrol_arr
+ * should have 4 elements for output of current, changeable, default
+ * and saved values respectively. Each element should be NULL or
+ * at least mx_mpage_len bytes long.
+ * Return of 0 -> overall success, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors.
+ * If success_mask pointer is not NULL then first zeros it. Then set bits
+ * 0, 1, 2 and/or 3 if the current, changeable, default and saved values
+ * respectively have been fetched. If error on current page
+ * then stops and returns that error; otherwise continues if an error is
+ * detected but returns the first error encountered.  */
+int
+sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, int sub_pg_code,
+                          bool dbd, bool flexible, int mx_mpage_len,
+                          int * success_mask, void * pcontrol_arr[],
+                          int * reported_lenp, int verbose)
+{
+    bool resp_mode6;
+    int k, n, res, offset, calc_len, xfer_len;
+    int resid = 0;
+    const int msense10_hlen = MODE10_RESP_HDR_LEN;
+    unsigned char buff[MODE_RESP_ARB_LEN];
+    char ebuff[EBUFF_SZ];
+    int first_err = 0;
+
+    if (success_mask)
+        *success_mask = 0;
+    if (reported_lenp)
+        *reported_lenp = 0;
+    if (mx_mpage_len < 4)
+        return 0;
+    memset(ebuff, 0, sizeof(ebuff));
+    /* first try to find length of current page response */
+    memset(buff, 0, msense10_hlen);
+    if (mode6)  /* want first 8 bytes just in case */
+        res = sg_ll_mode_sense6(sg_fd, dbd, 0 /* pc */, pg_code,
+                                sub_pg_code, buff, msense10_hlen, true,
+                                verbose);
+    else        /* MODE SENSE(10) obviously */
+        res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd,
+                                    0 /* pc */, pg_code, sub_pg_code, buff,
+                                    msense10_hlen, 0, &resid, true, verbose);
+    if (0 != res)
+        return res;
+    n = buff[0];
+    if (reported_lenp) {
+        int m;
+
+        m = sg_msense_calc_length(buff, msense10_hlen, mode6, NULL) - resid;
+        if (m < 0)      /* Grrr, this should not happen */
+            m = 0;
+        *reported_lenp = m;
+    }
+    resp_mode6 = mode6;
+    if (flexible) {
+        if (mode6 && (n < 3)) {
+            resp_mode6 = false;
+            if (verbose)
+                pr2ws(">>> msense(6) but resp[0]=%d so try msense(10) "
+                      "response processing\n", n);
+        }
+        if ((! mode6) && (n > 5)) {
+            if ((n > 11) && (0 == (n % 2)) && (0 == buff[4]) &&
+                (0 == buff[5]) && (0 == buff[6])) {
+                buff[1] = n;
+                buff[0] = 0;
+                if (verbose)
+                    pr2ws(">>> msense(10) but resp[0]=%d and not msense(6) "
+                          "response so fix length\n", n);
+            } else
+                resp_mode6 = true;
+        }
+    }
+    if (verbose && (resp_mode6 != mode6))
+        pr2ws(">>> msense(%d) but resp[0]=%d so switch response "
+              "processing\n", (mode6 ? 6 : 10), buff[0]);
+    calc_len = sg_msense_calc_length(buff, msense10_hlen, resp_mode6, NULL);
+    if (calc_len > MODE_RESP_ARB_LEN)
+        calc_len = MODE_RESP_ARB_LEN;
+    offset = sg_mode_page_offset(buff, calc_len, resp_mode6, ebuff, EBUFF_SZ);
+    if (offset < 0) {
+        if (('\0' != ebuff[0]) && (verbose > 0))
+            pr2ws("%s: %s\n", __func__, ebuff);
+        return SG_LIB_CAT_MALFORMED;
+    }
+    xfer_len = calc_len - offset;
+    if (xfer_len > mx_mpage_len)
+        xfer_len = mx_mpage_len;
+
+    for (k = 0; k < 4; ++k) {
+        if (NULL == pcontrol_arr[k])
+            continue;
+        memset(pcontrol_arr[k], 0, mx_mpage_len);
+        resid = 0;
+        if (mode6)
+            res = sg_ll_mode_sense6(sg_fd, dbd, k /* pc */,
+                                    pg_code, sub_pg_code, buff,
+                                    calc_len, true, verbose);
+        else
+            res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd,
+                                        k /* pc */, pg_code, sub_pg_code,
+                                        buff, calc_len, 0, &resid, true,
+                                        verbose);
+        if (res || resid) {
+            if (0 == first_err) {
+                if (res)
+                    first_err = res;
+                else {
+                    first_err = -49;    /* unexpected resid != 0 */
+                    if (verbose)
+                        pr2ws("%s: unexpected resid=%d, page=0x%x, "
+                              "pcontrol=%d\n", __func__, resid, pg_code, k);
+                }
+            }
+            if (0 == k)
+                break;  /* if problem on current page, it won't improve */
+            else
+                continue;
+        }
+        if (xfer_len > 0)
+            memcpy(pcontrol_arr[k], buff + offset, xfer_len);
+        if (success_mask)
+            *success_mask |= (1 << k);
+    }
+    return first_err;
+}
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors. */
+int
+sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+                int subpg_code, int paramp, unsigned char * resp,
+                int mx_resp_len, bool noisy, int verbose)
+{
+    return sg_ll_log_sense_v2(sg_fd, ppc, sp, pc, pg_code, subpg_code,
+                              paramp, resp, mx_resp_len, 0, NULL, noisy,
+                              verbose);
+}
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * 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 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
+ * bytes have been written. */
+int
+sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+                   int subpg_code, int paramp, unsigned char * resp,
+                   int mx_resp_len, int timeout_secs, int * residp,
+                   bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "log sense";
+    int res, ret, k, sense_cat, resid;
+    unsigned char logs_cdb[LOG_SENSE_CMDLEN] =
+        {LOG_SENSE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (mx_resp_len > 0xffff) {
+        pr2ws("mx_resp_len too big\n");
+        goto gen_err;
+    }
+    logs_cdb[1] = (unsigned char)((ppc ? 2 : 0) | (sp ? 1 : 0));
+    logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+    logs_cdb[3] = (unsigned char)(subpg_code & 0xff);
+    sg_put_unaligned_be16((int16_t)paramp, logs_cdb + 5);
+    sg_put_unaligned_be16((int16_t)mx_resp_len, logs_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < LOG_SENSE_CMDLEN; ++k)
+            pr2ws("%02x ", logs_cdb[k]);
+        pr2ws("\n");
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        goto gen_err;
+    set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len,
+                               sense_b, noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (residp)
+        *residp = resid;
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((mx_resp_len > 3) && (ret < 4)) {
+            /* resid indicates LOG SENSE response length bad, so zero it */
+            resp[2] = 0;
+            resp[3] = 0;
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+
+    if (resid > 0) {
+        if (resid > mx_resp_len) {
+            pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+                  cdb_name_s, resid, mx_resp_len);
+            return ret ? ret : SG_LIB_CAT_MALFORMED;
+        }
+        /* zero unfilled section of response buffer */
+        memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
+    }
+    return ret;
+gen_err:
+    if (residp)
+        *residp = 0;
+    return -1;
+}
+
+/* Invokes a SCSI LOG SELECT command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code,
+                 int subpg_code, unsigned char * paramp, int param_len,
+                 bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "log select";
+    int res, ret, k, sense_cat;
+    unsigned char logs_cdb[LOG_SELECT_CMDLEN] =
+        {LOG_SELECT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (param_len > 0xffff) {
+        pr2ws("%s: param_len too big\n", cdb_name_s);
+        return -1;
+    }
+    logs_cdb[1] = (unsigned char)((pcr ? 2 : 0) | (sp ? 1 : 0));
+    logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+    logs_cdb[3] = (unsigned char)(subpg_code & 0xff);
+    sg_put_unaligned_be16((int16_t)param_len, logs_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < LOG_SELECT_CMDLEN; ++k)
+            pr2ws("%02x ", logs_cdb[k]);
+        pr2ws("\n");
+    }
+    if ((verbose > 1) && (param_len > 0)) {
+        pr2ws("    %s parameter list\n", cdb_name_s);
+        hex2stderr(paramp, param_len, -1);
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI START STOP UNIT command (SBC + MMC).
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and
+ * format_layer_number(mmc) fields. They also overlap on the noflush(sbc)
+ * and fl(mmc) one bit field. This is the cause of the awkardly named
+ * pc_mod__fl_num and noflush__fl arguments to this function.
+ *  */
+int
+sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
+                      int power_cond, bool noflush__fl, bool loej, bool start,
+                      bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "start stop unit";
+    int k, res, ret, sense_cat;
+    struct sg_pt_base * ptvp;
+    unsigned char ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+
+    if (immed)
+        ssuBlk[1] = 0x1;
+    ssuBlk[3] = pc_mod__fl_num & 0xf;  /* bits 2 and 3 are reserved in MMC */
+    ssuBlk[4] = ((power_cond & 0xf) << 4);
+    if (noflush__fl)
+        ssuBlk[4] |= 0x4;
+    if (loej)
+        ssuBlk[4] |= 0x2;
+    if (start)
+        ssuBlk[4] |= 0x1;
+    if (verbose) {
+        pr2ws("    %s command:", cdb_name_s);
+        for (k = 0; k < (int)sizeof(ssuBlk); ++k)
+                pr2ws(" %02x", ssuBlk[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, START_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+            ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command
+ * [was in SPC-3 but displaced from SPC-4 into SBC-3, MMC-5, SSC-3]
+ * prevent==0 allows removal, prevent==1 prevents removal ...
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "prevent allow medium removal";
+    int k, res, ret, sense_cat;
+    unsigned char p_cdb[PREVENT_ALLOW_CMDLEN] =
+                {PREVENT_ALLOW_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if ((prevent < 0) || (prevent > 3)) {
+        pr2ws("prevent argument should be 0, 1, 2 or 3\n");
+        return -1;
+    }
+    p_cdb[4] |= (prevent & 0x3);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < PREVENT_ALLOW_CMDLEN; ++k)
+            pr2ws("%02x ", p_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, p_cdb, sizeof(p_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+            ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
diff --git a/tools/sg_write_buffer/sg_cmds_extra.c b/tools/sg_write_buffer/sg_cmds_extra.c
new file mode 100644
index 0000000..bebc859
--- /dev/null
+++ b/tools/sg_write_buffer/sg_cmds_extra.c
@@ -0,0 +1,2524 @@
+/*
+ * Copyright (c) 1999-2018 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+
+#define DEF_PT_TIMEOUT 60       /* 60 seconds */
+#define LONG_PT_TIMEOUT 7200    /* 7,200 seconds == 120 minutes */
+
+#define SERVICE_ACTION_IN_16_CMD 0x9e
+#define SERVICE_ACTION_IN_16_CMDLEN 16
+#define SERVICE_ACTION_OUT_16_CMD 0x9f
+#define SERVICE_ACTION_OUT_16_CMDLEN 16
+#define MAINTENANCE_IN_CMD 0xa3
+#define MAINTENANCE_IN_CMDLEN 12
+#define MAINTENANCE_OUT_CMD 0xa4
+#define MAINTENANCE_OUT_CMDLEN 12
+
+#define ATA_PT_12_CMD 0xa1
+#define ATA_PT_12_CMDLEN 12
+#define ATA_PT_16_CMD 0x85
+#define ATA_PT_16_CMDLEN 16
+#define ATA_PT_32_SA 0x1ff0
+#define ATA_PT_32_CMDLEN 32
+#define FORMAT_UNIT_CMD 0x4
+#define FORMAT_UNIT_CMDLEN 6
+#define PERSISTENT_RESERVE_IN_CMD 0x5e
+#define PERSISTENT_RESERVE_IN_CMDLEN 10
+#define PERSISTENT_RESERVE_OUT_CMD 0x5f
+#define PERSISTENT_RESERVE_OUT_CMDLEN 10
+#define READ_BLOCK_LIMITS_CMD 0x5
+#define READ_BLOCK_LIMITS_CMDLEN 6
+#define READ_BUFFER_CMD 0x3c
+#define READ_BUFFER_CMDLEN 10
+#define READ_DEFECT10_CMD     0x37
+#define READ_DEFECT10_CMDLEN    10
+#define REASSIGN_BLKS_CMD     0x7
+#define REASSIGN_BLKS_CMDLEN  6
+#define RECEIVE_DIAGNOSTICS_CMD   0x1c
+#define RECEIVE_DIAGNOSTICS_CMDLEN  6
+#define THIRD_PARTY_COPY_OUT_CMD 0x83   /* was EXTENDED_COPY_CMD */
+#define THIRD_PARTY_COPY_OUT_CMDLEN 16
+#define THIRD_PARTY_COPY_IN_CMD 0x84     /* was RECEIVE_COPY_RESULTS_CMD */
+#define THIRD_PARTY_COPY_IN_CMDLEN 16
+#define SEND_DIAGNOSTIC_CMD   0x1d
+#define SEND_DIAGNOSTIC_CMDLEN  6
+#define SERVICE_ACTION_IN_12_CMD 0xab
+#define SERVICE_ACTION_IN_12_CMDLEN 12
+#define READ_LONG10_CMD 0x3e
+#define READ_LONG10_CMDLEN 10
+#define UNMAP_CMD 0x42
+#define UNMAP_CMDLEN 10
+#define VERIFY10_CMD 0x2f
+#define VERIFY10_CMDLEN 10
+#define VERIFY16_CMD 0x8f
+#define VERIFY16_CMDLEN 16
+#define WRITE_LONG10_CMD 0x3f
+#define WRITE_LONG10_CMDLEN 10
+#define WRITE_BUFFER_CMD 0x3b
+#define WRITE_BUFFER_CMDLEN 10
+#define PRE_FETCH10_CMD 0x34
+#define PRE_FETCH10_CMDLEN 10
+#define PRE_FETCH16_CMD 0x90
+#define PRE_FETCH16_CMDLEN 16
+#define SEEK10_CMD 0x2b
+#define SEEK10_CMDLEN 10
+
+#define GET_LBA_STATUS16_SA 0x12
+#define GET_LBA_STATUS32_SA 0x12
+#define READ_LONG_16_SA 0x11
+#define READ_MEDIA_SERIAL_NUM_SA 0x1
+#define REPORT_IDENTIFYING_INFORMATION_SA 0x5
+#define REPORT_TGT_PRT_GRP_SA 0xa
+#define SET_IDENTIFYING_INFORMATION_SA 0x6
+#define SET_TGT_PRT_GRP_SA 0xa
+#define WRITE_LONG_16_SA 0x11
+#define REPORT_REFERRALS_SA 0x13
+#define EXTENDED_COPY_LID1_SA 0x0
+
+#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;
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+    struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp)
+        pr2ws("%s: out of memory\n", cname);
+    return ptvp;
+}
+
+
+/* Invokes a SCSI GET LBA STATUS(16) command (SBC). Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt,
+                      void * resp, int alloc_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Get LBA status(16)";
+    int k, res, sense_cat, ret;
+    unsigned char getLbaStatCmd[SERVICE_ACTION_IN_16_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(getLbaStatCmd, 0, sizeof(getLbaStatCmd));
+    getLbaStatCmd[0] = SERVICE_ACTION_IN_16_CMD;
+    getLbaStatCmd[1] = GET_LBA_STATUS16_SA;
+
+    sg_put_unaligned_be64(start_llba, getLbaStatCmd + 2);
+    sg_put_unaligned_be32((uint32_t)alloc_len, getLbaStatCmd + 10);
+    getLbaStatCmd[14] = rt;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
+            pr2ws("%02x ", getLbaStatCmd[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, getLbaStatCmd, sizeof(getLbaStatCmd));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, alloc_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response\n", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+int
+sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp,
+                     int alloc_len, bool noisy, int verbose)
+{
+    return sg_ll_get_lba_status16(sg_fd, start_llba, /* rt = */ 0x0, resp,
+                                  alloc_len, noisy, verbose);
+}
+
+#define GLS32_CMD_LEN 32
+
+int
+sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len,
+                       uint32_t element_id, uint8_t rt,
+                       void * resp, int alloc_len, bool noisy,
+                       int verbose)
+{
+    static const char * const cdb_name_s = "Get LBA status(32)";
+    int k, res, sense_cat, ret;
+    unsigned char gls32_cmd[GLS32_CMD_LEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(gls32_cmd, 0, sizeof(gls32_cmd));
+    gls32_cmd[0] = SG_VARIABLE_LENGTH_CMD;
+    gls32_cmd[7] = GLS32_CMD_LEN - 8;
+    sg_put_unaligned_be16((uint16_t)GET_LBA_STATUS32_SA, gls32_cmd + 8);
+    gls32_cmd[10] = rt;
+    sg_put_unaligned_be64(start_llba, gls32_cmd + 12);
+    sg_put_unaligned_be32(scan_len, gls32_cmd + 20);
+    sg_put_unaligned_be32(element_id, gls32_cmd + 24);
+    sg_put_unaligned_be32((uint32_t)alloc_len, gls32_cmd + 28);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < GLS32_CMD_LEN; ++k)
+            pr2ws("%02x ", gls32_cmd[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, gls32_cmd, sizeof(gls32_cmd));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, alloc_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response\n", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+int
+sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len,
+                         bool noisy, int verbose)
+{
+    return sg_ll_report_tgt_prt_grp2(sg_fd, resp, mx_resp_len, false, noisy,
+                                     verbose);
+}
+
+/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len,
+                          bool extended, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Report target port groups";
+    int k, res, ret, sense_cat;
+    unsigned char rtpg_cdb[MAINTENANCE_IN_CMDLEN] =
+                         {MAINTENANCE_IN_CMD, REPORT_TGT_PRT_GRP_SA,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (extended)
+        rtpg_cdb[1] |= 0x20;
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rtpg_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k)
+            pr2ws("%02x ", rtpg_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rtpg_cdb, sizeof(rtpg_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy,
+                      int verbose)
+{
+    static const char * const cdb_name_s = "Set target port groups";
+    int k, res, ret, sense_cat;
+    unsigned char stpg_cdb[MAINTENANCE_OUT_CMDLEN] =
+                         {MAINTENANCE_OUT_CMD, SET_TGT_PRT_GRP_SA,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be32((uint32_t)param_len, stpg_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", stpg_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, stpg_cdb, sizeof(stpg_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg,
+                       void * resp, int mx_resp_len, bool noisy,
+                       int verbose)
+{
+    static const char * const cdb_name_s = "Report referrals";
+    int k, res, ret, sense_cat;
+    unsigned char repRef_cdb[SERVICE_ACTION_IN_16_CMDLEN] =
+                         {SERVICE_ACTION_IN_16_CMD, REPORT_REFERRALS_SA,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be64(start_llba, repRef_cdb + 2);
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, repRef_cdb + 10);
+    if (one_seg)
+        repRef_cdb[14] = 0x1;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
+            pr2ws("%02x ", repRef_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, repRef_cdb, sizeof(repRef_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* 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 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,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit,
+                bool devofl_bit, bool unitofl_bit, int long_duration,
+                void * paramp, int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Send diagnostic";
+    int k, res, ret, sense_cat, tmout;
+    unsigned char senddiag_cdb[SEND_DIAGNOSTIC_CMDLEN] =
+        {SEND_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    senddiag_cdb[1] = (unsigned char)(st_code << 5);
+    if (pf_bit)
+        senddiag_cdb[1] |= 0x10;
+    if (st_bit)
+        senddiag_cdb[1] |= 0x4;
+    if (devofl_bit)
+        senddiag_cdb[1] |= 0x2;
+    if (unitofl_bit)
+        senddiag_cdb[1] |= 0x1;
+    sg_put_unaligned_be16((uint16_t)param_len, senddiag_cdb + 3);
+    if (long_duration > LONG_PT_TIMEOUT)
+        tmout = long_duration;
+    else
+        tmout = long_duration ? LONG_PT_TIMEOUT : DEF_PT_TIMEOUT;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SEND_DIAGNOSTIC_CMDLEN; ++k)
+            pr2ws("%02x ", senddiag_cdb[k]);
+        pr2ws("\n");
+        if (verbose > 1) {
+            if (paramp && param_len) {
+                pr2ws("    %s parameter list:\n", cdb_name_s);
+                hex2stderr((const uint8_t *)paramp, param_len, -1);
+            }
+            pr2ws("    %s timeout: %d seconds\n", cdb_name_s, tmout);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp,
+                   int mx_resp_len, bool noisy, int verbose)
+{
+    return sg_ll_receive_diag_v2(sg_fd, pcv, pg_code, resp, mx_resp_len, 0,
+                                 NULL, noisy, verbose);
+}
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp,
+                      int mx_resp_len, int timeout_secs, int * residp,
+                      bool noisy, int verbose)
+{
+    int resid = 0;
+    int k, res, ret, sense_cat;
+    static const char * const cdb_name_s = "Receive diagnostic results";
+    struct sg_pt_base * ptvp;
+    unsigned char rcvdiag_cdb[RECEIVE_DIAGNOSTICS_CMDLEN] =
+        {RECEIVE_DIAGNOSTICS_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+
+    if (pcv)
+        rcvdiag_cdb[1] = 0x1;
+    rcvdiag_cdb[2] = (unsigned char)(pg_code);
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, rcvdiag_cdb + 3);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < RECEIVE_DIAGNOSTICS_CMDLEN; ++k)
+            pr2ws("%02x ", rcvdiag_cdb[k]);
+        pr2ws("\n");
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) {
+        if (residp)
+            *residp = 0;
+        return -1;
+    }
+    set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (residp)
+        *residp = resid;
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                            -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, int dl_format,
+                    void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Read defect(10)";
+    int res, k, ret, sense_cat;
+    unsigned char rdef_cdb[READ_DEFECT10_CMDLEN] =
+        {READ_DEFECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    rdef_cdb[2] = (dl_format & 0x7);
+    if (req_plist)
+        rdef_cdb[2] |= 0x10;
+    if (req_glist)
+        rdef_cdb[2] |= 0x8;
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, rdef_cdb + 7);
+    if (mx_resp_len > 0xffff) {
+        pr2ws("mx_resp_len too big\n");
+        return -1;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_DEFECT10_CMDLEN; ++k)
+            pr2ws("%02x ", rdef_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rdef_cdb, sizeof(rdef_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response\n", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len,
+                            bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Read media serial number";
+    int k, res, ret, sense_cat;
+    unsigned char rmsn_cdb[SERVICE_ACTION_IN_12_CMDLEN] =
+                         {SERVICE_ACTION_IN_12_CMD, READ_MEDIA_SERIAL_NUM_SA,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rmsn_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_12_CMDLEN; ++k)
+            pr2ws("%02x ", rmsn_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rmsn_cdb, sizeof(rmsn_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was
+ * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len,
+                     bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Report identifying information";
+    int k, res, ret, sense_cat;
+    unsigned char rii_cdb[MAINTENANCE_IN_CMDLEN] = {MAINTENANCE_IN_CMD,
+                        REPORT_IDENTIFYING_INFORMATION_SA,
+                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be32((uint32_t)max_resp_len, rii_cdb + 6);
+    rii_cdb[10] |= (itype << 1) & 0xfe;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k)
+            pr2ws("%02x ", rii_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rii_cdb, sizeof(rii_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, max_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, max_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was
+ * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len,
+                  bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Set identifying information";
+    int k, res, ret, sense_cat;
+    unsigned char sii_cdb[MAINTENANCE_OUT_CMDLEN] = {MAINTENANCE_OUT_CMD,
+                         SET_IDENTIFYING_INFORMATION_SA,
+                         0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be32((uint32_t)param_len, sii_cdb + 6);
+    sii_cdb[10] |= (itype << 1) & 0xfe;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", sii_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, sii_cdb, sizeof(sii_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                  bool cmplst, int dlist_format, int timeout_secs,
+                  void * paramp, int param_len, bool noisy, int verbose)
+{
+    return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst,
+                                dlist_format, 0, timeout_secs, paramp,
+                                param_len, noisy, verbose);
+}
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                   bool cmplst, int dlist_format, int ffmt, int timeout_secs,
+                   void * paramp, int param_len, bool noisy, int verbose)
+{
+    return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst,
+                                dlist_format, ffmt, timeout_secs, paramp,
+                                param_len, noisy, verbose);
+}
+
+/* Invokes a FORMAT UNIT (SBC-4) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * FFMT field added in sbc4r10 [20160121] */
+int
+sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                     bool cmplst, int dlist_format, int ffmt,
+                     int timeout_secs, void * paramp, int param_len,
+                     bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Format unit";
+    int k, res, ret, sense_cat, tmout;
+    unsigned char fu_cdb[FORMAT_UNIT_CMDLEN] =
+                {FORMAT_UNIT_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (fmtpinfo)
+        fu_cdb[1] |= (fmtpinfo << 6);
+    if (longlist)
+        fu_cdb[1] |= 0x20;
+    if (fmtdata)
+        fu_cdb[1] |= 0x10;
+    if (cmplst)
+        fu_cdb[1] |= 0x8;
+    if (dlist_format)
+        fu_cdb[1] |= (dlist_format & 0x7);
+    if (ffmt)
+        fu_cdb[4] |= (ffmt & 0x3);
+    tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < 6; ++k)
+            pr2ws("%02x ", fu_cdb[k]);
+        pr2ws("\n");
+        if (verbose > 1) {
+            if (param_len > 0) {
+                pr2ws("    %s parameter list:\n", cdb_name_s);
+                hex2stderr((const uint8_t *)paramp, param_len, -1);
+            }
+            pr2ws("    %s timeout: %d seconds\n", cdb_name_s, tmout);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, fu_cdb, sizeof(fu_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI REASSIGN BLOCKS command.  Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, void * paramp,
+                      int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Reassign blocks";
+    int res, k, ret, sense_cat;
+    unsigned char reass_cdb[REASSIGN_BLKS_CMDLEN] =
+        {REASSIGN_BLKS_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (longlba)
+        reass_cdb[1] = 0x2;
+    if (longlist)
+        reass_cdb[1] |= 0x1;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < REASSIGN_BLKS_CMDLEN; ++k)
+            pr2ws("%02x ", reass_cdb[k]);
+        pr2ws("\n");
+    }
+    if (verbose > 1) {
+        pr2ws("    %s parameter list\n", cdb_name_s);
+        hex2stderr((const uint8_t *)paramp, param_len, -1);
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, reass_cdb, sizeof(reass_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0
+ * when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp,
+                            int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Persistent reservation in";
+    int res, k, ret, sense_cat;
+    unsigned char prin_cdb[PERSISTENT_RESERVE_IN_CMDLEN] =
+                 {PERSISTENT_RESERVE_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (rq_servact > 0)
+        prin_cdb[1] = (unsigned char)(rq_servact & 0x1f);
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, prin_cdb + 7);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < PERSISTENT_RESERVE_IN_CMDLEN; ++k)
+            pr2ws("%02x ", prin_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, prin_cdb, sizeof(prin_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0
+ * when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope,
+                             unsigned int rq_type, void * paramp,
+                             int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Persistent reservation out";
+    int res, k, ret, sense_cat;
+    unsigned char prout_cdb[PERSISTENT_RESERVE_OUT_CMDLEN] =
+                 {PERSISTENT_RESERVE_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (rq_servact > 0)
+        prout_cdb[1] = (unsigned char)(rq_servact & 0x1f);
+    prout_cdb[2] = (((rq_scope & 0xf) << 4) | (rq_type & 0xf));
+    sg_put_unaligned_be16((uint16_t)param_len, prout_cdb + 7);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < PERSISTENT_RESERVE_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", prout_cdb[k]);
+        pr2ws("\n");
+        if (verbose > 1) {
+            pr2ws("    %s parameters:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, 0);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, prout_cdb, sizeof(prout_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+static bool
+has_blk_ili(unsigned char * sensep, int sb_len)
+{
+    int resp_code;
+    const unsigned char * cup;
+
+    if (sb_len < 8)
+        return false;
+    resp_code = (0x7f & sensep[0]);
+    if (resp_code >= 0x72) { /* descriptor format */
+        /* find block command descriptor */
+        if ((cup = sg_scsi_sense_desc_find(sensep, sb_len, 0x5)))
+            return (cup[3] & 0x20);
+    } else /* fixed */
+        return (sensep[2] & 0x20);
+    return false;
+}
+
+/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba,
+                  void * resp, int xfer_len, int * offsetp, bool noisy,
+                  int verbose)
+{
+    static const char * const cdb_name_s = "read long(10)";
+    int k, res, sense_cat, ret;
+    unsigned char readLong_cdb[READ_LONG10_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(readLong_cdb, 0, READ_LONG10_CMDLEN);
+    readLong_cdb[0] = READ_LONG10_CMD;
+    if (pblock)
+        readLong_cdb[1] |= 0x4;
+    if (correct)
+        readLong_cdb[1] |= 0x2;
+
+    sg_put_unaligned_be32((uint32_t)lba, readLong_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_LONG10_CMDLEN; ++k)
+            pr2ws("%02x ", readLong_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, xfer_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_ILLEGAL_REQ:
+            {
+                bool valid, ili;
+                int slen;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                ili = has_blk_ili(sense_b, slen);
+                if (valid && ili) {
+                    if (offsetp)
+                        *offsetp = (int)(int64_t)ull;
+                    ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+                } else {
+                    if (verbose > 1)
+                        pr2ws("  info field: 0x%" PRIx64 ",  valid: %d, "
+                              "ili: %d\n", ull, valid, ili);
+                    ret = SG_LIB_CAT_ILLEGAL_REQ;
+                }
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba,
+                  void * resp, int xfer_len, int * offsetp, bool noisy,
+                  int verbose)
+{
+    static const char * const cdb_name_s = "read long(16)";
+    int k, res, sense_cat, ret;
+    unsigned char readLong_cdb[SERVICE_ACTION_IN_16_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(readLong_cdb, 0, sizeof(readLong_cdb));
+    readLong_cdb[0] = SERVICE_ACTION_IN_16_CMD;
+    readLong_cdb[1] = READ_LONG_16_SA;
+    if (pblock)
+        readLong_cdb[14] |= 0x2;
+    if (correct)
+        readLong_cdb[14] |= 0x1;
+
+    sg_put_unaligned_be64(llba, readLong_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 12);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
+            pr2ws("%02x ", readLong_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, xfer_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_ILLEGAL_REQ:
+            {
+                bool valid, ili;
+                int slen;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                ili = has_blk_ili(sense_b, slen);
+                if (valid && ili) {
+                    if (offsetp)
+                        *offsetp = (int)(int64_t)ull;
+                    ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+                } else {
+                    if (verbose > 1)
+                        pr2ws("  info field: 0x%" PRIx64 ",  valid: %d, "
+                              "ili: %d\n", ull, (int)valid, (int)ili);
+                    ret = SG_LIB_CAT_ILLEGAL_REQ;
+                }
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+                   unsigned int lba, void * data_out, int xfer_len,
+                   int * offsetp, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "write long(10)";
+    int k, res, sense_cat, ret;
+    unsigned char writeLong_cdb[WRITE_LONG10_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(writeLong_cdb, 0, WRITE_LONG10_CMDLEN);
+    writeLong_cdb[0] = WRITE_LONG10_CMD;
+    if (cor_dis)
+        writeLong_cdb[1] |= 0x80;
+    if (wr_uncor)
+        writeLong_cdb[1] |= 0x40;
+    if (pblock)
+        writeLong_cdb[1] |= 0x20;
+
+    sg_put_unaligned_be32((uint32_t)lba, writeLong_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < (int)sizeof(writeLong_cdb); ++k)
+            pr2ws("%02x ", writeLong_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, xfer_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret)
+        ;
+    else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_ILLEGAL_REQ:
+            {
+                int valid, slen, ili;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                ili = has_blk_ili(sense_b, slen);
+                if (valid && ili) {
+                    if (offsetp)
+                        *offsetp = (int)(int64_t)ull;
+                    ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+                } else {
+                    if (verbose > 1)
+                        pr2ws("  info field: 0x%" PRIx64 ",  valid: %d, "
+                              "ili: %d\n", ull, (int)valid, (int)ili);
+                    ret = SG_LIB_CAT_ILLEGAL_REQ;
+                }
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+                   uint64_t llba, void * data_out, int xfer_len,
+                   int * offsetp, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "write long(16)";
+    int k, res, sense_cat, ret;
+    unsigned char writeLong_cdb[SERVICE_ACTION_OUT_16_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(writeLong_cdb, 0, sizeof(writeLong_cdb));
+    writeLong_cdb[0] = SERVICE_ACTION_OUT_16_CMD;
+    writeLong_cdb[1] = WRITE_LONG_16_SA;
+    if (cor_dis)
+        writeLong_cdb[1] |= 0x80;
+    if (wr_uncor)
+        writeLong_cdb[1] |= 0x40;
+    if (pblock)
+        writeLong_cdb[1] |= 0x20;
+
+    sg_put_unaligned_be64(llba, writeLong_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 12);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_OUT_16_CMDLEN; ++k)
+            pr2ws("%02x ", writeLong_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, xfer_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_ILLEGAL_REQ:
+            {
+                bool valid, ili;
+                int slen;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                ili = has_blk_ili(sense_b, slen);
+                if (valid && ili) {
+                    if (offsetp)
+                        *offsetp = (int)(int64_t)ull;
+                    ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+                } else {
+                    if (verbose > 1)
+                        pr2ws("  info field: 0x%" PRIx64 ",  valid: %d, "
+                              "ili: %d\n", ull, (int)valid, (int)ili);
+                    ret = SG_LIB_CAT_ILLEGAL_REQ;
+                }
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI VERIFY (10) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success, * various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytchk,
+               unsigned int lba, int veri_len, void * data_out,
+               int data_out_len, unsigned int * infop, bool noisy,
+               int verbose)
+{
+    static const char * const cdb_name_s = "verify(10)";
+    int k, res, ret, sense_cat, slen;
+    unsigned char v_cdb[VERIFY10_CMDLEN] =
+                {VERIFY10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */
+    v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ;
+    if (dpo)
+        v_cdb[1] |= 0x10;
+    sg_put_unaligned_be32((uint32_t)lba, v_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)veri_len, v_cdb + 7);
+    if (verbose > 1) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < VERIFY10_CMDLEN; ++k)
+            pr2ws("%02x ", v_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) {
+            k = data_out_len > 4104 ? 4104 : data_out_len;
+            pr2ws("    data_out buffer%s\n",
+                  (data_out_len > 4104 ? ", first 4104 bytes" : ""));
+            hex2stderr((const uint8_t *)data_out, k, verbose < 5);
+        }
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (data_out_len > 0)
+        set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, data_out_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_MEDIUM_HARD:
+            {
+                bool valid;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                if (valid) {
+                    if (infop)
+                        *infop = (unsigned int)ull;
+                    ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO;
+                } else
+                    ret = SG_LIB_CAT_MEDIUM_HARD;
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI VERIFY (16) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytchk, uint64_t llba,
+               int veri_len, int group_num, void * data_out,
+               int data_out_len, uint64_t * infop, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "verify(16)";
+    int k, res, ret, sense_cat, slen;
+    unsigned char v_cdb[VERIFY16_CMDLEN] =
+                {VERIFY16_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */
+    v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ;
+    if (dpo)
+        v_cdb[1] |= 0x10;
+    sg_put_unaligned_be64(llba, v_cdb + 2);
+    sg_put_unaligned_be32((uint32_t)veri_len, v_cdb + 10);
+    v_cdb[14] = group_num & 0x1f;
+    if (verbose > 1) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < VERIFY16_CMDLEN; ++k)
+            pr2ws("%02x ", v_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) {
+            k = data_out_len > 4104 ? 4104 : data_out_len;
+            pr2ws("    data_out buffer%s\n",
+                  (data_out_len > 4104 ? ", first 4104 bytes" : ""));
+            hex2stderr((const uint8_t *)data_out, k, verbose < 5);
+        }
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (data_out_len > 0)
+        set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, data_out_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_MEDIUM_HARD:
+            {
+                bool valid;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                if (valid) {
+                    if (infop)
+                        *infop = ull;
+                    ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO;
+                } else
+                    ret = SG_LIB_CAT_MEDIUM_HARD;
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is
+ * selected by the cdb_len argument that can take values of 12, 16 or 32
+ * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9
+ * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from
+ * the control byte, the rest is copied into an internal cdb which is then
+ * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15
+ * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the
+ * timeout is set to 60 seconds. For data in or out transfers set dinp or
+ * doutp, and dlen to the number of bytes to transfer. If dlen is zero then
+ * no data transfer is assumed. If sense buffer obtained then it is written
+ * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is
+ * obtained then written to ata_return_dp, else ata_return_dp[0] is set to
+ * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers.
+ * Returns SCSI status value (>= 0) or -1 if other error. Users are
+ * expected to check the sense buffer themselves. If available the data in
+ * resid is written to residp. Note in SAT-2 and later, fixed format sense
+ * data may be placed in *sensep in which case sensep[0]==0x70, prior to
+ * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72).
+ */
+int
+sg_ll_ata_pt(int sg_fd, const unsigned char * cdbp, int cdb_len,
+             int timeout_secs, void * dinp, void * doutp, int dlen,
+             unsigned char * sensep, int max_sense_len,
+             unsigned char * ata_return_dp, int max_ata_return_len,
+             int * residp, int verbose)
+{
+    int k, res, slen, duration;
+    int ret = -1;
+    unsigned char apt_cdb[ATA_PT_32_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    unsigned char * sp;
+    const unsigned char * bp;
+    struct sg_pt_base * ptvp;
+    const char * cnamep;
+    char b[256];
+
+    memset(apt_cdb, 0, sizeof(apt_cdb));
+    b[0] = '\0';
+    switch (cdb_len) {
+    case 12:
+        cnamep = "ATA pass-through(12)";
+        apt_cdb[0] = ATA_PT_12_CMD;
+        memcpy(apt_cdb + 1, cdbp + 1,  10);
+        /* control byte at cdb[11] left at zero */
+        break;
+    case 16:
+        cnamep = "ATA pass-through(16)";
+        apt_cdb[0] = ATA_PT_16_CMD;
+        memcpy(apt_cdb + 1, cdbp + 1,  14);
+        /* control byte at cdb[15] left at zero */
+        break;
+    case 32:
+        cnamep = "ATA pass-through(32)";
+        apt_cdb[0] = SG_VARIABLE_LENGTH_CMD;
+        /* control byte at cdb[1] left at zero */
+        apt_cdb[7] = 0x18;    /* length starting at next byte */
+        sg_put_unaligned_be16(ATA_PT_32_SA, apt_cdb + 8);
+        memcpy(apt_cdb + 10, cdbp + 10,  32 - 10);
+        break;
+    default:
+        pr2ws("cdb_len must be 12, 16 or 32\n");
+        return -1;
+    }
+    if (NULL == cdbp) {
+        if (verbose)
+            pr2ws("%s NULL cdb pointer\n", cnamep);
+        return -1;
+    }
+    if (sensep && (max_sense_len >= (int)sizeof(sense_b))) {
+        sp = sensep;
+        slen = max_sense_len;
+    } else {
+        sp = sense_b;
+        slen = sizeof(sense_b);
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cnamep);
+        if (cdb_len < 32) {
+            for (k = 0; k < cdb_len; ++k)
+                pr2ws("%02x ", apt_cdb[k]);
+            pr2ws("\n");
+        } else {
+            pr2ws("\n");
+            hex2stderr(apt_cdb, cdb_len, -1);
+        }
+    }
+    if (NULL == ((ptvp = create_pt_obj(cnamep))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, apt_cdb, cdb_len);
+    set_scsi_pt_sense(ptvp, sp, slen);
+    if (dlen > 0) {
+        if (dinp)
+            set_scsi_pt_data_in(ptvp, (unsigned char *)dinp, dlen);
+        else if (doutp)
+            set_scsi_pt_data_out(ptvp, (unsigned char *)doutp, dlen);
+    }
+    res = do_scsi_pt(ptvp, sg_fd,
+                     ((timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT),
+                     verbose);
+    if (SCSI_PT_DO_BAD_PARAMS == res) {
+        if (verbose)
+            pr2ws("%s: bad parameters\n", cnamep);
+        goto out;
+    } else if (SCSI_PT_DO_TIMEOUT == res) {
+        if (verbose)
+            pr2ws("%s: timeout\n", cnamep);
+        goto out;
+    } else if (res > 2) {
+        if (verbose)
+            pr2ws("%s: do_scsi_pt: errno=%d\n", cnamep, -res);
+    }
+
+    if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0))
+        pr2ws("      duration=%d ms\n", duration);
+
+    switch (get_scsi_pt_result_category(ptvp)) {
+    case SCSI_PT_RESULT_GOOD:
+        if ((sensep) && (max_sense_len > 0))
+            *sensep = 0;
+        if ((ata_return_dp) && (max_ata_return_len > 0))
+            *ata_return_dp = 0;
+        if (residp && (dlen > 0))
+            *residp = get_scsi_pt_resid(ptvp);
+        ret = 0;
+        break;
+    case SCSI_PT_RESULT_STATUS: /* other than GOOD + CHECK CONDITION */
+        if ((sensep) && (max_sense_len > 0))
+            *sensep = 0;
+        if ((ata_return_dp) && (max_ata_return_len > 0))
+            *ata_return_dp = 0;
+        ret = get_scsi_pt_status_response(ptvp);
+        break;
+    case SCSI_PT_RESULT_SENSE:
+        if (sensep && (sp != sensep)) {
+            k = get_scsi_pt_sense_len(ptvp);
+            k = (k > max_sense_len) ? max_sense_len : k;
+            memcpy(sensep, sp, k);
+        }
+        if (ata_return_dp && (max_ata_return_len > 0))  {
+            /* search for ATA return descriptor */
+            bp = sg_scsi_sense_desc_find(sp, slen, 0x9);
+            if (bp) {
+                k = bp[1] + 2;
+                k = (k > max_ata_return_len) ? max_ata_return_len : k;
+                memcpy(ata_return_dp, bp, k);
+            } else
+                ata_return_dp[0] = 0x0;
+        }
+        if (residp && (dlen > 0))
+            *residp = get_scsi_pt_resid(ptvp);
+        ret = get_scsi_pt_status_response(ptvp);
+        break;
+    case SCSI_PT_RESULT_TRANSPORT_ERR:
+        if (verbose)
+            pr2ws("%s: transport error: %s\n", cnamep,
+                  get_scsi_pt_transport_err_str(ptvp, sizeof(b), b));
+        break;
+    case SCSI_PT_RESULT_OS_ERR:
+        if (verbose)
+            pr2ws("%s: os error: %s\n", cnamep,
+                  get_scsi_pt_os_err_str(ptvp, sizeof(b) , b));
+        break;
+    default:
+        if (verbose)
+            pr2ws("%s: unknown pt_result_category=%d\n", cnamep,
+                  get_scsi_pt_result_category(ptvp));
+        break;
+    }
+
+out:
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ BUFFER(10) command (SPC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+                  void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "read buffer(10)";
+    int res, k, ret, sense_cat;
+    unsigned char rbuf_cdb[READ_BUFFER_CMDLEN] =
+        {READ_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    rbuf_cdb[1] = (unsigned char)(mode & 0x1f);
+    rbuf_cdb[2] = (unsigned char)(buffer_id & 0xff);
+    sg_put_unaligned_be24((uint32_t)buffer_offset, rbuf_cdb + 3);
+    sg_put_unaligned_be24((uint32_t)mx_resp_len, rbuf_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_BUFFER_CMDLEN; ++k)
+            pr2ws("%02x ", rbuf_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rbuf_cdb, sizeof(rbuf_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+                   void * paramp, int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "write buffer";
+    int k, res, ret, sense_cat;
+    unsigned char wbuf_cdb[WRITE_BUFFER_CMDLEN] =
+        {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    wbuf_cdb[1] = (unsigned char)(mode & 0x1f);
+    wbuf_cdb[2] = (unsigned char)(buffer_id & 0xff);
+    sg_put_unaligned_be24((uint32_t)buffer_offset, wbuf_cdb + 3);
+    sg_put_unaligned_be24((uint32_t)param_len, wbuf_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k)
+            pr2ws("%02x ", wbuf_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list", cdb_name_s);
+            if (2 == verbose) {
+                pr2ws("%s:\n", (param_len > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)paramp,
+                           (param_len > 256 ? 256 : param_len), -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)paramp, param_len, 0);
+            }
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure. Adds mode specific field (spc4r32) and timeout
+ *  to command abort to override default of 60 seconds. If timeout_secs is
+ *  0 or less then the default timeout is used instead. */
+int
+sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id,
+                      uint32_t buffer_offset, void * paramp,
+                      uint32_t param_len, int timeout_secs, bool noisy,
+                      int verbose)
+{
+    int k, res, ret, sense_cat;
+    uint8_t wbuf_cdb[WRITE_BUFFER_CMDLEN] =
+        {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    uint8_t sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (buffer_offset > 0xffffff) {
+        pr2ws("%s: buffer_offset value too large for 24 bits\n", __func__);
+        return -1;
+    }
+    if (param_len > 0xffffff) {
+        pr2ws("%s: param_len value too large for 24 bits\n", __func__);
+        return -1;
+    }
+    wbuf_cdb[1] = (uint8_t)(mode & 0x1f);
+    wbuf_cdb[1] |= (uint8_t)((m_specific & 0x7) << 5);
+    wbuf_cdb[2] = (uint8_t)(buffer_id & 0xff);
+    sg_put_unaligned_be24(buffer_offset, wbuf_cdb + 3);
+    sg_put_unaligned_be24(param_len, wbuf_cdb + 6);
+    if (verbose) {
+        pr2ws("    Write buffer cdb: ");
+        for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k)
+            pr2ws("%02x ", wbuf_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    Write buffer parameter list%s:\n",
+                  ((param_len > 256) ? " (first 256 bytes)" : ""));
+            hex2stderr((const uint8_t *)paramp,
+                       ((param_len > 256) ? 256 : param_len), -1);
+        }
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+
+    ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp) {
+        pr2ws("%s: out of memory\n", __func__);
+        return -1;
+    }
+    set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, "Write buffer", res, SG_NO_DATA_IN,
+                               sense_b, noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI UNMAP command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp,
+            int param_len, bool noisy, int verbose)
+{
+    return sg_ll_unmap_v2(sg_fd, false, group_num, timeout_secs, paramp,
+                          param_len, noisy, verbose);
+}
+
+/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field
+ * (sbc3r22). Otherwise same as sg_ll_unmap() . */
+int
+sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs,
+               void * paramp, int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "unmap";
+    int k, res, ret, sense_cat, tmout;
+    unsigned char u_cdb[UNMAP_CMDLEN] =
+                         {UNMAP_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (anchor)
+        u_cdb[1] |= 0x1;
+    tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+    u_cdb[6] = group_num & 0x1f;
+    sg_put_unaligned_be16((uint16_t)param_len, u_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < UNMAP_CMDLEN; ++k)
+            pr2ws("%02x ", u_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, u_cdb, sizeof(u_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len,
+                        bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "read block limits";
+    int k, ret, res, sense_cat;
+    unsigned char rl_cdb[READ_BLOCK_LIMITS_CMDLEN] =
+      {READ_BLOCK_LIMITS_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_BLOCK_LIMITS_CMDLEN; ++k)
+            pr2ws("%02x ", rl_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI RECEIVE COPY RESULTS command. Actually cover all current
+ * uses of opcode 0x84 (Third-party copy IN). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp,
+                           int mx_resp_len, bool noisy, int verbose)
+{
+    int k, res, ret, sense_cat;
+    unsigned char rcvcopyres_cdb[THIRD_PARTY_COPY_IN_CMDLEN] =
+      {THIRD_PARTY_COPY_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+    char b[64];
+
+    sg_get_opcode_sa_name(THIRD_PARTY_COPY_IN_CMD, sa, 0, (int)sizeof(b), b);
+    rcvcopyres_cdb[1] = (unsigned char)(sa & 0x1f);
+    if (sa <= 4)        /* LID1 variants */
+        rcvcopyres_cdb[2] = (unsigned char)(list_id);
+    else if ((sa >= 5) && (sa <= 7))    /* LID4 variants */
+        sg_put_unaligned_be32((uint32_t)list_id, rcvcopyres_cdb + 2);
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rcvcopyres_cdb + 10);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", b);
+        for (k = 0; k < THIRD_PARTY_COPY_IN_CMDLEN; ++k)
+            pr2ws("%02x ", rcvcopyres_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(b))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rcvcopyres_cdb, sizeof(rcvcopyres_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, b, res, mx_resp_len, sense_b, noisy,
+                               verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+
+/* SPC-4 rev 35 and later calls this opcode (0x83) "Third-party copy OUT"
+ * The original EXTENDED COPY command (now called EXTENDED COPY (LID1))
+ * is the only one supported by sg_ll_extended_copy(). See function
+ * sg_ll_3party_copy_out() for the other service actions ( > 0 ). */
+
+/* Invokes a SCSI EXTENDED COPY (LID1) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy,
+                    int verbose)
+{
+    int k, res, ret, sense_cat;
+    unsigned char xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] =
+      {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+    const char * opcode_name = "Extended copy (LID1)";
+
+    xcopy_cdb[1] = (unsigned char)(EXTENDED_COPY_LID1_SA & 0x1f);
+    sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", opcode_name);
+        for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", xcopy_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", opcode_name);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(opcode_name))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, opcode_name, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Handles various service actions associated with opcode 0x83 which is
+ * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID1 and
+ * LID4), POPULATE TOKEN and WRITE USING TOKEN commands.
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, int group_num,
+                      int timeout_secs, void * paramp, int param_len,
+                      bool noisy, int verbose)
+{
+    int k, res, ret, sense_cat, tmout;
+    unsigned char xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] =
+      {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+    char cname[80];
+
+    sg_get_opcode_sa_name(THIRD_PARTY_COPY_OUT_CMD, sa, 0, sizeof(cname),
+                          cname);
+    xcopy_cdb[1] = (unsigned char)(sa & 0x1f);
+    switch (sa) {
+    case 0x0:   /* XCOPY(LID1) */
+    case 0x1:   /* XCOPY(LID4) */
+        sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+        break;
+    case 0x10:  /* POPULATE TOKEN (SBC-3) */
+    case 0x11:  /* WRITE USING TOKEN (SBC-3) */
+        sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 6);
+        sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+        xcopy_cdb[14] = (unsigned char)(group_num & 0x1f);
+        break;
+    case 0x1c:  /* COPY OPERATION ABORT */
+        sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 2);
+        break;
+    default:
+        pr2ws("%s: unknown service action 0x%x\n", __func__, sa);
+        return -1;
+    }
+    tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cname);
+        for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", xcopy_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cname);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cname))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    ret = sg_cmds_process_resp(ptvp, cname, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC).
+ * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_*
+ * positive values or -1 -> other errors. Note that CONDITION MET status
+ * is returned when immed=true and num_blocks can fit in device's cache,
+ * somewaht strangely, GOOD status (return 0) is returned if num_blocks
+ * cannot fit in device's cache. If do_seek10==true then does a SEEK(10)
+ * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10)
+ * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then
+ * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */
+int
+sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed,
+                  uint64_t lba, uint32_t num_blocks, int group_num,
+                  int timeout_secs, bool noisy, int verbose)
+{
+    static const char * const cdb10_name_s = "Pre-fetch(10)";
+    static const char * const cdb16_name_s = "Pre-fetch(16)";
+    static const char * const cdb_seek_name_s = "Seek(10)";
+    int k, res, sense_cat, ret, cdb_len, tmout;
+    const char *cdb_name_s;
+    unsigned char preFetchCdb[PRE_FETCH16_CMDLEN]; /* all use longest cdb */
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(preFetchCdb, 0, sizeof(preFetchCdb));
+    if (do_seek10) {
+        if (lba > UINT32_MAX) {
+            if (verbose)
+                pr2ws("%s: LBA exceeds 2**32 in %s\n", __func__,
+                      cdb_seek_name_s);
+            return -1;
+        }
+        preFetchCdb[0] = SEEK10_CMD;
+        cdb_len = SEEK10_CMDLEN;
+        cdb_name_s = cdb_seek_name_s;
+        sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2);
+    } else {
+        if ((! cdb16) &&
+            ((lba > UINT32_MAX) || (num_blocks > UINT16_MAX))) {
+            cdb16 = true;
+            if (noisy || verbose)
+                pr2ws("%s: do %s due to %s size\n", __func__, cdb16_name_s,
+                      (lba > UINT32_MAX) ? "LBA" : "NUM_BLOCKS");
+        }
+        if (cdb16) {
+            preFetchCdb[0] = PRE_FETCH16_CMD;
+            cdb_len = PRE_FETCH16_CMDLEN;
+            cdb_name_s = cdb16_name_s;
+            if (immed)
+                preFetchCdb[1] = 0x2;
+            sg_put_unaligned_be64(lba, preFetchCdb + 2);
+            sg_put_unaligned_be32(num_blocks, preFetchCdb + 10);
+            preFetchCdb[14] = 0x3f & group_num;
+        } else {
+            preFetchCdb[0] = PRE_FETCH10_CMD;
+            cdb_len = PRE_FETCH10_CMDLEN;
+            cdb_name_s = cdb10_name_s;
+            if (immed)
+                preFetchCdb[1] = 0x2;
+            sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2);
+            preFetchCdb[6] = 0x3f & group_num;
+            sg_put_unaligned_be16((uint16_t)num_blocks, preFetchCdb + 7);
+        }
+    }
+    tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < cdb_len; ++k)
+            pr2ws("%02x ", preFetchCdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, preFetchCdb, cdb_len);
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    if (0 == res) {
+        int sstat = get_scsi_pt_status_response(ptvp);
+
+        if (SG_LIB_CAT_CONDITION_MET == sstat) {
+            ret = SG_LIB_CAT_CONDITION_MET;
+            if (verbose > 2)
+                pr2ws("%s: returns SG_LIB_CAT_CONDITION_MET\n", __func__);
+            goto fini;
+        }
+    }
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+fini:
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
diff --git a/tools/sg_write_buffer/sg_cmds_mmc.c b/tools/sg_write_buffer/sg_cmds_mmc.c
new file mode 100644
index 0000000..18f6ae1
--- /dev/null
+++ b/tools/sg_write_buffer/sg_cmds_mmc.c
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2008-2018 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_mmc.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+
+#define DEF_PT_TIMEOUT 60       /* 60 seconds */
+
+#define GET_CONFIG_CMD 0x46
+#define GET_CONFIG_CMD_LEN 10
+#define GET_PERFORMANCE_CMD 0xac
+#define GET_PERFORMANCE_CMD_LEN 12
+#define SET_CD_SPEED_CMD 0xbb
+#define SET_CD_SPEED_CMDLEN 12
+#define SET_STREAMING_CMD 0xb6
+#define SET_STREAMING_CMDLEN 12
+
+#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;
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+    struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp)
+        pr2ws("%s: out of memory\n", cname);
+    return ptvp;
+}
+
+/* Invokes a SCSI SET CD SPEED command (MMC).
+ * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int
+sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed,
+                   int drv_write_speed, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "set cd speed";
+    int res, ret, k, sense_cat;
+    unsigned char scsCmdBlk[SET_CD_SPEED_CMDLEN] = {SET_CD_SPEED_CMD, 0,
+                                         0, 0, 0, 0, 0, 0, 0, 0, 0 ,0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    scsCmdBlk[1] |= (rot_control & 0x3);
+    sg_put_unaligned_be16((uint16_t)drv_read_speed, scsCmdBlk + 2);
+    sg_put_unaligned_be16((uint16_t)drv_write_speed, scsCmdBlk + 4);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SET_CD_SPEED_CMDLEN; ++k)
+            pr2ws("%02x ", scsCmdBlk[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, scsCmdBlk, sizeof(scsCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_NOT_READY:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI GET CONFIGURATION command (MMC-3,4,5).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int
+sg_ll_get_config(int sg_fd, int rt, int starting, void * resp,
+                 int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "get configuration";
+    int res, k, ret, sense_cat;
+    unsigned char gcCmdBlk[GET_CONFIG_CMD_LEN] = {GET_CONFIG_CMD, 0, 0, 0,
+                                                  0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if ((rt < 0) || (rt > 3)) {
+        pr2ws("Bad rt value: %d\n", rt);
+        return -1;
+    }
+    gcCmdBlk[1] = (rt & 0x3);
+    if ((starting < 0) || (starting > 0xffff)) {
+        pr2ws("Bad starting field number: 0x%x\n", starting);
+        return -1;
+    }
+    sg_put_unaligned_be16((uint16_t)starting, gcCmdBlk + 2);
+    if ((mx_resp_len < 0) || (mx_resp_len > 0xffff)) {
+        pr2ws("Bad mx_resp_len: 0x%x\n", starting);
+        return -1;
+    }
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, gcCmdBlk + 7);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < GET_CONFIG_CMD_LEN; ++k)
+            pr2ws("%02x ", gcCmdBlk[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, gcCmdBlk, sizeof(gcCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len,
+                               sense_b, noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 3)) {
+            unsigned char * bp;
+            int len;
+
+            bp = (unsigned char *)resp;
+            len = sg_get_unaligned_be32(bp + 0);
+            if (len < 0)
+                len = 0;
+            len = (ret < len) ? ret : len;
+            pr2ws("    %s: response:\n", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, len, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int
+sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba,
+                      int max_num_desc, int ttype, void * resp,
+                      int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "get performance";
+    int res, k, ret, sense_cat;
+    unsigned char gpCmdBlk[GET_PERFORMANCE_CMD_LEN] = {GET_PERFORMANCE_CMD, 0,
+                                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if ((data_type < 0) || (data_type > 0x1f)) {
+        pr2ws("Bad data_type value: %d\n", data_type);
+        return -1;
+    }
+    gpCmdBlk[1] = (data_type & 0x1f);
+    sg_put_unaligned_be32((uint32_t)starting_lba, gpCmdBlk + 2);
+    if ((max_num_desc < 0) || (max_num_desc > 0xffff)) {
+        pr2ws("Bad max_num_desc: 0x%x\n", max_num_desc);
+        return -1;
+    }
+    sg_put_unaligned_be16((uint16_t)max_num_desc, gpCmdBlk + 8);
+    if ((ttype < 0) || (ttype > 0xff)) {
+        pr2ws("Bad type: 0x%x\n", ttype);
+        return -1;
+    }
+    gpCmdBlk[10] = (unsigned char)ttype;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < GET_PERFORMANCE_CMD_LEN; ++k)
+            pr2ws("%02x ", gpCmdBlk[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, gpCmdBlk, sizeof(gpCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 3)) {
+            unsigned char * bp;
+            int len;
+
+            bp = (unsigned char *)resp;
+            len = sg_get_unaligned_be32(bp + 0);
+            if (len < 0)
+                len = 0;
+            len = (ret < len) ? ret : len;
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, len, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int
+sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len,
+                    bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "set streaming";
+    int k, res, ret, sense_cat;
+    unsigned char ssCmdBlk[SET_STREAMING_CMDLEN] =
+                 {SET_STREAMING_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    ssCmdBlk[8] = type;
+    sg_put_unaligned_be16((uint16_t)param_len, ssCmdBlk + 9);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SET_STREAMING_CMDLEN; ++k)
+            pr2ws("%02x ", ssCmdBlk[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, ssCmdBlk, sizeof(ssCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_NOT_READY:
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
diff --git a/tools/sg_write_buffer/sg_io_linux.c b/tools/sg_write_buffer/sg_io_linux.c
new file mode 100644
index 0000000..f9f08e7
--- /dev/null
+++ b/tools/sg_write_buffer/sg_io_linux.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 1999-2018 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef SG_LIB_LINUX
+
+#include "sg_io_linux.h"
+
+
+/* Version 1.07 20160405 */
+
+#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;
+}
+
+
+void
+sg_print_masked_status(int masked_status)
+{
+    int scsi_status = (masked_status << 1) & 0x7e;
+
+    sg_print_scsi_status(scsi_status);
+}
+
+static const char * linux_host_bytes[] = {
+    "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT",
+    "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR",
+    "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR",
+    "DID_IMM_RETRY", "DID_REQUEUE", "DID_TRANSPORT_DISRUPTED",
+    "DID_TRANSPORT_FAILFAST", "DID_TARGET_FAILURE", "DID_NEXUS_FAILURE",
+    "DID_ALLOC_FAILURE", "DID_MEDIUM_ERROR",
+};
+
+#define LINUX_HOST_BYTES_SZ \
+        (int)(sizeof(linux_host_bytes) / sizeof(linux_host_bytes[0]))
+
+void
+sg_print_host_status(int host_status)
+{
+    pr2ws("Host_status=0x%02x ", host_status);
+    if ((host_status < 0) || (host_status >= LINUX_HOST_BYTES_SZ))
+        pr2ws("is invalid ");
+    else
+        pr2ws("[%s] ", linux_host_bytes[host_status]);
+}
+
+static const char * linux_driver_bytes[] = {
+    "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA",
+    "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD",
+    "DRIVER_SENSE"
+};
+
+#define LINUX_DRIVER_BYTES_SZ \
+    (int)(sizeof(linux_driver_bytes) / sizeof(linux_driver_bytes[0]))
+
+#if 0
+static const char * linux_driver_suggests[] = {
+    "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP",
+    "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN",
+    "SUGGEST_SENSE"
+};
+
+#define LINUX_DRIVER_SUGGESTS_SZ \
+    (int)(sizeof(linux_driver_suggests) / sizeof(linux_driver_suggests[0]))
+#endif
+
+
+void
+sg_print_driver_status(int driver_status)
+{
+    int driv;
+    const char * driv_cp = "invalid";
+
+    driv = driver_status & SG_LIB_DRIVER_MASK;
+    if (driv < LINUX_DRIVER_BYTES_SZ)
+        driv_cp = linux_driver_bytes[driv];
+#if 0
+    sugg = (driver_status & SG_LIB_SUGGEST_MASK) >> 4;
+    if (sugg < LINUX_DRIVER_SUGGESTS_SZ)
+        sugg_cp = linux_driver_suggests[sugg];
+#endif
+    pr2ws("Driver_status=0x%02x", driver_status);
+    pr2ws(" [%s] ", driv_cp);
+}
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+   prints error/warning (prefix by 'leadin') and returns 0. */
+static int
+sg_linux_sense_print(const char * leadin, int scsi_status, int host_status,
+                     int driver_status, const unsigned char * sense_buffer,
+                     int sb_len, bool raw_sinfo)
+{
+    bool done_leadin = false;
+    bool done_sense = false;
+
+    scsi_status &= 0x7e; /*sanity */
+    if ((0 == scsi_status) && (0 == host_status) && (0 == driver_status))
+        return 1;       /* No problems */
+    if (0 != scsi_status) {
+        if (leadin)
+            pr2ws("%s: ", leadin);
+        done_leadin = true;
+        pr2ws("SCSI status: ");
+        sg_print_scsi_status(scsi_status);
+        pr2ws("\n");
+        if (sense_buffer && ((scsi_status == SAM_STAT_CHECK_CONDITION) ||
+                             (scsi_status == SAM_STAT_COMMAND_TERMINATED))) {
+            /* SAM_STAT_COMMAND_TERMINATED is obsolete */
+            sg_print_sense(0, sense_buffer, sb_len, raw_sinfo);
+            done_sense = true;
+        }
+    }
+    if (0 != host_status) {
+        if (leadin && (! done_leadin))
+            pr2ws("%s: ", leadin);
+        if (done_leadin)
+            pr2ws("plus...: ");
+        else
+            done_leadin = true;
+        sg_print_host_status(host_status);
+        pr2ws("\n");
+    }
+    if (0 != driver_status) {
+        if (done_sense &&
+            (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status)))
+            return 0;
+        if (leadin && (! done_leadin))
+            pr2ws("%s: ", leadin);
+        if (done_leadin)
+            pr2ws("plus...: ");
+        sg_print_driver_status(driver_status);
+        pr2ws("\n");
+        if (sense_buffer && (! done_sense) &&
+            (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status)))
+            sg_print_sense(0, sense_buffer, sb_len, raw_sinfo);
+    }
+    return 0;
+}
+
+#ifdef SG_IO
+
+bool
+sg_normalize_sense(const struct sg_io_hdr * hp,
+                   struct sg_scsi_sense_hdr * sshp)
+{
+    if ((NULL == hp) || (0 == hp->sb_len_wr)) {
+        if (sshp)
+            memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
+        return 0;
+    }
+    return sg_scsi_normalize_sense(hp->sbp, hp->sb_len_wr, sshp);
+}
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+   returns 0. */
+int
+sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp,
+                bool raw_sinfo)
+{
+    return sg_linux_sense_print(leadin, hp->status, hp->host_status,
+                                hp->driver_status, hp->sbp, hp->sb_len_wr,
+                                raw_sinfo);
+}
+#endif
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+   returns 0. */
+int
+sg_chk_n_print(const char * leadin, int masked_status, int host_status,
+               int driver_status, const unsigned char * sense_buffer,
+               int sb_len, bool raw_sinfo)
+{
+    int scsi_status = (masked_status << 1) & 0x7e;
+
+    return sg_linux_sense_print(leadin, scsi_status, host_status,
+                                driver_status, sense_buffer, sb_len,
+                                raw_sinfo);
+}
+
+#ifdef SG_IO
+int
+sg_err_category3(struct sg_io_hdr * hp)
+{
+    return sg_err_category_new(hp->status, hp->host_status,
+                               hp->driver_status, hp->sbp, hp->sb_len_wr);
+}
+#endif
+
+int
+sg_err_category(int masked_status, int host_status, int driver_status,
+                const unsigned char * sense_buffer, int sb_len)
+{
+    int scsi_status = (masked_status << 1) & 0x7e;
+
+    return sg_err_category_new(scsi_status, host_status, driver_status,
+                               sense_buffer, sb_len);
+}
+
+int
+sg_err_category_new(int scsi_status, int host_status, int driver_status,
+                    const unsigned char * sense_buffer, int sb_len)
+{
+    int masked_driver_status = (SG_LIB_DRIVER_MASK & driver_status);
+
+    scsi_status &= 0x7e;
+    if ((0 == scsi_status) && (0 == host_status) &&
+        (0 == masked_driver_status))
+        return SG_LIB_CAT_CLEAN;
+    if ((SAM_STAT_CHECK_CONDITION == scsi_status) ||
+        (SAM_STAT_COMMAND_TERMINATED == scsi_status) ||
+        (SG_LIB_DRIVER_SENSE == masked_driver_status))
+        return sg_err_category_sense(sense_buffer, sb_len);
+    if (0 != host_status) {
+        if ((SG_LIB_DID_NO_CONNECT == host_status) ||
+            (SG_LIB_DID_BUS_BUSY == host_status) ||
+            (SG_LIB_DID_TIME_OUT == host_status))
+            return SG_LIB_CAT_TIMEOUT;
+        if (SG_LIB_DID_NEXUS_FAILURE == host_status)
+            return SG_LIB_CAT_RES_CONFLICT;
+    }
+    if (SG_LIB_DRIVER_TIMEOUT == masked_driver_status)
+        return SG_LIB_CAT_TIMEOUT;
+    return SG_LIB_CAT_OTHER;
+}
+
+#endif  /* if SG_LIB_LINUX defined */
diff --git a/tools/sg_write_buffer/sg_lib.c b/tools/sg_write_buffer/sg_lib.c
new file mode 100644
index 0000000..53a17d4
--- /dev/null
+++ b/tools/sg_write_buffer/sg_lib.c
@@ -0,0 +1,3494 @@
+/*
+ * Copyright (c) 1999-2018 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.
+ */
+
+/* NOTICE:
+ *    On 5th October 2004 (v1.00) this file name was changed from sg_err.c
+ *    to sg_lib.c and the previous GPL was changed to a FreeBSD license.
+ *    The intention is to maintain this file and the related sg_lib.h file
+ *    as open source and encourage their unencumbered use.
+ *
+ * CONTRIBUTIONS:
+ *    This file started out as a copy of SCSI opcodes, sense keys and
+ *    additional sense codes (ASC/ASCQ) kept in the Linux SCSI subsystem
+ *    in the kernel source file: drivers/scsi/constant.c . That file
+ *    bore this notice: "Copyright (C) 1993, 1994, 1995 Eric Youngdale"
+ *    and a GPL notice.
+ *
+ *    Much of the data in this file is derived from SCSI draft standards
+ *    found at http://www.t10.org with the "SCSI Primary Commands-4" (SPC-4)
+ *    being the central point of reference.
+ *
+ *    Contributions:
+ *      sense key specific field decoding [Trent Piepho 20031116]
+ *
+ */
+
+#define _POSIX_C_SOURCE 200809L         /* for posix_memalign() */
+#define __STDC_FORMAT_MACROS 1
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */
+
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d  /* corresponding ASC is 0 */
+
+FILE * sg_warnings_strm = NULL;        /* would like to default to stderr */
+
+#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;
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+static int scnpr(char * cp, int cp_max_len, const char * fmt, ...)
+                 __attribute__ ((format (printf, 3, 4)));
+#else
+static int scnpr(char * cp, int cp_max_len, const char * fmt, ...);
+#endif
+
+/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of
+ * functions. Returns number of chars placed in cp excluding the
+ * trailing null char. So for cp_max_len > 0 the return value is always
+ * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are
+ * written to cp. Note this means that when cp_max_len = 1, this function
+ * assumes that cp[0] is the null character and does nothing (and returns
+ * 0). Linux kernel has a similar function called  scnprintf().  */
+static int
+scnpr(char * cp, int cp_max_len, const char * fmt, ...)
+{
+    va_list args;
+    int n;
+
+    if (cp_max_len < 2)
+        return 0;
+    va_start(args, fmt);
+    n = vsnprintf(cp, cp_max_len, fmt, args);
+    va_end(args);
+    return (n < cp_max_len) ? n : (cp_max_len - 1);
+}
+
+/* Simple ASCII printable (does not use locale), includes space and excludes
+ * DEL (0x7f). */
+static inline int my_isprint(int ch)
+{
+    return ((ch >= ' ') && (ch < 0x7f));
+}
+
+/* Searches 'arr' for match on 'value' then 'peri_type'. If matches
+   'value' but not 'peri_type' then yields first 'value' match entry.
+   Last element of 'arr' has NULL 'name'. If no match returns NULL. */
+static const struct sg_lib_value_name_t *
+get_value_name(const struct sg_lib_value_name_t * arr, int value,
+               int peri_type)
+{
+    const struct sg_lib_value_name_t * vp = arr;
+    const struct sg_lib_value_name_t * holdp;
+
+    if (peri_type < 0)
+        peri_type = 0;
+    for (; vp->name; ++vp) {
+        if (value == vp->value) {
+            if (peri_type == vp->peri_dev_type)
+                return vp;
+            holdp = vp;
+            while ((vp + 1)->name && (value == (vp + 1)->value)) {
+                ++vp;
+                if (peri_type == vp->peri_dev_type)
+                    return vp;
+            }
+            return holdp;
+        }
+    }
+    return NULL;
+}
+
+/* If this function is not called, sg_warnings_strm will be NULL and all users
+ * (mainly fprintf() ) need to check and substitute stderr as required */
+void
+sg_set_warnings_strm(FILE * warnings_strm)
+{
+    sg_warnings_strm = warnings_strm;
+}
+
+#define CMD_NAME_LEN 128
+
+void
+sg_print_command(const unsigned char * command)
+{
+    int k, sz;
+    char buff[CMD_NAME_LEN];
+
+    sg_get_command_name(command, 0, CMD_NAME_LEN, buff);
+    buff[CMD_NAME_LEN - 1] = '\0';
+
+    pr2ws("%s [", buff);
+    if (SG_VARIABLE_LENGTH_CMD == command[0])
+        sz = command[7] + 8;
+    else
+        sz = sg_get_command_size(command[0]);
+    for (k = 0; k < sz; ++k)
+        pr2ws("%02x ", command[k]);
+    pr2ws("]\n");
+}
+
+void
+sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff)
+{
+    const char * ccp = NULL;
+    bool unknown = false;
+
+    if ((NULL == buff) || (buff_len < 1))
+        return;
+    else if (1 ==  buff_len) {
+        buff[0] = '\0';
+        return;
+    }
+    scsi_status &= 0x7e; /* sanitize as much as possible */
+    switch (scsi_status) {
+        case 0: ccp = "Good"; break;
+        case 0x2: ccp = "Check Condition"; break;
+        case 0x4: ccp = "Condition Met"; break;
+        case 0x8: ccp = "Busy"; break;
+        case 0x10: ccp = "Intermediate (obsolete)"; break;
+        case 0x14: ccp = "Intermediate-Condition Met (obsolete)"; break;
+        case 0x18: ccp = "Reservation Conflict"; break;
+        case 0x22: ccp = "Command Terminated (obsolete)"; break;
+        case 0x28: ccp = "Task set Full"; break;
+        case 0x30: ccp = "ACA Active"; break;
+        case 0x40: ccp = "Task Aborted"; break;
+        default:
+            unknown = true;
+            break;
+    }
+    if (unknown)
+        scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status);
+    else
+        scnpr(buff, buff_len, "%s", ccp);
+}
+
+void
+sg_print_scsi_status(int scsi_status)
+{
+    char buff[128];
+
+    sg_get_scsi_status_str(scsi_status, sizeof(buff) - 1, buff);
+    buff[sizeof(buff) - 1] = '\0';
+    pr2ws("%s ", buff);
+}
+
+/* Get sense key from sense buffer. If successful returns a sense key value
+ * between 0 and 15. If sense buffer cannot be decode, returns -1 . */
+int
+sg_get_sense_key(const unsigned char * sbp, int sb_len)
+{
+    if ((NULL == sbp) || (sb_len < 2))
+        return -1;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        return (sb_len < 3) ? -1 : (sbp[2] & 0xf);
+    case 0x72:
+    case 0x73:
+        return sbp[1] & 0xf;
+    default:
+        return -1;
+    }
+}
+
+/* Yield string associated with sense_key value. Returns 'buff'. */
+char *
+sg_get_sense_key_str(int sense_key, int buff_len, char * buff)
+{
+    if (1 == buff_len) {
+        buff[0] = '\0';
+        return buff;
+    }
+    if ((sense_key >= 0) && (sense_key < 16))
+         scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]);
+    else
+         scnpr(buff, buff_len, "invalid value: 0x%x", sense_key);
+    return buff;
+}
+
+/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
+char *
+sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff)
+{
+    int k, num, rlen;
+    bool found = false;
+    struct sg_lib_asc_ascq_t * eip;
+    struct sg_lib_asc_ascq_range_t * ei2p;
+
+    if (1 == buff_len) {
+        buff[0] = '\0';
+        return buff;
+    }
+    for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) {
+        ei2p = &sg_lib_asc_ascq_range[k];
+        if ((ei2p->asc == asc) &&
+            (ascq >= ei2p->ascq_min)  &&
+            (ascq <= ei2p->ascq_max)) {
+            found = true;
+            num = scnpr(buff, buff_len, "Additional sense: ");
+            rlen = buff_len - num;
+            scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq);
+        }
+    }
+    if (found)
+        return buff;
+
+    for (k = 0; sg_lib_asc_ascq[k].text; ++k) {
+        eip = &sg_lib_asc_ascq[k];
+        if (eip->asc == asc &&
+            eip->ascq == ascq) {
+            found = true;
+            scnpr(buff, buff_len, "Additional sense: %s", eip->text);
+        }
+    }
+    if (! found) {
+        if (asc >= 0x80)
+            scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x "
+                  "(hex)", asc, ascq);
+        else if (ascq >= 0x80)
+            scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification "
+                  "ASCQ=%02x (hex)", asc, ascq);
+        else
+            scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq);
+    }
+    return buff;
+}
+
+/* Attempt to find the first SCSI sense data descriptor that matches the
+ * given 'desc_type'. If found return pointer to start of sense data
+ * descriptor; otherwise (including fixed format sense data) returns NULL. */
+const unsigned char *
+sg_scsi_sense_desc_find(const unsigned char * sbp, int sb_len,
+                        int desc_type)
+{
+    int add_sb_len, add_d_len, desc_len, k;
+    const unsigned char * descp;
+
+    if ((sb_len < 8) || (0 == (add_sb_len = sbp[7])))
+        return NULL;
+    if ((sbp[0] < 0x72) || (sbp[0] > 0x73))
+        return NULL;
+    add_sb_len = (add_sb_len < (sb_len - 8)) ?  add_sb_len : (sb_len - 8);
+    descp = &sbp[8];
+    for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) {
+        descp += desc_len;
+        add_d_len = (k < (add_sb_len - 1)) ? descp[1]: -1;
+        desc_len = add_d_len + 2;
+        if (descp[0] == desc_type)
+            return descp;
+        if (add_d_len < 0) /* short descriptor ?? */
+            break;
+    }
+    return NULL;
+}
+
+/* Returns true if valid bit set, false if valid bit clear. Irrespective the
+ * information field is written out via 'info_outp' (except when it is
+ * NULL). Handles both fixed and descriptor sense formats. */
+bool
+sg_get_sense_info_fld(const unsigned char * sbp, int sb_len,
+                      uint64_t * info_outp)
+{
+    const unsigned char * bp;
+    uint64_t ull;
+
+    if (info_outp)
+        *info_outp = 0;
+    if (sb_len < 7)
+        return false;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        if (info_outp)
+            *info_outp = sg_get_unaligned_be32(sbp + 3);
+        return !!(sbp[0] & 0x80);
+    case 0x72:
+    case 0x73:
+        bp = sg_scsi_sense_desc_find(sbp, sb_len, 0 /* info desc */);
+        if (bp && (0xa == bp[1])) {
+            ull = sg_get_unaligned_be64(bp + 4);
+            if (info_outp)
+                *info_outp = ull;
+            return !!(bp[2] & 0x80);   /* since spc3r23 should be set */
+        } else
+            return false;
+    default:
+        return false;
+    }
+}
+
+/* Returns true if fixed format or command specific information descriptor
+ * is found in the descriptor sense; else false. If available the command
+ * specific information field (4 byte integer in fixed format, 8 byte
+ * integer in descriptor format) is written out via 'cmd_spec_outp'.
+ * Handles both fixed and descriptor sense formats. */
+bool
+sg_get_sense_cmd_spec_fld(const unsigned char * sbp, int sb_len,
+                          uint64_t * cmd_spec_outp)
+{
+    const unsigned char * bp;
+
+    if (cmd_spec_outp)
+        *cmd_spec_outp = 0;
+    if (sb_len < 7)
+        return false;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        if (cmd_spec_outp)
+            *cmd_spec_outp = sg_get_unaligned_be32(sbp + 8);
+        return true;
+    case 0x72:
+    case 0x73:
+        bp = sg_scsi_sense_desc_find(sbp, sb_len,
+                                     1 /* command specific info desc */);
+        if (bp && (0xa == bp[1])) {
+            if (cmd_spec_outp)
+                *cmd_spec_outp = sg_get_unaligned_be64(bp + 4);
+            return true;
+        } else
+            return false;
+    default:
+        return false;
+    }
+}
+
+/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set.
+ * In descriptor format if the stream commands descriptor not found
+ * then returns false. Writes true or false corresponding to these bits to
+ * the last three arguments if they are non-NULL. */
+bool
+sg_get_sense_filemark_eom_ili(const unsigned char * sbp, int sb_len,
+                              bool * filemark_p, bool * eom_p, bool * ili_p)
+{
+    const unsigned char * bp;
+
+    if (sb_len < 7)
+        return false;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        if (sbp[2] & 0xe0) {
+            if (filemark_p)
+                *filemark_p = !!(sbp[2] & 0x80);
+            if (eom_p)
+                *eom_p = !!(sbp[2] & 0x40);
+            if (ili_p)
+                *ili_p = !!(sbp[2] & 0x20);
+            return true;
+        } else
+            return false;
+    case 0x72:
+    case 0x73:
+       /* Look for stream commands sense data descriptor */
+        bp = sg_scsi_sense_desc_find(sbp, sb_len, 4);
+        if (bp && (bp[1] >= 2)) {
+            if (bp[3] & 0xe0) {
+                if (filemark_p)
+                    *filemark_p = !!(bp[3] & 0x80);
+                if (eom_p)
+                    *eom_p = !!(bp[3] & 0x40);
+                if (ili_p)
+                    *ili_p = !!(bp[3] & 0x20);
+                return true;
+            }
+        }
+        return false;
+    default:
+        return false;
+    }
+}
+
+/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also
+ * returns true if progress indication sense data descriptor found. Places
+ * progress field from sense data where progress_outp points. If progress
+ * field is not available returns false and *progress_outp is unaltered.
+ * Handles both fixed and descriptor sense formats.
+ * Hint: if true is returned *progress_outp may be multiplied by 100 then
+ * divided by 65536 to get the percentage completion. */
+bool
+sg_get_sense_progress_fld(const unsigned char * sbp, int sb_len,
+                          int * progress_outp)
+{
+    const unsigned char * bp;
+    int sk, sk_pr;
+
+    if (sb_len < 7)
+        return false;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        sk = (sbp[2] & 0xf);
+        if ((sb_len < 18) ||
+            ((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk)))
+            return false;
+        if (sbp[15] & 0x80) {        /* SKSV bit set */
+            if (progress_outp)
+                *progress_outp = sg_get_unaligned_be16(sbp + 16);
+            return true;
+        } else
+            return false;
+    case 0x72:
+    case 0x73:
+        /* sense key specific progress (0x2) or progress descriptor (0xa) */
+        sk = (sbp[1] & 0xf);
+        sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk);
+        if (sk_pr && ((bp = sg_scsi_sense_desc_find(sbp, sb_len, 2))) &&
+            (0x6 == bp[1]) && (0x80 & bp[4])) {
+            if (progress_outp)
+                *progress_outp = sg_get_unaligned_be16(bp + 5);
+            return true;
+        } else if (((bp = sg_scsi_sense_desc_find(sbp, sb_len, 0xa))) &&
+                   ((0x6 == bp[1]))) {
+            if (progress_outp)
+                *progress_outp = sg_get_unaligned_be16(bp + 6);
+            return true;
+        } else
+            return false;
+    default:
+        return false;
+    }
+}
+
+char *
+sg_get_pdt_str(int pdt, int buff_len, char * buff)
+{
+    if ((pdt < 0) || (pdt > 31))
+        scnpr(buff, buff_len, "bad pdt");
+    else
+        scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]);
+    return buff;
+}
+
+int
+sg_lib_pdt_decay(int pdt)
+{
+    if ((pdt < 0) || (pdt > 31))
+        return 0;
+    return sg_lib_pdt_decay_arr[pdt];
+}
+
+char *
+sg_get_trans_proto_str(int tpi, int buff_len, char * buff)
+{
+    if ((tpi < 0) || (tpi > 15))
+        scnpr(buff, buff_len, "bad tpi");
+    else
+        scnpr(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]);
+    return buff;
+}
+
+#define TRANSPORT_ID_MIN_LEN 24
+
+char *
+sg_decode_transportid_str(const char * lip, unsigned char * bp, int bplen,
+                          bool only_one, int blen, char * b)
+{
+    int proto_id, num, k, n, normal_len, tpid_format;
+    uint64_t ull;
+    int bump;
+
+    if ((NULL == b) || (blen < 1))
+        return b;
+    else if (1 == blen) {
+        b[0] = '\0';
+        return b;
+    }
+    if (NULL == lip)
+        lip = "";
+    bump = TRANSPORT_ID_MIN_LEN; /* should be overwritten in all loop paths */
+    for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) {
+        if ((k > 0) && only_one)
+            break;
+        if ((bplen < 24) || (0 != (bplen % 4)))
+            n += scnpr(b + n, blen - n, "%sTransport Id short or not "
+                       "multiple of 4 [length=%d]:\n", lip, blen);
+        else
+            n += scnpr(b + n, blen - n, "%sTransport Id of initiator:\n",
+                       lip);
+        tpid_format = ((bp[0] >> 6) & 0x3);
+        proto_id = (bp[0] & 0xf);
+        normal_len = (bplen > TRANSPORT_ID_MIN_LEN) ?
+                                TRANSPORT_ID_MIN_LEN : bplen;
+        switch (proto_id) {
+        case TPROTO_FCP: /* Fibre channel */
+            n += scnpr(b + n, blen - n, "%s  FCP-2 World Wide Name:\n", lip);
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            n += hex2str(bp + 8, 8, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_SPI:        /* Scsi Parallel Interface, obsolete */
+            n += scnpr(b + n, blen - n, "%s  Parallel SCSI initiator SCSI "
+                       "address: 0x%x\n", lip, sg_get_unaligned_be16(bp + 2));
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            n += scnpr(b + n, blen - n, "%s  relative port number (of "
+                       "corresponding target): 0x%x\n", lip,
+                       sg_get_unaligned_be16(bp + 6));
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_SSA:
+            n += scnpr(b + n, blen - n, "%s  SSA (transport id not "
+                       "defined):\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_1394: /* IEEE 1394 */
+            n += scnpr(b + n, blen - n, "%s  IEEE 1394 EUI-64 name:\n", lip);
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            n += hex2str(&bp[8], 8, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_SRP:        /* SCSI over RDMA */
+            n += scnpr(b + n, blen - n, "%s  RDMA initiator port "
+                       "identifier:\n", lip);
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            n += hex2str(bp + 8, 16, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_ISCSI:
+            n += scnpr(b + n, blen - n, "%s  iSCSI ", lip);
+            num = sg_get_unaligned_be16(bp + 2);
+            if (0 == tpid_format)
+                n += scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]);
+            else if (1 == tpid_format)
+                n += scnpr(b + n, blen - n, "world wide unique port id: "
+                           "%.*s\n", num, &bp[4]);
+            else {
+                n += scnpr(b + n, blen - n, "  [Unexpected TPID format: "
+                           "%d]\n", tpid_format);
+                n += hex2str(bp, num + 4, lip, 0, blen - n, b + n);
+            }
+            bump = (((num + 4) < TRANSPORT_ID_MIN_LEN) ?
+                         TRANSPORT_ID_MIN_LEN : num + 4);
+            break;
+        case TPROTO_SAS:
+            ull = sg_get_unaligned_be64(bp + 4);
+            n += scnpr(b + n, blen - n, "%s  SAS address: 0x%" PRIx64 "\n",
+                       lip, ull);
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_ADT:        /* no TransportID defined by T10 yet */
+            n += scnpr(b + n, blen - n, "%s  ADT:\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_ATA:        /* no TransportID defined by T10 yet */
+            n += scnpr(b + n, blen - n, "%s  ATAPI:\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_UAS:        /* no TransportID defined by T10 yet */
+            n += scnpr(b + n, blen - n, "%s  UAS:\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_SOP:
+            n += scnpr(b + n, blen - n, "%s  SOP ", lip);
+            num = sg_get_unaligned_be16(bp + 2);
+            if (0 == tpid_format)
+                n += scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num);
+            else {
+                n += scnpr(b + n, blen - n, "  [Unexpected TPID format: "
+                           "%d]\n", tpid_format);
+                n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            }
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_PCIE:       /* no TransportID defined by T10 yet */
+            n += scnpr(b + n, blen - n, "%s  PCIE:\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_NONE:       /* no TransportID defined by T10 */
+            n += scnpr(b + n, blen - n, "%s  No specified protocol\n", lip);
+            /* n += hex2str(bp, ((bplen > 24) ? 24 : bplen),
+             *                 lip, 0, blen - n, b + n); */
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        default:
+            n += scnpr(b + n, blen - n, "%s  unknown protocol id=0x%x  "
+                       "TPID format=%d\n", lip, proto_id, tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        }
+    }
+    return b;
+}
+
+
+static const char * desig_code_set_str_arr[] =
+{
+    "Reserved [0x0]",
+    "Binary",
+    "ASCII",
+    "UTF-8",
+    "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]",
+    "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]",
+    "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
+};
+
+const char *
+sg_get_desig_code_set_str(int val)
+{
+    if ((val >= 0) && (val < 16))
+        return desig_code_set_str_arr[val];
+    else
+        return NULL;
+}
+
+static const char * desig_assoc_str_arr[] =
+{
+    "Addressed logical unit",
+    "Target port",      /* that received request; unless SCSI ports VPD */
+    "Target device that contains addressed lu",
+    "Reserved [0x3]",
+};
+
+const char *
+sg_get_desig_assoc_str(int val)
+{
+    if ((val >= 0) && (val < 4))
+        return desig_assoc_str_arr[val];
+    else
+        return NULL;
+}
+
+static const char * desig_type_str_arr[] =
+{
+    "vendor specific [0x0]",
+    "T10 vendor identification",
+    "EUI-64 based",
+    "NAA",
+    "Relative target port",
+    "Target port group",        /* spc4r09: _primary_ target port group */
+    "Logical unit group",
+    "MD5 logical unit identifier",
+    "SCSI name string",
+    "Protocol specific port identifier",        /* spc4r36 */
+    "UUID identifier",          /* spc5r08 */
+    "Reserved [0xb]",
+    "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
+};
+
+const char *
+sg_get_desig_type_str(int val)
+{
+    if ((val >= 0) && (val < 16))
+        return desig_type_str_arr[val];
+    else
+        return NULL;
+}
+
+int
+sg_get_designation_descriptor_str(const char * lip, const unsigned char * ddp,
+                                  int dd_len, bool print_assoc, bool do_long,
+                                  int blen, char * b)
+{
+    int m, p_id, piv, c_set, assoc, desig_type, ci_off, c_id, d_id, naa;
+    int vsi, k, n, dlen;
+    const unsigned char * ip;
+    uint64_t vsei;
+    uint64_t id_ext;
+    char e[64];
+    const char * cp;
+
+    n = 0;
+    if (NULL == lip)
+        lip = "";
+    if (dd_len < 4) {
+        n += scnpr(b + n, blen - n, "%sdesignator desc too short: got "
+                   "length of %d want 4 or more\n", lip, dd_len);
+        return n;
+    }
+    dlen = ddp[3];
+    if (dlen > (dd_len - 4)) {
+        n += scnpr(b + n, blen - n, "%sdesignator too long: says it is %d "
+                   "bytes, but given %d bytes\n", lip, dlen, dd_len - 4);
+        return n;
+    }
+    ip = ddp + 4;
+    p_id = ((ddp[0] >> 4) & 0xf);
+    c_set = (ddp[0] & 0xf);
+    piv = ((ddp[1] & 0x80) ? 1 : 0);
+    assoc = ((ddp[1] >> 4) & 0x3);
+    desig_type = (ddp[1] & 0xf);
+    if (print_assoc && ((cp = sg_get_desig_assoc_str(assoc))))
+        n += scnpr(b + n, blen - n, "%s  %s:\n", lip, cp);
+    n += scnpr(b + n, blen - n, "%s    designator type: ", lip);
+    cp = sg_get_desig_type_str(desig_type);
+    if (cp)
+        n += scnpr(b + n, blen - n, "%s", cp);
+    n += scnpr(b + n, blen - n, ",  code set: ");
+    cp = sg_get_desig_code_set_str(c_set);
+    if (cp)
+        n += scnpr(b + n, blen - n, "%s", cp);
+    n += scnpr(b + n, blen - n, "\n");
+    if (piv && ((1 == assoc) || (2 == assoc)))
+        n += scnpr(b + n, blen - n, "%s     transport: %s\n", lip,
+                   sg_get_trans_proto_str(p_id, sizeof(e), e));
+    /* printf("    associated with the %s\n", sdparm_assoc_arr[assoc]); */
+    switch (desig_type) {
+    case 0: /* vendor specific */
+        k = 0;
+        if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */
+            for (k = 0; (k < dlen) && my_isprint(ip[k]); ++k)
+                ;
+            if (k >= dlen)
+                k = 1;
+        }
+        if (k)
+            n += scnpr(b + n, blen - n, "%s      vendor specific: %.*s\n",
+                       lip, dlen, ip);
+        else {
+            n += scnpr(b + n, blen - n, "%s      vendor specific:\n", lip);
+            n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+        }
+        break;
+    case 1: /* T10 vendor identification */
+        n += scnpr(b + n, blen - n, "%s      vendor id: %.8s\n", lip, ip);
+        if (dlen > 8) {
+            if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+                n += scnpr(b + n, blen - n, "%s      vendor specific: "
+                           "%.*s\n", lip, dlen - 8, ip + 8);
+            } else {
+                n += scnpr(b + n, blen - n, "%s      vendor specific: 0x",
+                           lip);
+                for (m = 8; m < dlen; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "\n");
+            }
+        }
+        break;
+    case 2: /* EUI-64 based */
+        if (! do_long) {
+            if ((8 != dlen) && (12 != dlen) && (16 != dlen)) {
+                n += scnpr(b + n, blen - n, "%s      << expect 8, 12 and 16 "
+                           "byte EUI, got %d >>\n", lip, dlen);
+                 n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            n += scnpr(b + n, blen - n, "%s      0x", lip);
+            for (m = 0; m < dlen; ++m)
+                n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+            n += scnpr(b + n, blen - n, "\n");
+            break;
+        }
+        n += scnpr(b + n, blen - n, "%s      EUI-64 based %d byte "
+                   "identifier\n", lip, dlen);
+        if (1 != c_set) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary code_set "
+                       "(1) >>\n", lip);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        ci_off = 0;
+        if (16 == dlen) {
+            ci_off = 8;
+            id_ext = sg_get_unaligned_be64(ip);
+            n += scnpr(b + n, blen - n, "%s      Identifier extension: 0x%"
+                       PRIx64 "\n", lip, id_ext);
+        } else if ((8 != dlen) && (12 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << can only decode 8, 12 "
+                       "and 16 byte ids >>\n", lip);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        c_id = sg_get_unaligned_be24(ip + ci_off);
+        n += scnpr(b + n, blen - n, "%s      IEEE Company_id: 0x%x\n", lip,
+                   c_id);
+        vsei = 0;
+        for (m = 0; m < 5; ++m) {
+            if (m > 0)
+                vsei <<= 8;
+            vsei |= ip[ci_off + 3 + m];
+        }
+        n += scnpr(b + n, blen - n, "%s      Vendor Specific Extension "
+                   "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+        if (12 == dlen) {
+            d_id = sg_get_unaligned_be32(ip + 8);
+            n += scnpr(b + n, blen - n, "%s      Directory ID: 0x%x\n", lip,
+                       d_id);
+        }
+        break;
+    case 3: /* NAA <n> */
+        if (1 != c_set) {
+            n += scnpr(b + n, blen - n, "%s      << unexpected code set %d "
+                       "for NAA >>\n", lip, c_set);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        naa = (ip[0] >> 4) & 0xff;
+        switch (naa) {
+        case 2:         /* NAA 2: IEEE Extended */
+            if (8 != dlen) {
+                n += scnpr(b + n, blen - n, "%s      << unexpected NAA 2 "
+                           "identifier length: 0x%x >>\n", lip, dlen);
+                n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            d_id = (((ip[0] & 0xf) << 8) | ip[1]);
+            c_id = sg_get_unaligned_be24(ip + 2);
+            vsi = sg_get_unaligned_be24(ip + 5);
+            if (do_long) {
+                n += scnpr(b + n, blen - n, "%s      NAA 2, vendor specific "
+                           "identifier A: 0x%x\n", lip, d_id);
+                n += scnpr(b + n, blen - n, "%s      IEEE Company_id: 0x%x\n",
+                           lip, c_id);
+                n += scnpr(b + n, blen - n, "%s      vendor specific "
+                           "identifier B: 0x%x\n", lip, vsi);
+                n += scnpr(b + n, blen - n, "%s      [0x", lip);
+                for (m = 0; m < 8; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "]\n");
+            }
+            n += scnpr(b + n, blen - n, "%s      0x", lip);
+            for (m = 0; m < 8; ++m)
+                n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+            n += scnpr(b + n, blen - n, "\n");
+            break;
+        case 3:         /* NAA 3: Locally assigned */
+            if (8 != dlen) {
+                n += scnpr(b + n, blen - n, "%s      << unexpected NAA 3 "
+                           "identifier length: 0x%x >>\n", lip, dlen);
+                n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            if (do_long)
+                n += scnpr(b + n, blen - n, "%s      NAA 3, Locally "
+                           "assigned:\n", lip);
+            n += scnpr(b + n, blen - n, "%s      0x", lip);
+            for (m = 0; m < 8; ++m)
+                n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+            n += scnpr(b + n, blen - n, "\n");
+            break;
+        case 5:         /* NAA 5: IEEE Registered */
+            if (8 != dlen) {
+                n += scnpr(b + n, blen - n, "%s      << unexpected NAA 5 "
+                           "identifier length: 0x%x >>\n", lip, dlen);
+                n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) |
+                    (ip[2] << 4) | ((ip[3] & 0xf0) >> 4));
+            vsei = ip[3] & 0xf;
+            for (m = 1; m < 5; ++m) {
+                vsei <<= 8;
+                vsei |= ip[3 + m];
+            }
+            if (do_long) {
+                n += scnpr(b + n, blen - n, "%s      NAA 5, IEEE "
+                           "Company_id: 0x%x\n", lip, c_id);
+                n += scnpr(b + n, blen - n, "%s      Vendor Specific "
+                           "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+                n += scnpr(b + n, blen - n, "%s      [0x", lip);
+                for (m = 0; m < 8; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "]\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s      0x", lip);
+                for (m = 0; m < 8; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "\n");
+            }
+            break;
+        case 6:         /* NAA 6: IEEE Registered extended */
+            if (16 != dlen) {
+                n += scnpr(b + n, blen - n, "%s      << unexpected NAA 6 "
+                           "identifier length: 0x%x >>\n", lip, dlen);
+                n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) |
+                    (ip[2] << 4) | ((ip[3] & 0xf0) >> 4));
+            vsei = ip[3] & 0xf;
+            for (m = 1; m < 5; ++m) {
+                vsei <<= 8;
+                vsei |= ip[3 + m];
+            }
+            if (do_long) {
+                n += scnpr(b + n, blen - n, "%s      NAA 6, IEEE "
+                           "Company_id: 0x%x\n", lip, c_id);
+                n += scnpr(b + n, blen - n, "%s      Vendor Specific "
+                           "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+                vsei = sg_get_unaligned_be64(ip + 8);
+                n += scnpr(b + n, blen - n, "%s      Vendor Specific "
+                           "Identifier Extension: 0x%" PRIx64 "\n", lip,
+                                 vsei);
+                n += scnpr(b + n, blen - n, "%s      [0x", lip);
+                for (m = 0; m < 16; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "]\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s      0x", lip);
+                for (m = 0; m < 16; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "\n");
+            }
+            break;
+        default:
+            n += scnpr(b + n, blen - n, "%s      << unexpected NAA [0x%x] "
+                       ">>\n", lip, naa);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        break;
+    case 4: /* Relative target port */
+        if ((1 != c_set) || (1 != assoc) || (4 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set, target port association, length 4 >>\n",
+                       lip);
+            n += hex2str(ip, dlen, "", 1, blen - n, b + n);
+            break;
+        }
+        d_id = sg_get_unaligned_be16(ip + 2);
+        n += scnpr(b + n, blen - n, "%s      Relative target port: 0x%x\n",
+                   lip, d_id);
+        break;
+    case 5: /* (primary) Target port group */
+        if ((1 != c_set) || (1 != assoc) || (4 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set, target port association, length 4 >>\n",
+                       lip);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        d_id = sg_get_unaligned_be16(ip + 2);
+        n += scnpr(b + n, blen - n, "%s      Target port group: 0x%x\n", lip,
+                   d_id);
+        break;
+    case 6: /* Logical unit group */
+        if ((1 != c_set) || (0 != assoc) || (4 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set, logical unit association, length 4 >>\n",
+                       lip);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        d_id = sg_get_unaligned_be16(ip + 2);
+        n += scnpr(b + n, blen - n, "%s      Logical unit group: 0x%x\n", lip,
+                   d_id);
+        break;
+    case 7: /* MD5 logical unit identifier */
+        if ((1 != c_set) || (0 != assoc)) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set, logical unit association >>\n", lip);
+            n += hex2str(ip, dlen, "", 1, blen - n, b + n);
+            break;
+        }
+        n += scnpr(b + n, blen - n, "%s      MD5 logical unit identifier:\n",
+                   lip);
+        n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+        break;
+    case 8: /* SCSI name string */
+        if (3 != c_set) {       /* accept ASCII as subset of UTF-8 */
+            if (2 == c_set) {
+                if (do_long)
+                    n += scnpr(b + n, blen - n, "%s      << expected UTF-8, "
+                               "use ASCII >>\n", lip);
+            } else {
+                n += scnpr(b + n, blen - n, "%s      << expected UTF-8 "
+                           "code_set >>\n", lip);
+                n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+                break;
+            }
+        }
+        n += scnpr(b + n, blen - n, "%s      SCSI name string:\n", lip);
+        /* does %s print out UTF-8 ok??
+         * Seems to depend on the locale. Looks ok here with my
+         * locale setting: en_AU.UTF-8
+         */
+        n += scnpr(b + n, blen - n, "%s      %.*s\n", lip, dlen,
+                   (const char *)ip);
+        break;
+    case 9: /* Protocol specific port identifier */
+        /* added in spc4r36, PIV must be set, proto_id indicates */
+        /* whether UAS (USB) or SOP (PCIe) or ... */
+        if (! piv)
+            n += scnpr(b + n, blen - n, " %s      >>>> Protocol specific "
+                       "port identifier expects protocol\n"
+                       "%s           identifier to be valid and it is not\n",
+                       lip, lip);
+        if (TPROTO_UAS == p_id) {
+            n += scnpr(b + n, blen - n, "%s      USB device address: 0x%x\n",
+                       lip, 0x7f & ip[0]);
+            n += scnpr(b + n, blen - n, "%s      USB interface number: "
+                       "0x%x\n", lip, ip[2]);
+        } else if (TPROTO_SOP == p_id) {
+            n += scnpr(b + n, blen - n, "%s      PCIe routing ID, bus "
+                       "number: 0x%x\n", lip, ip[0]);
+            n += scnpr(b + n, blen - n, "%s          function number: 0x%x\n",
+                       lip, ip[1]);
+            n += scnpr(b + n, blen - n, "%s          [or device number: "
+                       "0x%x, function number: 0x%x]\n", lip,
+                       (0x1f & (ip[1] >> 3)), 0x7 & ip[1]);
+        } else
+            n += scnpr(b + n, blen - n, "%s      >>>> unexpected protocol "
+                       "indentifier: %s\n%s           with Protocol specific "
+                       "port identifier\n", lip,
+                       sg_get_trans_proto_str(p_id, sizeof(e), e), lip);
+        break;
+    case 0xa: /* UUID identifier */
+        if (1 != c_set) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set >>\n", lip);
+            n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+            break;
+        }
+        if ((1 != ((ip[0] >> 4) & 0xf)) || (18 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << expected locally "
+                       "assigned UUID, 16 bytes long >>\n", lip);
+            n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+            break;
+        }
+        n += scnpr(b + n, blen - n, "%s      Locally assigned UUID: ", lip);
+        for (m = 0; m < 16; ++m) {
+            if ((4 == m) || (6 == m) || (8 == m) || (10 == m))
+                n += scnpr(b + n, blen - n, "-");
+            n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]);
+        }
+        n += scnpr(b + n, blen - n, "\n");
+        if (do_long) {
+            n += scnpr(b + n, blen - n, "%s      [0x", lip);
+            for (m = 0; m < 16; ++m)
+                n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]);
+            n += scnpr(b + n, blen - n, "]\n");
+        }
+        break;
+    default: /* reserved */
+        n += scnpr(b + n, blen - n, "%s      reserved designator=0x%x\n", lip,
+                   desig_type);
+        n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+        break;
+    }
+    return n;
+}
+
+static int
+decode_sks(const char * lip, const unsigned char * descp, int add_d_len,
+           int sense_key, bool * processedp, int blen, char * b)
+{
+    int progress, pr, rem, n;
+
+    n = 0;
+    if (NULL == lip)
+        lip = "";
+    switch (sense_key) {
+    case SPC_SK_ILLEGAL_REQUEST:
+        if (add_d_len < 6) {
+            n += scnpr(b + n, blen - n, "Field pointer: ");
+            goto too_short;
+        }
+        /* abbreviate to fit on one line */
+        n += scnpr(b + n, blen - n, "Field pointer:\n");
+        n += scnpr(b + n, blen - n, "%s        Error in %s: byte %d", lip,
+                   (descp[4] & 0x40) ? "Command" :
+                                                  "Data parameters",
+                         sg_get_unaligned_be16(descp + 5));
+        if (descp[4] & 0x08) {
+            n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
+        } else
+            n += scnpr(b + n, blen - n, "\n");
+        break;
+    case SPC_SK_HARDWARE_ERROR:
+    case SPC_SK_MEDIUM_ERROR:
+    case SPC_SK_RECOVERED_ERROR:
+        n += scnpr(b + n, blen - n, "Actual retry count: ");
+        if (add_d_len < 6)
+            goto too_short;
+        n += scnpr(b + n, blen - n,"%u\n", sg_get_unaligned_be16(descp + 5));
+        break;
+    case SPC_SK_NO_SENSE:
+    case SPC_SK_NOT_READY:
+        n += scnpr(b + n, blen - n, "Progress indication: ");
+        if (add_d_len < 6)
+            goto too_short;
+        progress = sg_get_unaligned_be16(descp + 5);
+        pr = (progress * 100) / 65536;
+        rem = ((progress * 100) % 65536) / 656;
+        n += scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem);
+        break;
+    case SPC_SK_COPY_ABORTED:
+        n += scnpr(b + n, blen - n, "Segment pointer:\n");
+        if (add_d_len < 6)
+            goto too_short;
+        n += scnpr(b + n, blen - n, "%s        Relative to start of %s, byte "
+                   "%d", lip, (descp[4] & 0x20) ? "segment descriptor" :
+                                                  "parameter list",
+                   sg_get_unaligned_be16(descp + 5));
+        if (descp[4] & 0x08)
+            n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
+        else
+            n += scnpr(b + n, blen - n, "\n");
+        break;
+    case SPC_SK_UNIT_ATTENTION:
+        n += scnpr(b + n, blen - n, "Unit attention condition queue:\n");
+        n += scnpr(b + n, blen - n, "%s        overflow flag is %d\n", lip,
+                   !!(descp[4] & 0x1));
+        break;
+    default:
+        n += scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n",
+                   sense_key);
+        *processedp = false;
+        break;
+    }
+    return n;
+
+too_short:
+    n += scnpr(b + n, blen - n, "%s\n", "   >> descriptor too short");
+    *processedp = false;
+    return n;
+}
+
+#define TPGS_STATE_OPTIMIZED 0x0
+#define TPGS_STATE_NONOPTIMIZED 0x1
+#define TPGS_STATE_STANDBY 0x2
+#define TPGS_STATE_UNAVAILABLE 0x3
+#define TPGS_STATE_OFFLINE 0xe
+#define TPGS_STATE_TRANSITIONING 0xf
+
+static int
+decode_tpgs_state(int st, char * b, int blen)
+{
+    switch (st) {
+    case TPGS_STATE_OPTIMIZED:
+        return scnpr(b, blen, "active/optimized");
+    case TPGS_STATE_NONOPTIMIZED:
+        return scnpr(b, blen, "active/non optimized");
+    case TPGS_STATE_STANDBY:
+        return scnpr(b, blen, "standby");
+    case TPGS_STATE_UNAVAILABLE:
+        return scnpr(b, blen, "unavailable");
+    case TPGS_STATE_OFFLINE:
+        return scnpr(b, blen, "offline");
+    case TPGS_STATE_TRANSITIONING:
+        return scnpr(b, blen, "transitioning between states");
+    default:
+        return scnpr(b, blen, "unknown: 0x%x", st);
+    }
+}
+
+static int
+uds_referral_descriptor_str(char * b, int blen, const unsigned char * dp,
+                            int alen, const char * lip)
+{
+    int n = 0;
+    int dlen = alen - 2;
+    int k, j, g, f, tpgd;
+    const unsigned char * tp;
+    uint64_t ull;
+    char c[40];
+
+    if (NULL == lip)
+        lip = "";
+    n += scnpr(b + n, blen - n, "%s   Not all referrals: %d\n", lip,
+               !!(dp[2] & 0x1));
+    dp += 4;
+    for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) {
+        tpgd = dp[3];
+        g = (tpgd * 4) + 20;
+        n += scnpr(b + n, blen - n, "%s    Descriptor %d\n", lip, f);
+        if ((k + g) > dlen) {
+            n += scnpr(b + n, blen - n, "%s      truncated descriptor, "
+                       "stop\n", lip);
+            return n;
+        }
+        ull = sg_get_unaligned_be64(dp + 4);
+        n += scnpr(b + n, blen - n, "%s      first uds LBA: 0x%" PRIx64 "\n",
+                   lip, ull);
+        ull = sg_get_unaligned_be64(dp + 12);
+        n += scnpr(b + n, blen - n, "%s      last uds LBA:  0x%" PRIx64 "\n",
+                   lip, ull);
+        for (j = 0; j < tpgd; ++j) {
+            tp = dp + 20 + (j * 4);
+            decode_tpgs_state(tp[0] & 0xf, c, sizeof(c));
+            n += scnpr(b + n, blen - n, "%s        tpg: %d  state: %s\n",
+                       lip, sg_get_unaligned_be16(tp + 2), c);
+        }
+    }
+    return n;
+}
+
+static const char * dd_usage_reason_str_arr[] = {
+    "Unknown",
+    "resend this and further commands to:",
+    "resend this command to:",
+    "new subsiduary lu added to this administrative lu:",
+    "administrative lu associated with a preferred binding:",
+   };
+
+
+/* Decode descriptor format sense descriptors (assumes sense buffer is
+ * in descriptor format) */
+int
+sg_get_sense_descriptors_str(const char * lip, const unsigned char * sbp,
+                             int sb_len, int blen, char * b)
+{
+    int add_sb_len, add_d_len, desc_len, k, j, sense_key;
+    int n, progress, pr, rem;
+    bool processed;
+    const unsigned char * descp;
+    const char * dtsp = "   >> descriptor too short";
+    const char * eccp = "Extended copy command";
+    const char * ddp = "destination device";
+    char z[64];
+
+    if ((NULL == b) || (blen <= 0))
+        return 0;
+    b[0] = '\0';
+    if (lip)
+        scnpr(z, sizeof(z), "%.60s  ", lip);
+    else
+        scnpr(z, sizeof(z), "  ");
+    if ((sb_len < 8) || (0 == (add_sb_len = sbp[7])))
+        return 0;
+    add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8);
+    sense_key = (sbp[1] & 0xf);
+
+    for (descp = (sbp + 8), k = 0, n = 0;
+         (k < add_sb_len) && (n < blen);
+         k += desc_len, descp += desc_len) {
+        add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1;
+        if ((k + add_d_len + 2) > add_sb_len)
+            add_d_len = add_sb_len - k - 2;
+        desc_len = add_d_len + 2;
+        n += scnpr(b + n, blen - n, "%s  Descriptor type: ", lip);
+        processed = true;
+        switch (descp[0]) {
+        case 0:
+            n += scnpr(b + n, blen - n, "Information: ");
+            if ((add_d_len >= 10) && (0x80 & descp[2])) {
+                n += scnpr(b + n, blen - n, "0x");
+                for (j = 0; j < 8; ++j)
+                    n += scnpr(b + n, blen - n, "%02x", descp[4 + j]);
+                n += scnpr(b + n, blen - n, "\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 1:
+            n += scnpr(b + n, blen - n, "Command specific: ");
+            if (add_d_len >= 10) {
+                n += scnpr(b + n, blen - n, "0x");
+                for (j = 0; j < 8; ++j)
+                    n += scnpr(b + n, blen - n, "%02x", descp[4 + j]);
+                n += scnpr(b + n, blen - n, "\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 2:         /* Sense Key Specific */
+            n += scnpr(b + n, blen - n, "Sense key specific: ");
+            n += decode_sks(lip, descp, add_d_len, sense_key, &processed,
+                            blen - n, b + n);
+            break;
+        case 3:
+            n += scnpr(b + n, blen - n, "Field replaceable unit code: ");
+            if (add_d_len >= 2)
+                n += scnpr(b + n, blen - n, "0x%x\n", descp[3]);
+            else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 4:
+            n += scnpr(b + n, blen - n, "Stream commands: ");
+            if (add_d_len >= 2) {
+                if (descp[3] & 0x80)
+                    n += scnpr(b + n, blen - n, "FILEMARK");
+                if (descp[3] & 0x40)
+                    n += scnpr(b + n, blen - n, "End Of Medium (EOM)");
+                if (descp[3] & 0x20)
+                    n += scnpr(b + n, blen - n, "Incorrect Length Indicator "
+                               "(ILI)");
+                n += scnpr(b + n, blen - n, "\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 5:
+            n += scnpr(b + n, blen - n, "Block commands: ");
+            if (add_d_len >= 2)
+                n += scnpr(b + n, blen - n, "Incorrect Length Indicator "
+                           "(ILI) %s\n", (descp[3] & 0x20) ? "set" : "clear");
+            else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 6:
+            n += scnpr(b + n, blen - n, "OSD object identification\n");
+            processed = false;
+            break;
+        case 7:
+            n += scnpr(b + n, blen - n, "OSD response integrity check "
+                             "value\n");
+            processed = false;
+            break;
+        case 8:
+            n += scnpr(b + n, blen - n, "OSD attribute identification\n");
+            processed = false;
+            break;
+        case 9:         /* this is defined in SAT (SAT-2) */
+            n += scnpr(b + n, blen - n, "ATA Status Return: ");
+            if (add_d_len >= 12) {
+                int extend, count;
+
+                extend = descp[2] & 1;
+                count = descp[5] + (extend ? (descp[4] << 8) : 0);
+                n += scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s"
+                           "        count=0x%x ", extend, descp[3], lip,
+                           count);
+                if (extend)
+                    n += scnpr(b + n, blen - n,
+                               "lba=0x%02x%02x%02x%02x%02x%02x ",
+                                descp[10], descp[8], descp[6], descp[11],
+                                descp[9], descp[7]);
+                else
+                    n += scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ",
+                               descp[11], descp[9], descp[7]);
+                n += scnpr(b + n, blen - n, "device=0x%x status=0x%x\n",
+                           descp[12], descp[13]);
+            } else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 0xa:
+           /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */
+            n += scnpr(b + n, blen - n, "Another progress indication: ");
+            if (add_d_len < 6) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            progress = sg_get_unaligned_be16(descp + 6);
+            pr = (progress * 100) / 65536;
+            rem = ((progress * 100) % 65536) / 656;
+            n += scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem);
+            n += scnpr(b + n, blen - n, "%s        [sense_key=0x%x "
+                       "asc,ascq=0x%x,0x%x]\n", lip, descp[2], descp[3],
+                       descp[4]);
+            break;
+        case 0xb:       /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */
+            n += scnpr(b + n, blen - n, "User data segment referral: ");
+            if (add_d_len < 2) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            n += scnpr(b + n, blen - n, "\n");
+            n += uds_referral_descriptor_str(b + n, blen - n, descp,
+                                             add_d_len, lip);
+            break;
+        case 0xc:       /* Added in SPC-4 rev 28 */
+            n += scnpr(b + n, blen - n, "Forwarded sense data\n");
+            if (add_d_len < 2) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            n += scnpr(b + n, blen - n, "%s    FSDT: %s\n", lip,
+                       (descp[2] & 0x80) ? "set" : "clear");
+            j = descp[2] & 0xf;
+            n += scnpr(b + n, blen - n, "%s    Sense data source: ", lip);
+            switch (j) {
+            case 0:
+                n += scnpr(b + n, blen - n, "%s source device\n", eccp);
+                break;
+            case 1:
+            case 2:
+            case 3:
+            case 4:
+            case 5:
+            case 6:
+            case 7:
+                n += scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1);
+                break;
+            default:
+                n += scnpr(b + n, blen - n, "unknown [%d]\n", j);
+            }
+            {
+                char c[480];
+
+                sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c);
+                c[sizeof(c) - 1] = '\0';
+                n += scnpr(b + n, blen - n, "%s    Forwarded status: %s\n",
+                           lip, c);
+                if (add_d_len > 2) {
+                    /* recursing; hope not to get carried away */
+                    n += scnpr(b + n, blen - n, "%s vvvvvvvvvvvvvvvv\n", lip);
+                    sg_get_sense_str(lip, descp + 4, add_d_len - 2, false,
+                                     sizeof(c), c);
+                    n += scnpr(b + n, blen - n, "%s", c);
+                    n += scnpr(b + n, blen - n, "%s ^^^^^^^^^^^^^^^^\n", lip);
+                }
+            }
+            break;
+        case 0xd:       /* Added in SBC-3 rev 36d */
+            /* this descriptor combines descriptors 0, 1, 2 and 3 */
+            n += scnpr(b + n, blen - n, "Direct-access block device\n");
+            if (add_d_len < 28) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            if (0x20 & descp[2])
+                n += scnpr(b + n, blen - n, "%s    ILI (incorrect length "
+                           "indication) set\n", lip);
+            if (0x80 & descp[4]) {
+                n += scnpr(b + n, blen - n, "%s    Sense key specific: ",
+                           lip);
+                n += decode_sks(lip, descp, add_d_len, sense_key, &processed,
+                                blen - n, b + n);
+            }
+            n += scnpr(b + n, blen - n, "%s    Field replaceable unit code: "
+                       "0x%x\n", lip, descp[7]);
+            if (0x80 & descp[2]) {
+                n += scnpr(b + n, blen - n, "%s    Information: 0x", lip);
+                for (j = 0; j < 8; ++j)
+                    n += scnpr(b + n, blen - n, "%02x", descp[8 + j]);
+                n += scnpr(b + n, blen - n, "\n");
+            }
+            n += scnpr(b + n, blen - n, "%s    Command specific: 0x", lip);
+            for (j = 0; j < 8; ++j)
+                n += scnpr(b + n, blen - n, "%02x", descp[16 + j]);
+            n += scnpr(b + n, blen - n, "\n");
+            break;
+        case 0xe:       /* Added in SPC-5 rev 6 (for Bind/Unbind) */
+            n += scnpr(b + n, blen - n, "Device designation\n");
+            j = (int)(sizeof(dd_usage_reason_str_arr) /
+                      sizeof(dd_usage_reason_str_arr[0]));
+            if (descp[3] < j)
+                n += scnpr(b + n, blen - n, "%s    Usage reason: %s\n", lip,
+                           dd_usage_reason_str_arr[descp[3]]);
+            else
+                n += scnpr(b + n, blen - n, "%s    Usage reason: "
+                           "reserved[%d]\n", lip, descp[3]);
+            n += sg_get_designation_descriptor_str(z, descp + 4, descp[1] - 2,
+                                                   true, false, blen - n,
+                                                   b + n);
+            break;
+        case 0xf:       /* Added in SPC-5 rev 10 (for Write buffer) */
+            n += scnpr(b + n, blen - n, "Microcode activation ");
+            if (add_d_len < 6) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            progress = sg_get_unaligned_be16(descp + 6);
+            n += scnpr(b + n, blen - n, "time: ");
+            if (0 == progress)
+                n += scnpr(b + n, blen - n, "unknown\n");
+            else
+                n += scnpr(b + n, blen - n, "%d seconds\n", progress);
+            break;
+        default:
+            if (descp[0] >= 0x80)
+                n += scnpr(b + n, blen - n, "Vendor specific [0x%x]\n",
+                           descp[0]);
+            else
+                n += scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]);
+            processed = false;
+            break;
+        }
+        if (! processed) {
+            if (add_d_len > 0) {
+                n += scnpr(b + n, blen - n, "%s    ", lip);
+                for (j = 0; j < add_d_len; ++j) {
+                    if ((j > 0) && (0 == (j % 24)))
+                        n += scnpr(b + n, blen - n, "\n%s    ", lip);
+                    n += scnpr(b + n, blen - n, "%02x ", descp[j + 2]);
+                }
+                n += scnpr(b + n, blen - n, "\n");
+            }
+        }
+        if (add_d_len < 0)
+            n += scnpr(b + n, blen - n, "%s    short descriptor\n", lip);
+    }
+    return n;
+}
+
+/* Decode SAT ATA PASS-THROUGH fixed format sense. Shows "+" after 'count'
+ * and/or 'lba' values to indicate that not all data in those fields is shown.
+ * That extra field information may be available in the ATA pass-through
+ * results log page parameter with the corresponding 'log_index'. */
+static int
+sg_get_sense_sat_pt_fixed_str(const char * lip, const unsigned char * sp,
+                              int slen, int blen, char * b)
+{
+    int n = 0;
+    bool extend, count_upper_nz, lba_upper_nz;
+
+    if ((blen < 1) || (slen < 12))
+        return n;
+    if (NULL == lip)
+        lip = "";
+    if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2]))
+        n += scnpr(b + n, blen - n, "%s  >> expected Sense key: Recovered "
+                   "Error ??\n", lip);
+    /* Fixed sense command-specific information field starts at sp + 8 */
+    extend = !!(0x80 & sp[8]);
+    count_upper_nz = !!(0x40 & sp[8]);
+    lba_upper_nz = !!(0x20 & sp[8]);
+    /* Fixed sense information field starts at sp + 3 */
+    n += scnpr(b + n, blen - n, "%s  error=0x%x, status=0x%x, device=0x%x, "
+               "count(7:0)=0x%x%c\n", lip, sp[3], sp[4], sp[5], sp[6],
+               (count_upper_nz ? '+' : ' '));
+    n += scnpr(b + n, blen - n, "%s  extend=%d, log_index=0x%x, "
+               "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", lip, (int)extend,
+               (0xf & sp[8]), sp[9], sp[10], sp[11],
+               (lba_upper_nz ? '+' : ' '));
+    return n;
+}
+
+/* Fetch sense information */
+int
+sg_get_sense_str(const char * lip, const unsigned char * sbp, int sb_len,
+                 bool raw_sinfo, int cblen, char * cbp)
+{
+    bool descriptor_format = false;
+    bool sdat_ovfl = false;
+    bool valid;
+    int len, progress, n, r, pr, rem, blen;
+    unsigned int info;
+    uint8_t resp_code;
+    const char * ebp = NULL;
+    char ebuff[64];
+    char b[256];
+    struct sg_scsi_sense_hdr ssh;
+
+    if ((NULL == cbp) || (cblen <= 0))
+        return 0;
+    else if (1 == cblen) {
+        cbp[0] = '\0';
+        return 0;
+    }
+    blen = sizeof(b);
+    n = 0;
+    if (NULL == lip)
+        lip = "";
+    if ((NULL == sbp) || (sb_len < 1)) {
+            n += scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip);
+            return n;
+    }
+    resp_code = 0x7f & sbp[0];
+    valid = !!(sbp[0] & 0x80);
+    len = sb_len;
+    if (sg_scsi_normalize_sense(sbp, sb_len, &ssh)) {
+        switch (ssh.response_code) {
+        case 0x70:      /* fixed, current */
+            ebp = "Fixed format, current";
+            len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
+            len = (len > sb_len) ? sb_len : len;
+            sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
+            break;
+        case 0x71:      /* fixed, deferred */
+            /* error related to a previous command */
+            ebp = "Fixed format, <<<deferred>>>";
+            len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
+            len = (len > sb_len) ? sb_len : len;
+            sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
+            break;
+        case 0x72:      /* descriptor, current */
+            descriptor_format = true;
+            ebp = "Descriptor format, current";
+            sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+            break;
+        case 0x73:      /* descriptor, deferred */
+            descriptor_format = true;
+            ebp = "Descriptor format, <<<deferred>>>";
+            sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+            break;
+        case 0x0:
+            ebp = "Response code: 0x0 (?)";
+            break;
+        default:
+            scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x",
+                  ssh.response_code);
+            ebp = ebuff;
+            break;
+        }
+        n += scnpr(cbp + n, cblen - n, "%s%s; Sense key: %s\n", lip, ebp,
+                   sg_lib_sense_key_desc[ssh.sense_key]);
+        if (sdat_ovfl)
+            n += scnpr(cbp + n, cblen - n, "%s<<<Sense data overflow>>>\n",
+                       lip);
+        if (descriptor_format) {
+            n += scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+                       sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+            n += sg_get_sense_descriptors_str(lip, sbp, len,
+                                              cblen - n, cbp + n);
+        } else if ((len > 12) && (0 == ssh.asc) &&
+                   (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
+            /* SAT ATA PASS-THROUGH fixed format */
+            n += scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+                       sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+            n += sg_get_sense_sat_pt_fixed_str(lip, sbp, len,
+                                               cblen - n, cbp + n);
+        } else if (len > 2) {   /* fixed format */
+            if (len > 12)
+                n += scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+                           sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+            r = 0;
+            if (strlen(lip) > 0)
+                r += scnpr(b + r, blen - r, "%s", lip);
+            if (len > 6) {
+                info = sg_get_unaligned_be32(sbp + 3);
+                if (valid)
+                    r += scnpr(b + r, blen - r, "  Info fld=0x%x [%u] ",
+                               info, info);
+                else if (info > 0)
+                    r += scnpr(b + r, blen - r, "  Valid=0, Info fld=0x%x "
+                               "[%u] ", info, info);
+            } else
+                info = 0;
+            if (sbp[2] & 0xe0) {
+                if (sbp[2] & 0x80)
+                   r += scnpr(b + r, blen - r, " FMK");
+                            /* current command has read a filemark */
+                if (sbp[2] & 0x40)
+                   r += scnpr(b + r, blen - r, " EOM");
+                            /* end-of-medium condition exists */
+                if (sbp[2] & 0x20)
+                   r += scnpr(b + r, blen - r, " ILI");
+                            /* incorrect block length requested */
+                r += scnpr(b + r, blen - r, "\n");
+            } else if (valid || (info > 0))
+                r += scnpr(b + r, blen - r, "\n");
+            if ((len >= 14) && sbp[14])
+                r += scnpr(b + r, blen - r, "%s  Field replaceable unit "
+                           "code: %d\n", lip, sbp[14]);
+            if ((len >= 18) && (sbp[15] & 0x80)) {
+                /* sense key specific decoding */
+                switch (ssh.sense_key) {
+                case SPC_SK_ILLEGAL_REQUEST:
+                    r += scnpr(b + r, blen - r, "%s  Sense Key Specific: "
+                               "Error in %s: byte %d", lip,
+                               ((sbp[15] & 0x40) ? "Command" :
+                                                   "Data parameters"),
+                             sg_get_unaligned_be16(sbp + 16));
+                    if (sbp[15] & 0x08)
+                        r += scnpr(b + r, blen - r, " bit %d\n",
+                                   sbp[15] & 0x07);
+                    else
+                        r += scnpr(b + r, blen - r, "\n");
+                    break;
+                case SPC_SK_NO_SENSE:
+                case SPC_SK_NOT_READY:
+                    progress = sg_get_unaligned_be16(sbp + 16);
+                    pr = (progress * 100) / 65536;
+                    rem = ((progress * 100) % 65536) / 656;
+                    r += scnpr(b + r, blen - r, "%s  Progress indication: "
+                               "%d.%02d%%\n", lip, pr, rem);
+                    break;
+                case SPC_SK_HARDWARE_ERROR:
+                case SPC_SK_MEDIUM_ERROR:
+                case SPC_SK_RECOVERED_ERROR:
+                    r += scnpr(b + r, blen - r, "%s  Actual retry count: "
+                               "0x%02x%02x\n", lip, sbp[16], sbp[17]);
+                    break;
+                case SPC_SK_COPY_ABORTED:
+                    r += scnpr(b + r, blen - r, "%s  Segment pointer: ", lip);
+                    r += scnpr(b + r, blen - r, "Relative to start of %s, "
+                               "byte %d", ((sbp[15] & 0x20) ?
+                                     "segment descriptor" : "parameter list"),
+                               sg_get_unaligned_be16(sbp + 16));
+                    if (sbp[15] & 0x08)
+                        r += scnpr(b + r, blen - r, " bit %d\n",
+                                   sbp[15] & 0x07);
+                    else
+                        r += scnpr(b + r, blen - r, "\n");
+                    break;
+                case SPC_SK_UNIT_ATTENTION:
+                    r += scnpr(b + r, blen - r, "%s  Unit attention "
+                               "condition queue: ", lip);
+                    r += scnpr(b + r, blen - r, "overflow flag is %d\n",
+                               !!(sbp[15] & 0x1));
+                    break;
+                default:
+                    r += scnpr(b + r, blen - r, "%s  Sense_key: 0x%x "
+                               "unexpected\n", lip, ssh.sense_key);
+                    break;
+                }
+            }
+            if (r > 0)
+                n += scnpr(cbp + n, cblen - n, "%s", b);
+        } else
+            n += scnpr(cbp + n, cblen - n, "%s fixed descriptor length "
+                       "too short, len=%d\n", lip, len);
+    } else {    /* unable to normalise sense buffer, something irregular */
+        if (sb_len < 4) {       /* Too short */
+            n += scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 "
+                       "byte minimum)\n", lip);
+            goto check_raw;
+        }
+        if (0x7f == resp_code) {        /* Vendor specific */
+            n += scnpr(cbp + n, cblen - n, "%sVendor specific sense buffer, "
+                       "in hex:\n", lip);
+            n += hex2str(sbp, sb_len, lip, -1, cblen - n, cbp + n);
+            return n;   /* no need to check raw, just output in hex */
+        }
+        /* non-extended SCSI-1 sense data ?? */
+        r = 0;
+        if (strlen(lip) > 0)
+            r += scnpr(b + r, blen - r, "%s", lip);
+        r += scnpr(b + r, blen - r, "Probably uninitialized data.\n%s  Try "
+                   "to view as SCSI-1 non-extended sense:\n", lip);
+        r += scnpr(b + r, blen - r, "  AdValid=%d  Error class=%d  Error "
+                   "code=%d\n", valid, ((sbp[0] >> 4) & 0x7),
+                   (sbp[0] & 0xf));
+        if (valid)
+            scnpr(b + r, blen - r, "%s  lba=0x%x\n", lip,
+                  sg_get_unaligned_be24(sbp + 1) & 0x1fffff);
+        n += scnpr(cbp + n, cblen - n, "%s\n", b);
+        len = sb_len;
+        if (len > 32)
+            len = 32;   /* trim in case there is a lot of rubbish */
+    }
+check_raw:
+    if (raw_sinfo) {
+        char z[64];
+
+        n += scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex):\n",
+                   lip);
+        if (n >= (cblen - 1))
+            return n;
+        scnpr(z, sizeof(z), "%.50s        ", lip);
+        n += hex2str(sbp, len, z,  -1, cblen - n, cbp + n);
+    }
+    return n;
+}
+
+/* Print sense information */
+void
+sg_print_sense(const char * leadin, const unsigned char * sbp, int sb_len,
+               bool raw_sinfo)
+{
+    uint32_t pg_sz = sg_get_page_size();
+    char *cp;
+    uint8_t *free_cp;
+
+    cp = (char *)sg_memalign(pg_sz, pg_sz, &free_cp, 0);
+    if (NULL == cp)
+        return;
+    sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp);
+    pr2ws("%s", cp);
+    free(free_cp);
+}
+
+/* Following examines exit_status and outputs a clear error message to
+ * warnings_strm (usually stderr) if one is known and returns true.
+ * Otherwise it doesn't print anything and returns false. Note that
+ * if exit_status==0 then returns true but prints nothing and if
+ * exit_status<0 ("some error occurred") false is returned. If leadin is
+ * non-NULL then it is printed before the error message. */
+bool
+sg_if_can2stderr(const char * leadin, int exit_status)
+{
+    const char * s = leadin ? leadin : "";
+
+    if (exit_status < 0)
+        return false;
+    else if (0 == exit_status)
+        return true;
+
+    switch (exit_status) {
+    case SG_LIB_CAT_NOT_READY:          /* 2 */
+        pr2ws("%sDevice not ready\n", s);
+        return true;
+    case SG_LIB_CAT_MEDIUM_HARD:        /* 3 */
+        pr2ws("%sMedium or hardware error\n", s); /* 3 sense keys: Medium, */
+        return true;    /* hardware error or 'Blank check' for tapes */
+    case SG_LIB_CAT_UNIT_ATTENTION:     /* 6 */
+        pr2ws("%sDevice reported 'Unit attention'\n", s);
+        return true;
+    case SG_LIB_CAT_DATA_PROTECT:       /* 7 */
+        pr2ws("%sDevice reported 'Data protect', read-only?\n", s);
+        return true;
+    case SG_LIB_CAT_COPY_ABORTED:       /* 10 */
+        pr2ws("%sCopy aborted\n", s);
+        return true;
+    case SG_LIB_CAT_ABORTED_COMMAND:    /* 11 */
+        pr2ws("%sCommand aborted\n", s);
+        return true;
+    case SG_LIB_CAT_MISCOMPARE:         /* 14 */
+        pr2ws("%sMiscompare\n", s);
+        return true;
+    case SG_LIB_CAT_RES_CONFLICT:       /* 24 */
+        pr2ws("%sReservation conflict\n", s);
+        return true;
+    case SG_LIB_CAT_BUSY:               /* 26 */
+        pr2ws("%sDevice is busy, try again\n", s);
+        return true;
+    case SG_LIB_CAT_TASK_ABORTED:       /* 29 */
+        pr2ws("%sTask aborted\n", s);
+        return true;
+    case SG_LIB_CAT_TIMEOUT:            /* 33 */
+        pr2ws("%sTime out\n", s);
+        return true;
+    case SG_LIB_CAT_PROTECTION:         /* 40 */
+        pr2ws("%sProtection error\n", s);
+        return true;
+    case SG_LIB_NVME_STATUS:            /* 48 */
+        pr2ws("%sNVMe error (non-zero status)\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + EACCES:   /* 50 + */
+        pr2ws("%sPermission denied\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + ENOMEM:
+        pr2ws("%sUtility unable to allocate memory\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + ENOTTY:
+        pr2ws("%sInappropriate I/O control operation\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + EPERM:
+        pr2ws("%sNot permitted\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + EINTR:
+        pr2ws("%sInterrupted system call\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + EIO:
+        pr2ws("%sInput/output error\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + ENODEV:
+        pr2ws("%sNo such device\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + ENOENT:
+        pr2ws("%sNo such file or directory\n", s);
+        return true;
+    default:
+        return false;
+    }
+    return false;
+}
+
+/* If os_err_num is within bounds then the returned value is 'os_err_num +
+ * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0
+ * is returned. */
+int
+sg_convert_errno(int os_err_num)
+{
+    if (os_err_num <= 0) {
+        if (os_err_num < -1)
+            return -1;
+        return os_err_num;
+    }
+    if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR))
+        return SG_LIB_OS_BASE_ERR + os_err_num;
+    return -1;
+}
+
+/* See description in sg_lib.h header file */
+bool
+sg_scsi_normalize_sense(const unsigned char * sbp, int sb_len,
+                        struct sg_scsi_sense_hdr * sshp)
+{
+    uint8_t resp_code;
+    if (sshp)
+        memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
+    if ((NULL == sbp) || (sb_len < 1))
+        return false;
+    resp_code = 0x7f & sbp[0];
+    if ((resp_code < 0x70) || (resp_code > 0x73))
+        return false;
+    if (sshp) {
+        sshp->response_code = resp_code;
+        if (sshp->response_code >= 0x72) {  /* descriptor format */
+            if (sb_len > 1)
+                sshp->sense_key = (0xf & sbp[1]);
+            if (sb_len > 2)
+                sshp->asc = sbp[2];
+            if (sb_len > 3)
+                sshp->ascq = sbp[3];
+            if (sb_len > 7)
+                sshp->additional_length = sbp[7];
+        } else {                              /* fixed format */
+            if (sb_len > 2)
+                sshp->sense_key = (0xf & sbp[2]);
+            if (sb_len > 7) {
+                sb_len = (sb_len < (sbp[7] + 8)) ? sb_len : (sbp[7] + 8);
+                if (sb_len > 12)
+                    sshp->asc = sbp[12];
+                if (sb_len > 13)
+                    sshp->ascq = sbp[13];
+            }
+        }
+    }
+    return true;
+}
+
+/* Returns a SG_LIB_CAT_* value. If cannot decode sense buffer (sbp) or a
+ * less common sense key then return SG_LIB_CAT_SENSE .*/
+int
+sg_err_category_sense(const unsigned char * sbp, int sb_len)
+{
+    struct sg_scsi_sense_hdr ssh;
+
+    if ((sbp && (sb_len > 2)) &&
+        (sg_scsi_normalize_sense(sbp, sb_len, &ssh))) {
+        switch (ssh.sense_key) {        /* 0 to 0x1f */
+        case SPC_SK_NO_SENSE:
+            return SG_LIB_CAT_NO_SENSE;
+        case SPC_SK_RECOVERED_ERROR:
+            return SG_LIB_CAT_RECOVERED;
+        case SPC_SK_NOT_READY:
+            return SG_LIB_CAT_NOT_READY;
+        case SPC_SK_MEDIUM_ERROR:
+        case SPC_SK_HARDWARE_ERROR:
+        case SPC_SK_BLANK_CHECK:
+            return SG_LIB_CAT_MEDIUM_HARD;
+        case SPC_SK_UNIT_ATTENTION:
+            return SG_LIB_CAT_UNIT_ATTENTION;
+            /* used to return SG_LIB_CAT_MEDIA_CHANGED when ssh.asc==0x28 */
+        case SPC_SK_ILLEGAL_REQUEST:
+            if ((0x20 == ssh.asc) && (0x0 == ssh.ascq))
+                return SG_LIB_CAT_INVALID_OP;
+            else
+                return SG_LIB_CAT_ILLEGAL_REQ;
+            break;
+        case SPC_SK_ABORTED_COMMAND:
+            if (0x10 == ssh.asc)
+                return SG_LIB_CAT_PROTECTION;
+            else
+                return SG_LIB_CAT_ABORTED_COMMAND;
+        case SPC_SK_MISCOMPARE:
+            return SG_LIB_CAT_MISCOMPARE;
+        case SPC_SK_DATA_PROTECT:
+            return SG_LIB_CAT_DATA_PROTECT;
+        case SPC_SK_COPY_ABORTED:
+            return SG_LIB_CAT_COPY_ABORTED;
+        case SPC_SK_COMPLETED:
+        case SPC_SK_VOLUME_OVERFLOW:
+            return SG_LIB_CAT_SENSE;
+        default:
+            ;   /* reserved and vendor specific sense keys fall through */
+        }
+    }
+    return SG_LIB_CAT_SENSE;
+}
+
+/* Beware: gives wrong answer for variable length command (opcode=0x7f) */
+int
+sg_get_command_size(unsigned char opcode)
+{
+    switch ((opcode >> 5) & 0x7) {
+    case 0:
+        return 6;
+    case 1: case 2: case 6: case 7:
+        return 10;
+    case 3: case 5:
+        return 12;
+        break;
+    case 4:
+        return 16;
+    default:
+        return 10;
+    }
+}
+
+void
+sg_get_command_name(const unsigned char * cmdp, int peri_type, int buff_len,
+                    char * buff)
+{
+    int service_action;
+
+    if ((NULL == buff) || (buff_len < 1))
+        return;
+    else if (1 == buff_len) {
+        buff[0] = '\0';
+        return;
+    }
+    if (NULL == cmdp) {
+        scnpr(buff, buff_len, "%s", "<null> command pointer");
+        return;
+    }
+    service_action = (SG_VARIABLE_LENGTH_CMD == cmdp[0]) ?
+                     sg_get_unaligned_be16(cmdp + 8) : (cmdp[1] & 0x1f);
+    sg_get_opcode_sa_name(cmdp[0], service_action, peri_type, buff_len, buff);
+}
+
+struct op_code2sa_t {
+    int op_code;
+    int pdt_match;      /* -1->all; 0->disk,ZBC,RCB, 1->tape+adc+smc */
+    struct sg_lib_value_name_t * arr;
+    const char * prefix;
+};
+
+static struct op_code2sa_t op_code2sa_arr[] = {
+    {SG_VARIABLE_LENGTH_CMD, -1, sg_lib_variable_length_arr, NULL},
+    {SG_MAINTENANCE_IN, -1, sg_lib_maint_in_arr, NULL},
+    {SG_MAINTENANCE_OUT, -1, sg_lib_maint_out_arr, NULL},
+    {SG_SERVICE_ACTION_IN_12, -1, sg_lib_serv_in12_arr, NULL},
+    {SG_SERVICE_ACTION_OUT_12, -1, sg_lib_serv_out12_arr, NULL},
+    {SG_SERVICE_ACTION_IN_16, -1, sg_lib_serv_in16_arr, NULL},
+    {SG_SERVICE_ACTION_OUT_16, -1, sg_lib_serv_out16_arr, NULL},
+    {SG_SERVICE_ACTION_BIDI, -1, sg_lib_serv_bidi_arr, NULL},
+    {SG_PERSISTENT_RESERVE_IN, -1, sg_lib_pr_in_arr, "Persistent reserve in"},
+    {SG_PERSISTENT_RESERVE_OUT, -1, sg_lib_pr_out_arr,
+     "Persistent reserve out"},
+    {SG_3PARTY_COPY_OUT, -1, sg_lib_xcopy_sa_arr, NULL},
+    {SG_3PARTY_COPY_IN, -1, sg_lib_rec_copy_sa_arr, NULL},
+    {SG_READ_BUFFER, -1, sg_lib_read_buff_arr, "Read buffer(10)"},
+    {SG_READ_BUFFER_16, -1, sg_lib_read_buff_arr, "Read buffer(16)"},
+    {SG_READ_ATTRIBUTE, -1, sg_lib_read_attr_arr, "Read attribute"},
+    {SG_READ_POSITION, 1, sg_lib_read_pos_arr, "Read position"},
+    {SG_SANITIZE, 0, sg_lib_sanitize_sa_arr, "Sanitize"},
+    {SG_WRITE_BUFFER, -1, sg_lib_write_buff_arr, "Write buffer"},
+    {SG_ZONING_IN, 0, sg_lib_zoning_in_arr, NULL},
+    {SG_ZONING_OUT, 0, sg_lib_zoning_out_arr, NULL},
+    {0xffff, -1, NULL, NULL},
+};
+
+void
+sg_get_opcode_sa_name(unsigned char cmd_byte0, int service_action,
+                      int peri_type, int buff_len, char * buff)
+{
+    int d_pdt;
+    const struct sg_lib_value_name_t * vnp;
+    const struct op_code2sa_t * osp;
+    char b[80];
+
+    if ((NULL == buff) || (buff_len < 1))
+        return;
+    else if (1 == buff_len) {
+        buff[0] = '\0';
+        return;
+    }
+
+    if (peri_type < 0)
+        peri_type = 0;
+    d_pdt = sg_lib_pdt_decay(peri_type);
+    for (osp = op_code2sa_arr; osp->arr; ++osp) {
+        if ((int)cmd_byte0 == osp->op_code) {
+            if ((osp->pdt_match < 0) || (d_pdt == osp->pdt_match)) {
+                vnp = get_value_name(osp->arr, service_action, peri_type);
+                if (vnp) {
+                    if (osp->prefix)
+                        scnpr(buff, buff_len, "%s, %s", osp->prefix,
+                              vnp->name);
+                    else
+                        scnpr(buff, buff_len, "%s", vnp->name);
+                } else {
+                    sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b);
+                    scnpr(buff, buff_len, "%s service action=0x%x", b,
+                          service_action);
+                }
+            } else
+                sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
+            return;
+        }
+    }
+    sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
+}
+
+void
+sg_get_opcode_name(unsigned char cmd_byte0, int peri_type, int buff_len,
+                   char * buff)
+{
+    const struct sg_lib_value_name_t * vnp;
+    int grp;
+
+    if ((NULL == buff) || (buff_len < 1))
+        return;
+    else if (1 == buff_len) {
+        buff[0] = '\0';
+        return;
+    }
+    if (SG_VARIABLE_LENGTH_CMD == cmd_byte0) {
+        scnpr(buff, buff_len, "%s", "Variable length");
+        return;
+    }
+    grp = (cmd_byte0 >> 5) & 0x7;
+    switch (grp) {
+    case 0:
+    case 1:
+    case 2:
+    case 4:
+    case 5:
+        vnp = get_value_name(sg_lib_normal_opcodes, cmd_byte0, peri_type);
+        if (vnp)
+            scnpr(buff, buff_len, "%s", vnp->name);
+        else
+            scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
+        break;
+    case 3:
+        scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0);
+        break;
+    case 6:
+    case 7:
+        scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0);
+        break;
+    default:
+        scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
+        break;
+    }
+}
+
+/* Iterates to next designation descriptor in the device identification
+ * VPD page. The 'initial_desig_desc' should point to start of first
+ * descriptor with 'page_len' being the number of valid bytes in that
+ * and following descriptors. To start, 'off' should point to a negative
+ * value, thereafter it should point to the value yielded by the previous
+ * call. If 0 returned then 'initial_desig_desc + *off' should be a valid
+ * descriptor; returns -1 if normal end condition and -2 for an abnormal
+ * termination. Matches association, designator_type and/or code_set when
+ * any of those values are greater than or equal to zero. */
+int
+sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len,
+                   int * off, int m_assoc, int m_desig_type, int m_code_set)
+{
+    bool fltr = ((m_assoc >= 0) || (m_desig_type >= 0) || (m_code_set >= 0));
+    int k = *off;
+    const unsigned char * bp = initial_desig_desc;
+
+    while ((k + 3) < page_len) {
+        k = (k < 0) ? 0 : (k + bp[k + 3] + 4);
+        if ((k + 4) > page_len)
+            break;
+        if (fltr) {
+            if (m_code_set >= 0) {
+                if ((bp[k] & 0xf) != m_code_set)
+                    continue;
+            }
+            if (m_assoc >= 0) {
+                if (((bp[k + 1] >> 4) & 0x3) != m_assoc)
+                    continue;
+            }
+            if (m_desig_type >= 0) {
+                if ((bp[k + 1] & 0xf) != m_desig_type)
+                    continue;
+            }
+        }
+        *off = k;
+        return 0;
+    }
+    return (k == page_len) ? -1 : -2;
+}
+
+static const char * const bad_sense_cat = "Bad sense category";
+
+/* Yield string associated with sense category. Returns 'buff' (or pointer
+ * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then
+ * yield "Sense category: <sense_cat>" string. */
+const char *
+sg_get_category_sense_str(int sense_cat, int buff_len, char * buff,
+                          int verbose)
+{
+    int n;
+
+    if (NULL == buff)
+        return bad_sense_cat;
+    if (buff_len <= 0)
+        return buff;
+    switch (sense_cat) {
+    case SG_LIB_CAT_CLEAN:              /* 0 */
+        scnpr(buff, buff_len, "No errors");
+        break;
+    case SG_LIB_SYNTAX_ERROR:           /* 1 */
+        scnpr(buff, buff_len, "Syntax error");
+        break;
+    case SG_LIB_CAT_NOT_READY:          /* 2 */
+        n = scnpr(buff, buff_len, "Not ready");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_MEDIUM_HARD:        /* 3 */
+        n = scnpr(buff, buff_len, "Medium or hardware error");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key (plus blank check)");
+        break;
+    case SG_LIB_CAT_ILLEGAL_REQ:        /* 5 */
+        n = scnpr(buff, buff_len, "Illegal request");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key, apart from Invalid "
+                  "opcode");
+        break;
+    case SG_LIB_CAT_UNIT_ATTENTION:     /* 6 */
+        n = scnpr(buff, buff_len, "Unit attention");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_DATA_PROTECT:       /* 7 */
+        n = scnpr(buff, buff_len, "Data protect");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key, write protected "
+                     "media?");
+        break;
+    case SG_LIB_CAT_INVALID_OP:         /* 9 */
+        n = scnpr(buff, buff_len, "Illegal request, invalid opcode");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_COPY_ABORTED:       /* 10 */
+        n = scnpr(buff, buff_len, "Copy aborted");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_ABORTED_COMMAND:    /* 11 */
+        n = scnpr(buff, buff_len, "Aborted command");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key, other than "
+                     "protection related (asc=0x10)");
+        break;
+    case SG_LIB_CAT_MISCOMPARE:         /* 14 */
+        n = scnpr(buff, buff_len, "Miscompare");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_FILE_ERROR:             /* 15 */
+        scnpr(buff, buff_len, "File error");
+        break;
+    case SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO:  /* 17 */
+        scnpr(buff, buff_len, "Illegal request with info");
+        break;
+    case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO:  /* 18 */
+        scnpr(buff, buff_len, "Medium or hardware error with info");
+        break;
+    case SG_LIB_CAT_NO_SENSE:           /* 20 */
+        n = scnpr(buff, buff_len, "No sense key");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " probably additional sense "
+                     "information");
+        break;
+    case SG_LIB_CAT_RECOVERED:          /* 21 */
+        n = scnpr(buff, buff_len, "Recovered error");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_RES_CONFLICT:       /* 24 */
+        n = scnpr(buff, buff_len, "Reservation conflict");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_CONDITION_MET:      /* 25 */
+        n = scnpr(buff, buff_len, "Condition met");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_BUSY:               /* 26 */
+        n = scnpr(buff, buff_len, "Busy");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_TS_FULL:            /* 27 */
+        n = scnpr(buff, buff_len, "Task set full");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_ACA_ACTIVE:         /* 28 */
+        n = scnpr(buff, buff_len, "ACA active");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_TASK_ABORTED:       /* 29 */
+        n = scnpr(buff, buff_len, "Task aborted");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_TIMEOUT:            /* 33 */
+        scnpr(buff, buff_len, "SCSI command timeout");
+        break;
+    case SG_LIB_CAT_PROTECTION:         /* 40 */
+        n = scnpr(buff, buff_len, "Aborted command, protection");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " information (PI) problem");
+        break;
+    case SG_LIB_CAT_PROTECTION_WITH_INFO: /* 41 */
+        n = scnpr(buff, buff_len, "Aborted command with info, protection");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " information (PI) problem");
+        break;
+    case SG_LIB_CAT_MALFORMED:          /* 97 */
+        n = scnpr(buff, buff_len, "Malformed response");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " to SCSI command");
+        break;
+    case SG_LIB_CAT_SENSE:              /* 98 */
+        n = scnpr(buff, buff_len, "Some other sense data problem");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, ", try '-v' option for more "
+                     "information");
+        break;
+    case SG_LIB_CAT_OTHER:              /* 99 */
+        n = scnpr(buff, buff_len, "Some other error/warning has occurred");
+        if ((0 == verbose) && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, ", possible transport of driver "
+                     "issue");
+        break;
+    default:
+        if ((sense_cat > SG_LIB_OS_BASE_ERR) &&
+            (sense_cat < (SG_LIB_OS_BASE_ERR + 47))) {
+            int k = sense_cat - SG_LIB_OS_BASE_ERR;
+
+            n = scnpr(buff, buff_len, "OS error: %s [%d]", safe_strerror(k),
+                      k);
+        } else {
+            n = scnpr(buff, buff_len, "Sense category: %d", sense_cat);
+            if ((0 == verbose) && (n < (buff_len - 1)))
+                scnpr(buff + n, buff_len - n, ", try '-v' option for more "
+                      "information");
+        }
+        break;
+    }
+    return buff;
+}
+
+static const char * sg_sfs_spc_reserved = "SPC Reserved";
+static const char * sg_sfs_sbc_reserved = "SBC Reserved";
+static const char * sg_sfs_ssc_reserved = "SSC Reserved";
+static const char * sg_sfs_zbc_reserved = "ZBC Reserved";
+static const char * sg_sfs_reserved = "Reserved";
+
+/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31)
+ * returns pointer to string (same as 'buff') associated with 'sfs_code'.
+ * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match
+ * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL
+ * then where it points is set to true if a match is found else it is set to
+ * false. If 'buff' is not NULL then in the case of a match a descriptive
+ * string is written to 'buff' while if there is not a not then a string
+ * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC
+ * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL.
+ * Example:
+ *    char b[64];
+ *    ...
+ *    printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0));
+ */
+const char *
+sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, char * buff,
+               bool * foundp, int verbose)
+{
+    const struct sg_lib_value_name_t * vnp = NULL;
+    int n = 0;
+    int my_pdt;
+
+    if ((NULL == buff) || (buff_len < 1)) {
+        if (foundp)
+            *foundp = false;
+        return NULL;
+    } else if (1 == buff_len) {
+        buff[0] = '\0';
+        if (foundp)
+            *foundp = false;
+        return NULL;
+    }
+    my_pdt = ((peri_type < -1) || (peri_type > 0x1f)) ? -2 : peri_type;
+    vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt);
+    if (vnp && (-2 != my_pdt)) {
+        if (peri_type != vnp->peri_dev_type)
+            vnp = NULL;         /* shouldn't really happen */
+    }
+    if (foundp)
+        *foundp = vnp ? true : false;
+    if (sfs_code < 0x100) {             /* SPC Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "SPC %s", vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved);
+    } else if (sfs_code < 0x200) {      /* SBC Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "SBC %s", vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved);
+    } else if (sfs_code < 0x300) {      /* SSC Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "SSC %s", vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved);
+    } else if (sfs_code < 0x400) {      /* ZBC Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "ZBC %s", vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved);
+    } else {                            /* Other SCSI Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "[unrecognized PDT] %s",
+                           vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_reserved);
+
+    }
+    if (verbose > 4)
+        pr2serr("%s: length of returned string (n) %d\n", __func__, n);
+    return buff;
+}
+
+/* This is a heuristic that takes into account the command bytes and length
+ * to decide whether the presented unstructured sequence of bytes could be
+ * a SCSI command. If so it returns true otherwise false. Vendor specific
+ * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed
+ * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The
+ * only SCSI commands considered above 16 bytes of length 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.
+ * No NVMe commands (64 bytes long plus some extra added by some OSes) have
+ * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS
+ * structures that are sent across the wire. The FIS register structure is
+ * used to move a command from a SATA host to device, but the ATA 'command'
+ * is not the first byte. So it is harder to say what will happen if a
+ * FIS structure is presented as a SCSI command, hopfully there is a low
+ * probability this function will yield true in that case. */
+bool
+sg_is_scsi_cdb(const uint8_t * cdbp, int clen)
+{
+    int ilen, sa;
+    uint8_t opcode;
+    uint8_t top3bits;
+
+    if (clen < 6)
+        return false;
+    opcode = cdbp[0];
+    top3bits = opcode >> 5;
+    if (0x3 == top3bits) {
+        if ((clen < 12) || (clen % 4))
+            return false;       /* must be modulo 4 and 12 or more bytes */
+        switch (opcode) {
+        case 0x7e:      /* Extended cdb (XCDB) */
+            ilen = 4 + sg_get_unaligned_be16(cdbp + 2);
+            return (ilen == clen);
+        case 0x7f:      /* Variable Length cdb */
+            ilen = 8 + cdbp[7];
+            sa = sg_get_unaligned_be16(cdbp + 8);
+            /* service action (sa) 0x0 is reserved */
+            return ((ilen == clen) && sa);
+        default:
+            return false;
+        }
+    } else if (clen <= 16) {
+        switch (clen) {
+        case 6:
+            if (top3bits > 0x5)         /* vendor */
+                return true;
+            return (0x0 == top3bits);   /* 6 byte cdb */
+        case 10:
+            if (top3bits > 0x5)         /* vendor */
+                return true;
+            return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */
+        case 16:
+            if (top3bits > 0x5)         /* vendor */
+                return true;
+            return (0x4 == top3bits);   /* 16 byte cdb */
+        case 12:
+            if (top3bits > 0x5)         /* vendor */
+                return true;
+            return (0x5 == top3bits);   /* 12 byte cdb */
+        default:
+            return false;
+        }
+    }
+    /* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or
+     * opcode > 0x7f). */
+    return false;
+}
+
+/* Yield string associated with NVMe command status value in sct_sc. It
+ * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25
+ * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC).
+ * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found
+ * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated.
+ * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/
+char *
+sg_get_nvme_cmd_status_str(uint16_t sct_sc, int b_len, char * b)
+{
+    int k;
+    uint16_t s = 0x3ff & sct_sc;
+    const struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr;
+
+    if ((b_len <= 0) || (NULL == b))
+        return b;
+    else if (1 == b_len) {
+        b[0] = '\0';
+        return b;
+    }
+    for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) {
+        if (s == (uint16_t)vp->value) {
+            strncpy(b, vp->name, b_len);
+            b[b_len - 1] = '\0';
+            return b;
+        }
+    }
+    if (k >= 1000)
+        pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n",
+                        __func__);
+    snprintf(b, b_len, "Reserved [0x%x]", sct_sc);
+    return b;
+}
+
+/* Attempts to map NVMe status value ((SCT << 8) | SC) to SCSI status,
+ * sense_key, asc and ascq tuple. If successful returns true and writes to
+ * non-NULL pointer arguments; otherwise returns false. */
+bool
+sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p,
+                    uint8_t * asc_p, uint8_t * ascq_p)
+{
+    int k, ind;
+    uint16_t s = 0x3ff & sct_sc;
+    struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr;
+    struct sg_lib_4tuple_u8 * mp = sg_lib_scsi_status_sense_arr;
+
+    for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) {
+        if (s == (uint16_t)vp->value)
+            break;
+    }
+    if (k >= 1000) {
+        pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n",
+              __func__);
+        return false;
+    }
+    if (NULL == vp->name)
+        return false;
+    ind = vp->peri_dev_type;
+
+
+    for (k = 0; (0xff != mp->t2) && k < 1000; ++k, ++mp)
+        ;       /* count entries for valid index range */
+    if (k >= 1000) {
+        pr2ws("%s: where is sentinel for sg_lib_scsi_status_sense_arr ??\n",
+              __func__);
+        return false;
+    } else if (ind >= k)
+        return false;
+    mp = sg_lib_scsi_status_sense_arr + ind;
+    if (status_p)
+        *status_p = mp->t1;
+    if (sk_p)
+        *sk_p = mp->t2;
+    if (asc_p)
+        *asc_p = mp->t3;
+    if (ascq_p)
+        *ascq_p = mp->t4;
+    return true;
+}
+
+/* safe_strerror() contributed by Clayton Weaver <cgweav at email dot com>
+ * Allows for situation in which strerror() is given a wild value (or the
+ * C library is incomplete) and returns NULL. Still not thread safe.
+ */
+
+static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ',
+                               'e', 'r', 'r', 'n', 'o', ':', ' ', 0};
+
+char *
+safe_strerror(int errnum)
+{
+    size_t len;
+    char * errstr;
+
+    if (errnum < 0)
+        errnum = -errnum;
+    errstr = strerror(errnum);
+    if (NULL == errstr) {
+        len = strlen(safe_errbuf);
+        scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum);
+        return safe_errbuf;
+    }
+    return errstr;
+}
+
+static void
+trimTrailingSpaces(char * b)
+{
+    int k;
+
+    for (k = ((int)strlen(b) - 1); k >= 0; --k) {
+        if (' ' != b[k])
+            break;
+    }
+    if ('\0' != b[k + 1])
+        b[k + 1] = '\0';
+}
+
+/* Note the ASCII-hex output goes to stdout. [Most other output from functions
+ * in this file go to sg_warnings_strm (default stderr).]
+ * 'no_ascii' allows for 3 output types:
+ *     > 0     each line has address then up to 16 ASCII-hex bytes
+ *     = 0     in addition, the bytes are listed in ASCII to the right
+ *     < 0     only the ASCII-hex bytes are listed (i.e. without address) */
+static void
+dStrHexFp(const char* str, int len, int no_ascii, FILE * fp)
+{
+    const char * p = str;
+    const char * formatstr;
+    unsigned char c;
+    char buff[82];
+    int a = 0;
+    int bpstart = 5;
+    const int cpstart = 60;
+    int cpos = cpstart;
+    int bpos = bpstart;
+    int i, k, blen;
+
+    if (len <= 0)
+        return;
+    blen = (int)sizeof(buff);
+    if (0 == no_ascii)  /* address at left and ASCII at right */
+        formatstr = "%.76s\n";
+    else                        /* previously when > 0 str was "%.58s\n" */
+        formatstr = "%s\n";     /* when < 0 str was: "%.48s\n" */
+    memset(buff, ' ', 80);
+    buff[80] = '\0';
+    if (no_ascii < 0) {
+        bpstart = 0;
+        bpos = bpstart;
+        for (k = 0; k < len; k++) {
+            c = *p++;
+            if (bpos == (bpstart + (8 * 3)))
+                bpos++;
+            scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c);
+            buff[bpos + 2] = ' ';
+            if ((k > 0) && (0 == ((k + 1) % 16))) {
+                trimTrailingSpaces(buff);
+                fprintf(fp, formatstr, buff);
+                bpos = bpstart;
+                memset(buff, ' ', 80);
+            } else
+                bpos += 3;
+        }
+        if (bpos > bpstart) {
+            buff[bpos + 2] = '\0';
+            trimTrailingSpaces(buff);
+            fprintf(fp, "%s\n", buff);
+        }
+        return;
+    }
+    /* no_ascii>=0, start each line with address (offset) */
+    k = scnpr(buff + 1, blen - 1, "%.2x", a);
+    buff[k + 1] = ' ';
+
+    for (i = 0; i < len; i++) {
+        c = *p++;
+        bpos += 3;
+        if (bpos == (bpstart + (9 * 3)))
+            bpos++;
+        scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c);
+        buff[bpos + 2] = ' ';
+        if (no_ascii)
+            buff[cpos++] = ' ';
+        else {
+            if (! my_isprint(c))
+                c = '.';
+            buff[cpos++] = c;
+        }
+        if (cpos > (cpstart + 15)) {
+            if (no_ascii)
+                trimTrailingSpaces(buff);
+            fprintf(fp, formatstr, buff);
+            bpos = bpstart;
+            cpos = cpstart;
+            a += 16;
+            memset(buff, ' ', 80);
+            k = scnpr(buff + 1, blen - 1, "%.2x", a);
+            buff[k + 1] = ' ';
+        }
+    }
+    if (cpos > cpstart) {
+        buff[cpos] = '\0';
+        if (no_ascii)
+            trimTrailingSpaces(buff);
+        fprintf(fp, "%s\n", buff);
+    }
+}
+
+void
+dStrHex(const char* str, int len, int no_ascii)
+{
+    dStrHexFp(str, len, no_ascii, stdout);
+}
+
+void
+dStrHexErr(const char* str, int len, int no_ascii)
+{
+    dStrHexFp(str, len, no_ascii,
+              (sg_warnings_strm ? sg_warnings_strm : stderr));
+}
+
+#define DSHS_LINE_BLEN 160
+#define DSHS_BPL 16
+
+/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space
+ * separated) to 'b' not to exceed 'b_len' characters. Each line
+ * starts with 'leadin' (NULL for no leadin) and there are 16 bytes
+ * per line with an extra space between the 8th and 9th bytes. 'format'
+ * is 0 for repeat in printable ASCII ('.' for non printable) to
+ * right of each line; 1 don't (so just output ASCII hex). Returns
+ * number of bytes written to 'b' excluding the trailing '\0'. */
+int
+dStrHexStr(const char * str, int len, const char * leadin, int format,
+           int b_len, char * b)
+{
+    unsigned char c;
+    int bpstart, bpos, k, n, prior_ascii_len;
+    bool want_ascii;
+    char buff[DSHS_LINE_BLEN + 2];
+    char a[DSHS_BPL + 1];
+    const char * p = str;
+
+    if (len <= 0) {
+        if (b_len > 0)
+            b[0] = '\0';
+        return 0;
+    }
+    if (b_len <= 0)
+        return 0;
+    want_ascii = !format;
+    if (want_ascii) {
+        memset(a, ' ', DSHS_BPL);
+        a[DSHS_BPL] = '\0';
+    }
+    if (leadin) {
+        bpstart = strlen(leadin);
+        /* Cap leadin at (DSHS_LINE_BLEN - 70) characters */
+        if (bpstart > (DSHS_LINE_BLEN - 70))
+            bpstart = DSHS_LINE_BLEN - 70;
+    } else
+        bpstart = 0;
+    bpos = bpstart;
+    prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1;
+    n = 0;
+    memset(buff, ' ', DSHS_LINE_BLEN);
+    buff[DSHS_LINE_BLEN] = '\0';
+    if (bpstart > 0)
+        memcpy(buff, leadin, bpstart);
+    for (k = 0; k < len; k++) {
+        c = *p++;
+        if (bpos == (bpstart + ((DSHS_BPL / 2) * 3)))
+            bpos++;     /* for extra space in middle of each line's hex */
+        scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x",
+              (int)(unsigned char)c);
+        buff[bpos + 2] = ' ';
+        if (want_ascii)
+            a[k % DSHS_BPL] = my_isprint(c) ? c : '.';
+        if ((k > 0) && (0 == ((k + 1) % DSHS_BPL))) {
+            trimTrailingSpaces(buff);
+            if (want_ascii) {
+                n += scnpr(b + n, b_len - n, "%-*s   %s\n", prior_ascii_len,
+                           buff, a);
+                memset(a, ' ', DSHS_BPL);
+            } else
+                n += scnpr(b + n, b_len - n, "%s\n", buff);
+            if (n >= (b_len - 1))
+                return n;
+            memset(buff, ' ', DSHS_LINE_BLEN);
+            bpos = bpstart;
+            if (bpstart > 0)
+                memcpy(buff, leadin, bpstart);
+        } else
+            bpos += 3;
+    }
+    if (bpos > bpstart) {
+        trimTrailingSpaces(buff);
+        if (want_ascii)
+            n += scnpr(b + n, b_len - n, "%-*s   %s\n", prior_ascii_len,
+                       buff, a);
+        else
+            n += scnpr(b + n, b_len - n, "%s\n", buff);
+    }
+    return n;
+}
+
+void
+hex2stdout(const uint8_t * b_str, int len, int no_ascii)
+{
+    dStrHex((const char *)b_str, len, no_ascii);
+}
+
+void
+hex2stderr(const uint8_t * b_str, int len, int no_ascii)
+{
+    dStrHexErr((const char *)b_str, len, no_ascii);
+}
+
+int
+hex2str(const uint8_t * b_str, int len, const char * leadin, int format,
+        int b_len, char * b)
+{
+    return dStrHexStr((const char *)b_str, len, leadin, format, b_len, b);
+}
+
+/* Returns true when executed on big endian machine; else returns false.
+ * Useful for displaying ATA identify words (which need swapping on a
+ * big endian machine). */
+bool
+sg_is_big_endian()
+{
+    union u_t {
+        uint16_t s;
+        unsigned char c[sizeof(uint16_t)];
+    } u;
+
+    u.s = 0x0102;
+    return (u.c[0] == 0x01);     /* The lowest address contains
+                                    the most significant byte */
+}
+
+bool
+sg_all_zeros(const uint8_t * bp, int b_len)
+{
+    if ((NULL == bp) || (b_len <= 0))
+        return false;
+    for (--b_len; b_len >= 0; --b_len) {
+        if (0x0 != bp[b_len])
+            return false;
+    }
+    return true;
+}
+
+bool
+sg_all_ffs(const uint8_t * bp, int b_len)
+{
+    if ((NULL == bp) || (b_len <= 0))
+        return false;
+    for (--b_len; b_len >= 0; --b_len) {
+        if (0xff != bp[b_len])
+            return false;
+    }
+    return true;
+}
+
+static uint16_t
+swapb_uint16(uint16_t u)
+{
+    uint16_t r;
+
+    r = (u >> 8) & 0xff;
+    r |= ((u & 0xff) << 8);
+    return r;
+}
+
+/* Note the ASCII-hex output goes to stdout. [Most other output from functions
+ * in this file go to sg_warnings_strm (default stderr).]
+ * 'no_ascii' allows for 3 output types:
+ *     > 0     each line has address then up to 8 ASCII-hex 16 bit words
+ *     = 0     in addition, the ASCI bytes pairs are listed to the right
+ *     = -1    only the ASCII-hex words are listed (i.e. without address)
+ *     = -2    only the ASCII-hex words, formatted for "hdparm --Istdin"
+ *     < -2    same as -1
+ * If 'swapb' is true then bytes in each word swapped. Needs to be set
+ * for ATA IDENTIFY DEVICE response on big-endian machines. */
+void
+dWordHex(const uint16_t* words, int num, int no_ascii, bool swapb)
+{
+    const uint16_t * p = words;
+    uint16_t c;
+    char buff[82];
+    unsigned char upp, low;
+    int a = 0;
+    const int bpstart = 3;
+    const int cpstart = 52;
+    int cpos = cpstart;
+    int bpos = bpstart;
+    int i, k, blen;
+
+    if (num <= 0)
+        return;
+    blen = (int)sizeof(buff);
+    memset(buff, ' ', 80);
+    buff[80] = '\0';
+    if (no_ascii < 0) {
+        for (k = 0; k < num; k++) {
+            c = *p++;
+            if (swapb)
+                c = swapb_uint16(c);
+            bpos += 5;
+            scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c);
+            buff[bpos + 4] = ' ';
+            if ((k > 0) && (0 == ((k + 1) % 8))) {
+                if (-2 == no_ascii)
+                    printf("%.39s\n", buff +8);
+                else
+                    printf("%.47s\n", buff);
+                bpos = bpstart;
+                memset(buff, ' ', 80);
+            }
+        }
+        if (bpos > bpstart) {
+            if (-2 == no_ascii)
+                printf("%.39s\n", buff +8);
+            else
+                printf("%.47s\n", buff);
+        }
+        return;
+    }
+    /* no_ascii>=0, start each line with address (offset) */
+    k = scnpr(buff + 1, blen - 1, "%.2x", a);
+    buff[k + 1] = ' ';
+
+    for (i = 0; i < num; i++) {
+        c = *p++;
+        if (swapb)
+            c = swapb_uint16(c);
+        bpos += 5;
+        scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c);
+        buff[bpos + 4] = ' ';
+        if (no_ascii) {
+            buff[cpos++] = ' ';
+            buff[cpos++] = ' ';
+            buff[cpos++] = ' ';
+        } else {
+            upp = (c >> 8) & 0xff;
+            low = c & 0xff;
+            if (! my_isprint(upp))
+                upp = '.';
+            buff[cpos++] = upp;
+            if (! my_isprint(low))
+                low = '.';
+            buff[cpos++] = low;
+            buff[cpos++] = ' ';
+        }
+        if (cpos > (cpstart + 23)) {
+            printf("%.76s\n", buff);
+            bpos = bpstart;
+            cpos = cpstart;
+            a += 8;
+            memset(buff, ' ', 80);
+            k = scnpr(buff + 1, blen - 1, "%.2x", a);
+            buff[k + 1] = ' ';
+        }
+    }
+    if (cpos > cpstart)
+        printf("%.76s\n", buff);
+}
+
+/* If the number in 'buf' can be decoded or the multiplier is unknown
+ * then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal
+ * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
+ * Main (SI) multipliers supported: K, M, G. Ignore leading spaces and
+ * tabs; accept comma, hyphen, space, tab and hash as terminator. */
+int
+sg_get_num(const char * buf)
+{
+    int res, num, n, len;
+    unsigned int unum;
+    char * cp;
+    const char * b;
+    char c = 'c';
+    char c2 = '\0';     /* keep static checker happy */
+    char c3 = '\0';     /* keep static checker happy */
+    char lb[16];
+
+    if ((NULL == buf) || ('\0' == buf[0]))
+        return -1;
+    len = strlen(buf);
+    n = strspn(buf, " \t");
+    if (n > 0) {
+        if (n == len)
+            return -1;
+        buf += n;
+        len -= n;
+    }
+    /* following hack to keep C++ happy */
+    cp = strpbrk((char *)buf, " \t,#-");
+    if (cp) {
+        len = cp - buf;
+        n = (int)sizeof(lb) - 1;
+        len = (len < n) ? len : n;
+        memcpy(lb, buf, len);
+        lb[len] = '\0';
+        b = lb;
+    } else
+        b = buf;
+    if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+        res = sscanf(b + 2, "%x", &unum);
+        num = unum;
+    } else if ('H' == toupper((int)b[len - 1])) {
+        res = sscanf(b, "%x", &unum);
+        num = unum;
+    } else
+        res = sscanf(b, "%d%c%c%c", &num, &c, &c2, &c3);
+    if (res < 1)
+        return -1LL;
+    else if (1 == res)
+        return num;
+    else {
+        if (res > 2)
+            c2 = toupper((int)c2);
+        if (res > 3)
+            c3 = toupper((int)c3);
+        switch (toupper((int)c)) {
+        case 'C':
+            return num;
+        case 'W':
+            return num * 2;
+        case 'B':
+            return num * 512;
+        case 'K':
+            if (2 == res)
+                return num * 1024;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1024;
+            return -1;
+        case 'M':
+            if (2 == res)
+                return num * 1048576;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1048576;
+            return -1;
+        case 'G':
+            if (2 == res)
+                return num * 1073741824;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1073741824;
+            return -1;
+        case 'X':
+            cp = (char *)strchr(b, 'x');
+            if (NULL == cp)
+                cp = (char *)strchr(b, 'X');
+            if (cp) {
+                n = sg_get_num(cp + 1);
+                if (-1 != n)
+                    return num * n;
+            }
+            return -1;
+        default:
+            pr2ws("unrecognized multiplier\n");
+            return -1;
+        }
+    }
+}
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. */
+int
+sg_get_num_nomult(const char * buf)
+{
+    int res, len, num;
+    unsigned int unum;
+    char * commap;
+
+    if ((NULL == buf) || ('\0' == buf[0]))
+        return -1;
+    len = strlen(buf);
+    commap = (char *)strchr(buf + 1, ',');
+    if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
+        res = sscanf(buf + 2, "%x", &unum);
+        num = unum;
+    } else if (commap && ('H' == toupper((int)*(commap - 1)))) {
+        res = sscanf(buf, "%x", &unum);
+        num = unum;
+    } else if ((NULL == commap) && ('H' == toupper((int)buf[len - 1]))) {
+        res = sscanf(buf, "%x", &unum);
+        num = unum;
+    } else
+        res = sscanf(buf, "%d", &num);
+    if (1 == res)
+        return num;
+    else
+        return -1;
+}
+
+/* If the number in 'buf' can be decoded or the multiplier is unknown
+ * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a decimal
+ * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
+ * Main (SI) multipliers supported: K, M, G, T, P. Ignore leading spaces
+ * and tabs; accept comma, hyphen, space, tab and hash as terminator. */
+int64_t
+sg_get_llnum(const char * buf)
+{
+    int res, len, n;
+    int64_t num, ll;
+    uint64_t unum;
+    char * cp;
+    const char * b;
+    char c = 'c';
+    char c2 = '\0';     /* keep static checker happy */
+    char c3 = '\0';     /* keep static checker happy */
+    char lb[32];
+
+    if ((NULL == buf) || ('\0' == buf[0]))
+        return -1LL;
+    len = strlen(buf);
+    n = strspn(buf, " \t");
+    if (n > 0) {
+        if (n == len)
+            return -1LL;
+        buf += n;
+        len -= n;
+    }
+    /* following hack to keep C++ happy */
+    cp = strpbrk((char *)buf, " \t,#-");
+    if (cp) {
+        len = cp - buf;
+        n = (int)sizeof(lb) - 1;
+        len = (len < n) ? len : n;
+        memcpy(lb, buf, len);
+        lb[len] = '\0';
+        b = lb;
+    } else
+        b = buf;
+    if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+        res = sscanf(b + 2, "%" SCNx64 , &unum);
+        num = unum;
+    } else if ('H' == toupper((int)b[len - 1])) {
+        res = sscanf(b, "%" SCNx64 , &unum);
+        num = unum;
+    } else
+        res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3);
+    if (res < 1)
+        return -1LL;
+    else if (1 == res)
+        return num;
+    else {
+        if (res > 2)
+            c2 = toupper((int)c2);
+        if (res > 3)
+            c3 = toupper((int)c3);
+        switch (toupper((int)c)) {
+        case 'C':
+            return num;
+        case 'W':
+            return num * 2;
+        case 'B':
+            return num * 512;
+        case 'K':
+            if (2 == res)
+                return num * 1024;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1024;
+            return -1LL;
+        case 'M':
+            if (2 == res)
+                return num * 1048576;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1048576;
+            return -1LL;
+        case 'G':
+            if (2 == res)
+                return num * 1073741824;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1073741824;
+            return -1LL;
+        case 'T':
+            if (2 == res)
+                return num * 1099511627776LL;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000000000LL;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1099511627776LL;
+            return -1LL;
+        case 'P':
+            if (2 == res)
+                return num * 1099511627776LL * 1024;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000000000LL * 1000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1099511627776LL * 1024;
+            return -1LL;
+        case 'X':
+            cp = (char *)strchr(b, 'x');
+            if (NULL == cp)
+                cp = (char *)strchr(b, 'X');
+            if (cp) {
+                ll = sg_get_llnum(cp + 1);
+                if (-1LL != ll)
+                    return num * ll;
+            }
+            return -1LL;
+        default:
+            pr2ws("unrecognized multiplier\n");
+            return -1LL;
+        }
+    }
+}
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * negative numbers and '-1' must be treated separately. */
+int64_t
+sg_get_llnum_nomult(const char * buf)
+{
+    int res, len;
+    int64_t num;
+    uint64_t unum;
+
+    if ((NULL == buf) || ('\0' == buf[0]))
+        return -1;
+    len = strlen(buf);
+    if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
+        res = sscanf(buf + 2, "%" SCNx64 "", &unum);
+        num = unum;
+    } else if ('H' == toupper(buf[len - 1])) {
+        res = sscanf(buf, "%" SCNx64 "", &unum);
+        num = unum;
+    } else
+        res = sscanf(buf, "%" SCNd64 "", &num);
+    return (1 == res) ? num : -1;
+}
+
+/* Extract character sequence from ATA words as in the model string
+ * in a IDENTIFY DEVICE response. Returns number of characters
+ * written to 'ochars' before 0 character is found or 'num' words
+ * are processed. */
+int
+sg_ata_get_chars(const uint16_t * word_arr, int start_word,
+                 int num_words, bool is_big_endian, char * ochars)
+{
+    int k;
+    uint16_t s;
+    char a, b;
+    char * op = ochars;
+
+    for (k = start_word; k < (start_word + num_words); ++k) {
+        s = word_arr[k];
+        if (is_big_endian) {
+            a = s & 0xff;
+            b = (s >> 8) & 0xff;
+        } else {
+            a = (s >> 8) & 0xff;
+            b = s & 0xff;
+        }
+        if (a == 0)
+            break;
+        *op++ = a;
+        if (b == 0)
+            break;
+        *op++ = b;
+    }
+    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;
+}
+
+#ifdef SG_LIB_FREEBSD
+#include <sys/param.h>
+#elif defined(SG_LIB_WIN32)
+#include <windows.h>
+
+static bool got_page_size = false;
+static uint32_t win_page_size;
+#endif
+
+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)
+    if (! got_page_size) {
+        SYSTEM_INFO si;
+
+        GetSystemInfo(&si);
+        win_page_size = si.dwPageSize;
+        got_page_size = true;
+    }
+    return win_page_size;
+#elif defined(SG_LIB_FREEBSD)
+    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;
+
+    if (buff_to_free)   /* make sure buff_to_free is NULL if alloc fails */
+        *buff_to_free = NULL;
+    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_ma, len=%d, ", __func__, num_bytes);
+            if (buff_to_free)
+                pr2ws("wrkBuffp=%p, ", (void *)res);
+            pr2ws("psz=%u, rp=%p\n", (unsigned int)psz, (void *)res);
+        }
+        return res;
+    }
+#else
+    {
+        void * wrkBuff;
+        sg_uintptr_t align_1 = psz - 1;
+
+        wrkBuff = (uint8_t *)calloc(num_bytes + psz, 1);
+        if (NULL == wrkBuff) {
+            if (buff_to_free)
+                *buff_to_free = NULL;
+            return NULL;
+        } else if (buff_to_free)
+            *buff_to_free = (uint8_t *)wrkBuff;
+        res = (uint8_t *)(void *)
+            (((sg_uintptr_t)wrkBuff + align_1) & (~align_1));
+        if (vb) {
+            pr2ws("%s: hack, len=%d, ", __func__, num_bytes);
+            if (buff_to_free)
+                pr2ws("buff_to_free=%p, ", wrkBuff);
+            pr2ws("align_1=%lu, rp=%p\n", align_1, (void *)res);
+        }
+        return res;
+    }
+#endif
+}
+
+const char *
+sg_lib_version()
+{
+    return sg_lib_version_str;
+}
+
+
+#ifdef SG_LIB_MINGW
+/* Non Unix OSes distinguish between text and binary files.
+   Set text mode on fd. Does nothing in Unix. Returns negative number on
+   failure. */
+
+#include <unistd.h>
+#include <fcntl.h>
+
+int
+sg_set_text_mode(int fd)
+{
+    return setmode(fd, O_TEXT);
+}
+
+/* Set binary mode on fd. Does nothing in Unix. Returns negative number on
+   failure. */
+int
+sg_set_binary_mode(int fd)
+{
+    return setmode(fd, O_BINARY);
+}
+
+#else
+/* For Unix the following functions are dummies. */
+int
+sg_set_text_mode(int fd)
+{
+    return fd;  /* fd should be >= 0 */
+}
+
+int
+sg_set_binary_mode(int fd)
+{
+    return fd;
+}
+
+#endif
diff --git a/tools/sg_write_buffer/sg_lib_data.c b/tools/sg_write_buffer/sg_lib_data.c
new file mode 100644
index 0000000..211054d
--- /dev/null
+++ b/tools/sg_write_buffer/sg_lib_data.c
@@ -0,0 +1,1684 @@
+/*
+ * Copyright (c) 2007-2018 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 <stdlib.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#else
+#define SG_SCSI_STRINGS 1
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+
+
+const char * sg_lib_version_str = "2.38 20180122";/* spc5r17, sbc4r15 */
+
+
+/* indexed by pdt; those that map to own index do not decay */
+int sg_lib_pdt_decay_arr[32] = {
+    PDT_DISK, PDT_TAPE, PDT_TAPE /* printer */, PDT_PROCESSOR,
+    PDT_DISK /* WO */, PDT_MMC, PDT_SCANNER, PDT_DISK /* optical */,
+    PDT_MCHANGER, PDT_COMMS, 0xa, 0xb,
+    PDT_SAC, PDT_SES, PDT_DISK /* rbc */, PDT_OCRW,
+    PDT_BCC, PDT_OSD, PDT_TAPE /* adc */, PDT_SMD,
+    PDT_DISK /* zbc */, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, PDT_WLUN, PDT_UNKNOWN
+};
+
+#ifdef SG_SCSI_STRINGS
+struct sg_lib_value_name_t sg_lib_normal_opcodes[] = {
+    {0, 0, "Test Unit Ready"},
+    {0x1, 0, "Rezero Unit"},
+    {0x1, PDT_TAPE, "Rewind"},
+    {0x3, 0, "Request Sense"},
+    {0x4, 0, "Format Unit"},
+    {0x4, PDT_TAPE, "Format medium"},
+    {0x4, PDT_PRINTER, "Format"},
+    {0x5, 0, "Read Block Limits"},
+    {0x7, 0, "Reassign Blocks"},
+    {0x7, PDT_MCHANGER, "Initialize element status"},
+    {0x8, 0, "Read(6)"},        /* obsolete in sbc3r30 */
+    {0x8, PDT_PROCESSOR, "Receive"},
+    {0xa, 0, "Write(6)"},       /* obsolete in sbc3r30 */
+    {0xa, PDT_PRINTER, "Print"},
+    {0xa, PDT_PROCESSOR, "Send"},
+    {0xb, 0, "Seek(6)"},
+    {0xb, PDT_TAPE, "Set capacity"},
+    {0xb, PDT_PRINTER, "Slew and print"},
+    {0xf, 0, "Read reverse(6)"},
+    {0x10, 0, "Write filemarks(6)"},
+    {0x10, PDT_PRINTER, "Synchronize buffer"},
+    {0x11, 0, "Space(6)"},
+    {0x12, 0, "Inquiry"},
+    {0x13, 0, "Verify(6)"},  /* SSC */
+    {0x14, 0, "Recover buffered data"},
+    {0x15, 0, "Mode select(6)"}, /* SBC-3 r31 recommends Mode select(10) */
+    {0x16, 0, "Reserve(6)"},    /* obsolete in SPC-4 r11 */
+    {0x16, PDT_MCHANGER, "Reserve element(6)"},
+    {0x17, 0, "Release(6)"},    /* obsolete in SPC-4 r11 */
+    {0x17, PDT_MCHANGER, "Release element(6)"},
+    {0x18, 0, "Copy"},          /* obsolete in SPC-4 r11 */
+    {0x19, 0, "Erase(6)"},
+    {0x1a, 0, "Mode sense(6)"}, /* SBC-3 r31 recommends Mode sense(10) */
+    {0x1b, 0, "Start stop unit"},
+    {0x1b, PDT_TAPE, "Load unload"},
+    {0x1b, PDT_ADC, "Load unload"},
+    {0x1b, PDT_PRINTER, "Stop print"},
+    {0x1c, 0, "Receive diagnostic results"},
+    {0x1d, 0, "Send diagnostic"},
+    {0x1e, 0, "Prevent allow medium removal"},
+    {0x23, 0, "Read Format capacities"},
+    {0x24, 0, "Set window"},
+    {0x25, 0, "Read capacity(10)"},
+                        /* SBC-3 r31 recommends Read capacity(16) */
+    {0x25, PDT_OCRW, "Read card capacity"},
+    {0x28, 0, "Read(10)"},      /* SBC-3 r31 recommends Read(16) */
+    {0x29, 0, "Read generation"},
+    {0x2a, 0, "Write(10)"},     /* SBC-3 r31 recommends Write(16) */
+    {0x2b, 0, "Seek(10)"},
+    {0x2b, PDT_TAPE, "Locate(10)"},
+    {0x2b, PDT_MCHANGER, "Position to element"},
+    {0x2c, 0, "Erase(10)"},
+    {0x2d, 0, "Read updated block"},
+    {0x2e, 0, "Write and verify(10)"},
+                        /* SBC-3 r31 recommends Write and verify(16) */
+    {0x2f, 0, "Verify(10)"},    /* SBC-3 r31 recommends Verify(16) */
+    {0x30, 0, "Search data high(10)"},
+    {0x31, 0, "Search data equal(10)"},
+    {0x32, 0, "Search data low(10)"},
+    {0x33, 0, "Set limits(10)"},
+    {0x34, 0, "Pre-fetch(10)"}, /* SBC-3 r31 recommends Pre-fetch(16) */
+    {0x34, PDT_TAPE, "Read position"},
+    {0x35, 0, "Synchronize cache(10)"},
+                        /* SBC-3 r31 recommends Synchronize cache(16) */
+    {0x36, 0, "Lock unlock cache(10)"},
+    {0x37, 0, "Read defect data(10)"},
+                        /* SBC-3 r31 recommends Read defect data(12) */
+    {0x37, PDT_MCHANGER, "Initialize element status with range"},
+    {0x38, 0, "Medium scan"},
+    {0x39, 0, "Compare"},               /* obsolete in SPC-4 r11 */
+    {0x3a, 0, "Copy and verify"},       /* obsolete in SPC-4 r11 */
+    {0x3b, 0, "Write buffer"},
+    {0x3c, 0, "Read buffer(10)"},
+    {0x3d, 0, "Update block"},
+    {0x3e, 0, "Read long(10)"},         /* obsolete in SBC-4 r7 */
+    {0x3f, 0, "Write long(10)"}, /* SBC-3 r31 recommends Write long(16) */
+    {0x40, 0, "Change definition"},     /* obsolete in SPC-4 r11 */
+    {0x41, 0, "Write same(10)"}, /* SBC-3 r31 recommends Write same(16) */
+    {0x42, 0, "Unmap"},                 /* added SPC-4 rev 18 */
+    {0x42, PDT_MMC, "Read sub-channel"},
+    {0x43, PDT_MMC, "Read TOC/PMA/ATIP"},
+    {0x44, 0, "Report density support"},
+    {0x45, PDT_MMC, "Play audio(10)"},
+    {0x46, PDT_MMC, "Get configuration"},
+    {0x47, PDT_MMC, "Play audio msf"},
+    {0x48, 0, "Sanitize"},
+    {0x4a, PDT_MMC, "Get event status notification"},
+    {0x4b, PDT_MMC, "Pause/resume"},
+    {0x4c, 0, "Log select"},
+    {0x4d, 0, "Log sense"},
+    {0x4e, 0, "Stop play/scan"},
+    {0x50, 0, "Xdwrite(10)"},           /* obsolete in SBC-3 r31 */
+    {0x51, 0, "Xpwrite(10)"},           /* obsolete in SBC-4 r15 */
+    {0x51, PDT_MMC, "Read disk information"},
+    {0x52, 0, "Xdread(10)"},            /* obsolete in SBC-3 r31 */
+    {0x52, PDT_MMC, "Read track information"},
+    {0x53, 0, "Xdwriteread(10)"},       /* obsolete in SBC-4 r15 */
+    {0x54, 0, "Send OPC information"},
+    {0x55, 0, "Mode select(10)"},
+    {0x56, 0, "Reserve(10)"},           /* obsolete in SPC-4 r11 */
+    {0x56, PDT_MCHANGER, "Reserve element(10)"},
+    {0x57, 0, "Release(10)"},           /* obsolete in SPC-4 r11 */
+    {0x57, PDT_MCHANGER, "Release element(10)"},
+    {0x58, 0, "Repair track"},
+    {0x5a, 0, "Mode sense(10)"},
+    {0x5b, 0, "Close track/session"},
+    {0x5c, 0, "Read buffer capacity"},
+    {0x5d, 0, "Send cue sheet"},
+    {0x5e, 0, "Persistent reserve in"},
+    {0x5f, 0, "Persistent reserve out"},
+    {0x7e, 0, "Extended cdb (XCBD)"},           /* added in SPC-4 r12 */
+    {0x80, 0, "Xdwrite extended(16)"},          /* obsolete in SBC-4 r15 */
+    {0x80, PDT_TAPE, "Write filemarks(16)"},
+    {0x81, 0, "Rebuild(16)"},
+    {0x81, PDT_TAPE, "Read reverse(16)"},
+    {0x82, 0, "Regenerate(16)"},
+    {0x83, 0, "Third party copy out"},  /* Extended copy, before spc4r34 */
+        /* Following was "Receive copy results", before spc4r34 */
+    {0x84, 0, "Third party copy in"},
+    {0x85, 0, "ATA pass-through(16)"},  /* was 0x98 in spc3 rev21c */
+    {0x86, 0, "Access control in"},
+    {0x87, 0, "Access control out"},
+    {0x88, 0, "Read(16)"},
+    {0x89, 0, "Compare and write"},
+    {0x8a, 0, "Write(16)"},
+    {0x8b, 0, "Orwrite(16)"},
+    {0x8c, 0, "Read attribute"},
+    {0x8d, 0, "Write attribute"},
+    {0x8e, 0, "Write and verify(16)"},
+    {0x8f, 0, "Verify(16)"},
+    {0x90, 0, "Pre-fetch(16)"},
+    {0x91, 0, "Synchronize cache(16)"},
+    {0x91, PDT_TAPE, "Space(16)"},
+    {0x92, 0, "Lock unlock cache(16)"},
+    {0x92, PDT_TAPE, "Locate(16)"},
+    {0x93, 0, "Write same(16)"},
+    {0x93, PDT_TAPE, "Erase(16)"},
+    {0x94, PDT_ZBC, "ZBC out"},  /* new sbc4r04, has service actions */
+    {0x95, PDT_ZBC, "ZBC in"},   /* new sbc4r04, has service actions */
+    {0x9a, 0, "Write stream(16)"},      /* added sbc4r07 */
+    {0x9b, 0, "Read buffer(16)"},       /* added spc5r02 */
+    {0x9c, 0, "Write atomic(16)"},
+    {0x9d, 0, "Service action bidirectional"},  /* added spc4r35 */
+    {0x9e, 0, "Service action in(16)"},
+    {0x9f, 0, "Service action out(16)"},
+    {0xa0, 0, "Report luns"},
+    {0xa1, 0, "ATA pass-through(12)"},
+    {0xa1, PDT_MMC, "Blank"},
+    {0xa2, 0, "Security protocol in"},
+    {0xa3, 0, "Maintenance in"},
+    {0xa3, PDT_MMC, "Send key"},
+    {0xa4, 0, "Maintenance out"},
+    {0xa4, PDT_MMC, "Report key"},
+    {0xa5, 0, "Move medium"},
+    {0xa5, PDT_MMC, "Play audio(12)"},
+    {0xa6, 0, "Exchange medium"},
+    {0xa6, PDT_MMC, "Load/unload medium"},
+    {0xa7, 0, "Move medium attached"},
+    {0xa7, PDT_MMC, "Set read ahead"},
+    {0xa8, 0, "Read(12)"},      /* SBC-3 r31 recommends Read(16) */
+    {0xa9, 0, "Service action out(12)"},
+    {0xaa, 0, "Write(12)"},     /* SBC-3 r31 recommends Write(16) */
+    {0xab, 0, "Service action in(12)"},
+    {0xac, 0, "erase(12)"},
+    {0xac, PDT_MMC, "Get performance"},
+    {0xad, PDT_MMC, "Read DVD/BD structure"},
+    {0xae, 0, "Write and verify(12)"},
+                        /* SBC-3 r31 recommends Write and verify(16) */
+    {0xaf, 0, "Verify(12)"},    /* SBC-3 r31 recommends Verify(16) */
+    {0xb0, 0, "Search data high(12)"},
+    {0xb1, 0, "Search data equal(12)"},
+    {0xb1, PDT_MCHANGER, "Open/close import/export element"},
+    {0xb2, 0, "Search data low(12)"},
+    {0xb3, 0, "Set limits(12)"},
+    {0xb4, 0, "Read element status attached"},
+    {0xb5, 0, "Security protocol out"},
+    {0xb5, PDT_MCHANGER, "Request volume element address"},
+    {0xb6, 0, "Send volume tag"},
+    {0xb6, PDT_MMC, "Set streaming"},
+    {0xb7, 0, "Read defect data(12)"},
+    {0xb8, 0, "Read element status"},
+    {0xb9, 0, "Read CD msf"},
+    {0xba, 0, "Redundancy group in"},
+    {0xba, PDT_MMC, "Scan"},
+    {0xbb, 0, "Redundancy group out"},
+    {0xbb, PDT_MMC, "Set CD speed"},
+    {0xbc, 0, "Spare in"},
+    {0xbd, 0, "Spare out"},
+    {0xbd, PDT_MMC, "Mechanism status"},
+    {0xbe, 0, "Volume set in"},
+    {0xbe, PDT_MMC, "Read CD"},
+    {0xbf, 0, "Volume set out"},
+    {0xbf, PDT_MMC, "Send DVD/BD structure"},
+    {0xffff, 0, NULL},
+};
+
+/* Read buffer(10) [0x3c] and Read buffer(16) [0x9b] service actions (sa),
+ * need prefix */
+struct sg_lib_value_name_t sg_lib_read_buff_arr[] = {
+    {0x0, 0, "combined header and data [or multiple modes]"},
+    {0x2, 0, "data"},
+    {0x3, 0, "descriptor"},
+    {0xa, 0, "read data from echo buffer"},
+    {0xb, 0, "echo buffer descriptor"},
+    {0x1a, 0, "enable expander comms protocol and echo buffer"},
+    {0x1c, 0, "error history"},
+    {0xffff, 0, NULL},
+};
+
+/* Write buffer [0x3b] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_write_buff_arr[] = {
+    {0x0, 0, "combined header and data [or multiple modes]"},
+    {0x2, 0, "data"},
+    {0x4, 0, "download microcode and activate"},
+    {0x5, 0, "download microcode, save, and activate"},
+    {0x6, 0, "download microcode with offsets and activate"},
+    {0x7, 0, "download microcode with offsets, save, and activate"},
+    {0xa, 0, "write data to echo buffer"},
+    {0xd, 0, "download microcode with offsets, select activation events, "
+             "save and defer activate"},
+    {0xe, 0, "download microcode with offsets, save and defer activate"},
+    {0xf, 0, "activate deferred microcode"},
+    {0x1a, 0, "enable expander comms protocol and echo buffer"},
+    {0x1b, 0, "disable expander comms protocol"},
+    {0x1c, 0, "download application client error history"},
+    {0xffff, 0, NULL},
+};
+
+/* Read position (SSC) [0x34] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_read_pos_arr[] = {
+    {0x0, PDT_TAPE, "short form - block id"},
+    {0x1, PDT_TAPE, "short form - vendor specific"},
+    {0x6, PDT_TAPE, "long form"},
+    {0x8, PDT_TAPE, "extended form"},
+    {0xffff, 0, NULL},
+};
+
+/* Maintenance in [0xa3] service actions */
+struct sg_lib_value_name_t sg_lib_maint_in_arr[] = {
+    {0x0, PDT_SAC, "Report assigned/unassigned p_extent"},
+    {0x1, PDT_SAC, "Report component device"},
+    {0x2, PDT_SAC, "Report component device attachments"},
+    {0x3, PDT_SAC, "Report peripheral device"},
+    {0x4, PDT_SAC, "Report peripheral device associations"},
+    {0x5, 0, "Report identifying information"},
+                /* was "Report device identifier" prior to spc4r07 */
+    {0x6, PDT_SAC, "Report states"},
+    {0x7, PDT_SAC, "Report device identification"},
+    {0x8, PDT_SAC, "Report unconfigured capacity"},
+    {0x9, PDT_SAC, "Report supported configuration method"},
+    {0xa, 0, "Report target port groups"},
+    {0xb, 0, "Report aliases"},
+    {0xc, 0, "Report supported operation codes"},
+    {0xd, 0, "Report supported task management functions"},
+    {0xe, 0, "Report priority"},
+    {0xf, 0, "Report timestamp"},
+    {0x10, 0, "Management protocol in"},
+    {0x1d, PDT_DISK, "Report provisioning initialization pattern"},
+        /* added in sbc4r07, shares sa 0x1d with ssc5r01 (tape) */
+    {0x1d, PDT_TAPE, "Receive recommended access order"},
+    {0x1e, PDT_TAPE, "Read dynamic runtime attribute"},
+    {0x1e, PDT_ADC, "Report automation device attributes"},
+    {0x1f, 0, "Maintenance in vendor specific"},
+    {0xffff, 0, NULL},
+};
+
+/* Maintenance out [0xa4] service actions */
+struct sg_lib_value_name_t sg_lib_maint_out_arr[] = {
+    {0x0, PDT_SAC, "Add peripheral device / component device"},
+    {0x1, PDT_SAC, "Attach to component device"},
+    {0x2, PDT_SAC, "Exchange p_extent"},
+    {0x3, PDT_SAC, "Exchange peripheral device / component device"},
+    {0x4, PDT_SAC, "Instruct component device"},
+    {0x5, PDT_SAC, "Remove peripheral device / component device"},
+    {0x6, 0, "Set identifying information"},
+                /* was "Set device identifier" prior to spc4r07 */
+    {0x7, PDT_SAC, "Break peripheral device / component device"},
+    {0xa, 0, "Set target port groups"},
+    {0xb, 0, "Change aliases"},
+    {0xc, 0, "Remove I_T nexus"},
+    {0xe, 0, "Set priority"},
+    {0xf, 0, "Set timestamp"},
+    {0x10, 0, "Management protocol out"},
+    {0x1d, PDT_TAPE, "Generate recommended access order"},
+    {0x1e, PDT_TAPE, "write dynamic runtime attribute"},
+    {0x1e, PDT_ADC, "Set automation device attributes"},
+    {0x1f, 0, "Maintenance out vendor specific"},
+    {0xffff, 0, NULL},
+};
+
+/* Sanitize [0x48] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = {
+    {0x1, 0, "overwrite"},
+    {0x2, 0, "block erase"},
+    {0x3, 0, "cryptographic erase"},
+    {0x1f, 0, "exit failure mode"},
+    {0xffff, 0, NULL},
+};
+
+/* Service action in(12) [0xab] service actions */
+struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = {
+    {0x1, 0, "Read media serial number"},
+    {0xffff, 0, NULL},
+};
+
+/* Service action out(12) [0xa9] service actions */
+struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = {
+    {0xff, 0, "Impossible command name"},
+    {0xffff, 0, NULL},
+};
+
+/* Service action in(16) [0x9e] service actions */
+struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = {
+    {0xf, 0, "Receive binding report"}, /* added spc5r11 */
+    {0x10, 0, "Read capacity(16)"},
+    {0x11, 0, "Read long(16)"},         /* obsolete in SBC-4 r7 */
+    {0x12, 0, "Get LBA status(16)"},    /* 32 byte variant added in sbc4r14 */
+    {0x13, 0, "Report referrals"},
+    {0x14, 0, "Stream control"},
+    {0x15, 0, "Background control"},
+    {0x16, 0, "Get stream status"},
+    {0x17, 0, "Get physical element status"},   /* added sbc4r13 */
+    {0x18, 0, "Remove element and truncate"},   /* added sbc4r13 */
+    {0xffff, 0, NULL},
+};
+
+/* Service action out(16) [0x9f] service actions */
+struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = {
+    {0x0b, 0, "Test bind"},             /* added spc5r13 */
+    {0x0c, 0, "Prepare bind report"},   /* added spc5r11 */
+    {0x0d, 0, "Set affiliation"},
+    {0x0e, 0, "Bind"},
+    {0x0f, 0, "Unbind"},
+    {0x11, 0, "Write long(16)"},
+    {0x12, 0, "Write scattered(16)"},   /* added sbc4r11 */
+    {0x14, PDT_ZBC, "Reset write pointer"},
+    {0x1f, PDT_ADC, "Notify data transfer device(16)"},
+    {0xffff, 0, NULL},
+};
+
+/* Service action bidirectional [0x9d] service actions */
+struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+/* Persistent reserve in [0x5e] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_pr_in_arr[] = {
+    {0x0, 0, "read keys"},
+    {0x1, 0, "read reservation"},
+    {0x2, 0, "report capabilities"},
+    {0x3, 0, "read full status"},
+    {0xffff, 0, NULL},
+};
+
+/* Persistent reserve out [0x5f] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_pr_out_arr[] = {
+    {0x0, 0, "register"},
+    {0x1, 0, "reserve"},
+    {0x2, 0, "release"},
+    {0x3, 0, "clear"},
+    {0x4, 0, "preempt"},
+    {0x5, 0, "preempt and abort"},
+    {0x6, 0, "register and ignore existing key"},
+    {0x7, 0, "register and move"},
+    {0x8, 0, "replace lost reservation"},
+    {0xffff, 0, NULL},
+};
+
+/* Third party copy in [0x83] service actions
+ * Opcode 'Receive copy results' was renamed 'Third party copy in' in spc4r34
+ * LID1 is an abbreviation of List Identifier length of 1 byte */
+struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = {
+    {0x0, 0, "Extended copy(LID1)"},
+    {0x1, 0, "Extended copy(LID4)"},
+    {0x10, 0, "Populate token"},
+    {0x11, 0, "Write using token"},
+    {0x1c, 0, "Copy operation abort"},
+    {0xffff, 0, NULL},
+};
+
+/* Third party copy out [0x84] service actions
+ * Opcode 'Extended copy' was renamed 'Third party copy out' in spc4r34
+ * LID4 is an abbreviation of List Identifier length of 4 bytes */
+struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = {
+    {0x0, 0, "Receive copy status(LID1)"},
+    {0x1, 0, "Receive copy data(LID1)"},
+    {0x3, 0, "Receive copy operating parameters"},
+    {0x4, 0, "Receive copy failure details(LID1)"},
+    {0x5, 0, "Receive copy status(LID4)"},
+    {0x6, 0, "Receive copy data(LID4)"},
+    {0x7, 0, "Receive ROD token information"},
+    {0x8, 0, "Report all ROD tokens"},
+    {0xffff, 0, NULL},
+};
+
+/* Variable length cdb [0x7f] service actions (more than 16 bytes long) */
+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 */
+    {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 */
+    {0x8, 0, "Xdwrite extended(64)"},   /* obsolete in SBC-4 r15 */
+    {0x9, 0, "Read(32)"},
+    {0xa, 0, "Verify(32)"},
+    {0xb, 0, "Write(32)"},
+    {0xc, 0, "Write and verify(32)"},
+    {0xd, 0, "Write same(32)"},
+    {0xe, 0, "Orwrite(32)"},            /* added sbc3r25 */
+    {0xf, 0, "Atomic write(32)"},       /* added sbc4r02 */
+    {0x10, 0, "Write stream(32)"},      /* added sbc4r07 */
+    {0x11, 0, "Write scattered(32)"},   /* added sbc4r11 */
+    {0x12, 0, "Get LBA status(32)"},    /* added sbc4r14 */
+    {0x1800, 0, "Receive credential"},
+    {0x1ff0, 0, "ATA pass-through(32)"},/* added sat4r05 */
+    {0x8801, 0, "Format OSD (osd)"},
+    {0x8802, 0, "Create (osd)"},
+    {0x8803, 0, "List (osd)"},
+    {0x8805, 0, "Read (osd)"},
+    {0x8806, 0, "Write (osd)"},
+    {0x8807, 0, "Append (osd)"},
+    {0x8808, 0, "Flush (osd)"},
+    {0x880a, 0, "Remove (osd)"},
+    {0x880b, 0, "Create partition (osd)"},
+    {0x880c, 0, "Remove partition (osd)"},
+    {0x880e, 0, "Get attributes (osd)"},
+    {0x880f, 0, "Set attributes (osd)"},
+    {0x8812, 0, "Create and write (osd)"},
+    {0x8815, 0, "Create collection (osd)"},
+    {0x8816, 0, "Remove collection (osd)"},
+    {0x8817, 0, "List collection (osd)"},
+    {0x8818, 0, "Set key (osd)"},
+    {0x8819, 0, "Set master key (osd)"},
+    {0x881a, 0, "Flush collection (osd)"},
+    {0x881b, 0, "Flush partition (osd)"},
+    {0x881c, 0, "Flush OSD (osd)"},
+    {0x8880, 0, "Object structure check (osd-2)"},
+    {0x8881, 0, "Format OSD (osd-2)"},
+    {0x8882, 0, "Create (osd-2)"},
+    {0x8883, 0, "List (osd-2)"},
+    {0x8884, 0, "Punch (osd-2)"},
+    {0x8885, 0, "Read (osd-2)"},
+    {0x8886, 0, "Write (osd-2)"},
+    {0x8887, 0, "Append (osd-2)"},
+    {0x8888, 0, "Flush (osd-2)"},
+    {0x8889, 0, "Clear (osd-2)"},
+    {0x888a, 0, "Remove (osd-2)"},
+    {0x888b, 0, "Create partition (osd-2)"},
+    {0x888c, 0, "Remove partition (osd-2)"},
+    {0x888e, 0, "Get attributes (osd-2)"},
+    {0x888f, 0, "Set attributes (osd-2)"},
+    {0x8892, 0, "Create and write (osd-2)"},
+    {0x8895, 0, "Create collection (osd-2)"},
+    {0x8896, 0, "Remove collection (osd-2)"},
+    {0x8897, 0, "List collection (osd-2)"},
+    {0x8898, 0, "Set key (osd-2)"},
+    {0x8899, 0, "Set master key (osd-2)"},
+    {0x889a, 0, "Flush collection (osd-2)"},
+    {0x889b, 0, "Flush partition (osd-2)"},
+    {0x889c, 0, "Flush OSD (osd-2)"},
+    {0x88a0, 0, "Query (osd-2)"},
+    {0x88a1, 0, "Remove member objects (osd-2)"},
+    {0x88a2, 0, "Get member attributes (osd-2)"},
+    {0x88a3, 0, "Set member attributes (osd-2)"},
+    {0x88b1, 0, "Read map (osd-2)"},
+    {0x8f7c, 0, "Perform SCSI command (osd-2)"},
+    {0x8f7d, 0, "Perform task management function (osd-2)"},
+    {0x8f7e, 0, "Perform SCSI command (osd)"},
+    {0x8f7f, 0, "Perform task management function (osd)"},
+    {0xffff, 0, NULL},
+};
+
+/* Zoning out [0x94] service actions */
+struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = {
+    {0x1, PDT_ZBC, "Close zone"},
+    {0x2, PDT_ZBC, "Finish zone"},
+    {0x3, PDT_ZBC, "Open zone"},
+    {0x4, PDT_ZBC, "Reset write pointer"},
+    {0xffff, 0, NULL},
+};
+
+/* Zoning in [0x95] service actions */
+struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = {
+    {0x0, PDT_ZBC, "Report zones"},
+    {0xffff, 0, NULL},
+};
+
+/* Read attribute [0x8c] service actions */
+struct sg_lib_value_name_t sg_lib_read_attr_arr[] = {
+    {0x0, 0, "attribute values"},
+    {0x1, 0, "attribute list"},
+    {0x2, 0, "logical volume list"},
+    {0x3, 0, "partition list"},
+    {0x5, 0, "supported attributes"},
+    {0xffff, 0, NULL},
+};
+
+#else   /* SG_SCSI_STRINGS */
+
+struct sg_lib_value_name_t sg_lib_normal_opcodes[] = {
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_read_buff_arr[] = {  /* opcode 0x3c */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_write_buff_arr[] = {  /* opcode 0x3b */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_maint_in_arr[] = {  /* opcode 0xa3 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_maint_out_arr[] = {  /* opcode 0xa4 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = {  /* opcode 0x94 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { /* opcode 0xab */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { /* opcode 0xa9 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { /* opcode 0x9e */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { /* opcode 0x9f */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { /* opcode 0x9d */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { /* opcode 0x5e */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { /* opcode 0x5f */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { /* opcode 0x83 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { /* opcode 0x84 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_variable_length_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_read_attr_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+#endif  /* SG_SCSI_STRINGS */
+
+/* A conveniently formatted list of SCSI ASC/ASCQ codes and their
+ * corresponding text can be found at: www.t10.org/lists/asc-num.txt
+ * The following should match asc-num.txt dated 20150423 */
+
+#ifdef SG_SCSI_STRINGS
+struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] =
+{
+    {0x40,0x01,0x7f,"Ram failure [0x%x]"},
+    {0x40,0x80,0xff,"Diagnostic failure on component [0x%x]"},
+    {0x41,0x01,0xff,"Data path failure [0x%x]"},
+    {0x42,0x01,0xff,"Power-on or self-test failure [0x%x]"},
+    {0x4d,0x00,0xff,"Tagged overlapped commands [0x%x]"},
+    {0x70,0x00,0xff,"Decompression exception short algorithm id of 0x%x"},
+    {0, 0, 0, NULL}
+};
+
+struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] =
+{
+    {0x00,0x00,"No additional sense information"},
+    {0x00,0x01,"Filemark detected"},
+    {0x00,0x02,"End-of-partition/medium detected"},
+    {0x00,0x03,"Setmark detected"},
+    {0x00,0x04,"Beginning-of-partition/medium detected"},
+    {0x00,0x05,"End-of-data detected"},
+    {0x00,0x06,"I/O process terminated"},
+    {0x00,0x07,"Programmable early warning detected"},
+    {0x00,0x11,"Audio play operation in progress"},
+    {0x00,0x12,"Audio play operation paused"},
+    {0x00,0x13,"Audio play operation successfully completed"},
+    {0x00,0x14,"Audio play operation stopped due to error"},
+    {0x00,0x15,"No current audio status to return"},
+    {0x00,0x16,"operation in progress"},
+    {0x00,0x17,"Cleaning requested"},
+    {0x00,0x18,"Erase operation in progress"},
+    {0x00,0x19,"Locate operation in progress"},
+    {0x00,0x1a,"Rewind operation in progress"},
+    {0x00,0x1b,"Set capacity operation in progress"},
+    {0x00,0x1c,"Verify operation in progress"},
+    {0x00,0x1d,"ATA pass through information available"},
+    {0x00,0x1e,"Conflicting SA creation request"},
+    {0x00,0x1f,"Logical unit transitioning to another power condition"},
+    {0x00,0x20,"Extended copy information available"},
+    {0x00,0x21,"Atomic command aborted due to ACA"},
+    {0x00,0x22,"Deferred microcode is pending"},
+    {0x01,0x00,"No index/sector signal"},
+    {0x02,0x00,"No seek complete"},
+    {0x03,0x00,"Peripheral device write fault"},
+    {0x03,0x01,"No write current"},
+    {0x03,0x02,"Excessive write errors"},
+    {0x04,0x00,"Logical unit not ready, cause not reportable"},
+    {0x04,0x01,"Logical unit is in process of becoming ready"},
+    {0x04,0x02,"Logical unit not ready, "
+                "initializing command required"},
+    {0x04,0x03,"Logical unit not ready, "
+                "manual intervention required"},
+    {0x04,0x04,"Logical unit not ready, format in progress"},
+    {0x04,0x05,"Logical unit not ready, rebuild in progress"},
+    {0x04,0x06,"Logical unit not ready, recalculation in progress"},
+    {0x04,0x07,"Logical unit not ready, operation in progress"},
+    {0x04,0x08,"Logical unit not ready, long write in progress"},
+    {0x04,0x09,"Logical unit not ready, self-test in progress"},
+    {0x04,0x0a,"Logical unit "
+                "not accessible, asymmetric access state transition"},
+    {0x04,0x0b,"Logical unit "
+                "not accessible, target port in standby state"},
+    {0x04,0x0c,"Logical unit "
+                "not accessible, target port in unavailable state"},
+    {0x04,0x0d,"Logical unit not ready, structure check required"},
+    {0x04,0x0e,"Logical unit not ready, security session in progress"},
+    {0x04,0x10,"Logical unit not ready, "
+                "auxiliary memory not accessible"},
+    {0x04,0x11,"Logical unit not ready, "
+                "notify (enable spinup) required"},
+    {0x04,0x12,"Logical unit not ready, offline"},
+    {0x04,0x13,"Logical unit not ready, SA creation in progress"},
+    {0x04,0x14,"Logical unit not ready, space allocation in progress"},
+    {0x04,0x15,"Logical unit not ready, robotics disabled"},
+    {0x04,0x16,"Logical unit not ready, configuration required"},
+    {0x04,0x17,"Logical unit not ready, calibration required"},
+    {0x04,0x18,"Logical unit not ready, a door is open"},
+    {0x04,0x19,"Logical unit not ready, operating in sequential mode"},
+    {0x04,0x1a,"Logical unit not ready, start stop unit command in progress"},
+    {0x04,0x1b,"Logical unit not ready, sanitize in progress"},
+    {0x04,0x1c,"Logical unit not ready, additional power use not yet "
+                "granted"},
+    {0x04,0x1d,"Logical unit not ready, configuration in progress"},
+    {0x04,0x1e,"Logical unit not ready, microcode activation required"},
+    {0x04,0x1f,"Logical unit not ready, microcode download required"},
+    {0x04,0x20,"Logical unit not ready, logical unit reset required"},
+    {0x04,0x21,"Logical unit not ready, hard reset required"},
+    {0x04,0x22,"Logical unit not ready, power cycle required"},
+    {0x04,0x23,"Logical unit not ready, affiliation required"},
+    {0x05,0x00,"Logical unit does not respond to selection"},
+    {0x06,0x00,"No reference position found"},
+    {0x07,0x00,"Multiple peripheral devices selected"},
+    {0x08,0x00,"Logical unit communication failure"},
+    {0x08,0x01,"Logical unit communication time-out"},
+    {0x08,0x02,"Logical unit communication parity error"},
+    {0x08,0x03,"Logical unit communication CRC error (Ultra-DMA/32)"},
+    {0x08,0x04,"Unreachable copy target"},
+    {0x09,0x00,"Track following error"},
+    {0x09,0x01,"Tracking servo failure"},
+    {0x09,0x02,"Focus servo failure"},
+    {0x09,0x03,"Spindle servo failure"},
+    {0x09,0x04,"Head select fault"},
+    {0x09,0x05,"Vibration induced tracking error"},
+    {0x0A,0x00,"Error log overflow"},
+    {0x0B,0x00,"Warning"},
+    {0x0B,0x01,"Warning - specified temperature exceeded"},
+    {0x0B,0x02,"Warning - enclosure degraded"},
+    {0x0B,0x03,"Warning - background self-test failed"},
+    {0x0B,0x04,"Warning - background pre-scan detected medium error"},
+    {0x0B,0x05,"Warning - background medium scan detected medium error"},
+    {0x0B,0x06,"Warning - non-volatile cache now volatile"},
+    {0x0B,0x07,"Warning - degraded power to non-volatile cache"},
+    {0x0B,0x08,"Warning - power loss expected"},
+    {0x0B,0x09,"Warning - device statistics notification active"},
+    {0x0B,0x0A,"Warning - high critical temperature limit exceeded"},
+    {0x0B,0x0B,"Warning - low critical temperature limit exceeded"},
+    {0x0B,0x0C,"Warning - high operating temperature limit exceeded"},
+    {0x0B,0x0D,"Warning - low operating temperature limit exceeded"},
+    {0x0B,0x0E,"Warning - high critical humidity limit exceeded"},
+    {0x0B,0x0F,"Warning - low critical humidity limit exceeded"},
+    {0x0B,0x10,"Warning - high operating humidity limit exceeded"},
+    {0x0B,0x11,"Warning - low operating humidity limit exceeded"},
+    {0x0B,0x12,"Warning - microcode security at risk"},
+    {0x0B,0x13,"Warning - microcode digital signature validation failure"},
+    {0x0C,0x00,"Write error"},
+    {0x0C,0x01,"Write error - recovered with auto reallocation"},
+    {0x0C,0x02,"Write error - auto reallocation failed"},
+    {0x0C,0x03,"Write error - recommend reassignment"},
+    {0x0C,0x04,"Compression check miscompare error"},
+    {0x0C,0x05,"Data expansion occurred during compression"},
+    {0x0C,0x06,"Block not compressible"},
+    {0x0C,0x07,"Write error - recovery needed"},
+    {0x0C,0x08,"Write error - recovery failed"},
+    {0x0C,0x09,"Write error - loss of streaming"},
+    {0x0C,0x0A,"Write error - padding blocks added"},
+    {0x0C,0x0B,"Auxiliary memory write error"},
+    {0x0C,0x0C,"Write error - unexpected unsolicited data"},
+    {0x0C,0x0D,"Write error - not enough unsolicited data"},
+    {0x0C,0x0E,"Multiple write errors"},
+    {0x0C,0x0F,"Defects in error window"},
+    {0x0C,0x10,"Incomplete multiple atomic write operations"},
+    {0x0C,0x11,"Write error - recovery scan needed"},
+    {0x0C,0x12,"Write error - insufficient zone resources"},
+    {0x0D,0x00,"Error detected by third party temporary initiator"},
+    {0x0D,0x01,"Third party device failure"},
+    {0x0D,0x02,"Copy target device not reachable"},
+    {0x0D,0x03,"Incorrect copy target device type"},
+    {0x0D,0x04,"Copy target device data underrun"},
+    {0x0D,0x05,"Copy target device data overrun"},
+    {0x0E,0x00,"Invalid information unit"},
+    {0x0E,0x01,"Information unit too short"},
+    {0x0E,0x02,"Information unit too long"},
+    {0x0E,0x03,"Invalid field in command information unit"},
+    {0x10,0x00,"Id CRC or ECC error"},
+    {0x10,0x01,"Logical block guard check failed"},
+    {0x10,0x02,"Logical block application tag check failed"},
+    {0x10,0x03,"Logical block reference tag check failed"},
+    {0x10,0x04,"Logical block protection error on recover buffered data"},
+    {0x10,0x05,"Logical block protection method error"},
+    {0x11,0x00,"Unrecovered read error"},
+    {0x11,0x01,"Read retries exhausted"},
+    {0x11,0x02,"Error too long to correct"},
+    {0x11,0x03,"Multiple read errors"},
+    {0x11,0x04,"Unrecovered read error - auto reallocate failed"},
+    {0x11,0x05,"L-EC uncorrectable error"},
+    {0x11,0x06,"CIRC unrecovered error"},
+    {0x11,0x07,"Data re-synchronization error"},
+    {0x11,0x08,"Incomplete block read"},
+    {0x11,0x09,"No gap found"},
+    {0x11,0x0A,"Miscorrected error"},
+    {0x11,0x0B,"Unrecovered read error - recommend reassignment"},
+    {0x11,0x0C,"Unrecovered read error - recommend rewrite the data"},
+    {0x11,0x0D,"De-compression CRC error"},
+    {0x11,0x0E,"Cannot decompress using declared algorithm"},
+    {0x11,0x0F,"Error reading UPC/EAN number"},
+    {0x11,0x10,"Error reading ISRC number"},
+    {0x11,0x11,"Read error - loss of streaming"},
+    {0x11,0x12,"Auxiliary memory read error"},
+    {0x11,0x13,"Read error - failed retransmission request"},
+    {0x11,0x14,"Read error - LBA marked bad by application client"},
+    {0x11,0x15,"Write after sanitize required"},
+    {0x12,0x00,"Address mark not found for id field"},
+    {0x13,0x00,"Address mark not found for data field"},
+    {0x14,0x00,"Recorded entity not found"},
+    {0x14,0x01,"Record not found"},
+    {0x14,0x02,"Filemark or setmark not found"},
+    {0x14,0x03,"End-of-data not found"},
+    {0x14,0x04,"Block sequence error"},
+    {0x14,0x05,"Record not found - recommend reassignment"},
+    {0x14,0x06,"Record not found - data auto-reallocated"},
+    {0x14,0x07,"Locate operation failure"},
+    {0x15,0x00,"Random positioning error"},
+    {0x15,0x01,"Mechanical positioning error"},
+    {0x15,0x02,"Positioning error detected by read of medium"},
+    {0x16,0x00,"Data synchronization mark error"},
+    {0x16,0x01,"Data sync error - data rewritten"},
+    {0x16,0x02,"Data sync error - recommend rewrite"},
+    {0x16,0x03,"Data sync error - data auto-reallocated"},
+    {0x16,0x04,"Data sync error - recommend reassignment"},
+    {0x17,0x00,"Recovered data with no error correction applied"},
+    {0x17,0x01,"Recovered data with retries"},
+    {0x17,0x02,"Recovered data with positive head offset"},
+    {0x17,0x03,"Recovered data with negative head offset"},
+    {0x17,0x04,"Recovered data with retries and/or circ applied"},
+    {0x17,0x05,"Recovered data using previous sector id"},
+    {0x17,0x06,"Recovered data without ECC - data auto-reallocated"},
+    {0x17,0x07,"Recovered data without ECC - recommend reassignment"},
+    {0x17,0x08,"Recovered data without ECC - recommend rewrite"},
+    {0x17,0x09,"Recovered data without ECC - data rewritten"},
+    {0x18,0x00,"Recovered data with error correction applied"},
+    {0x18,0x01,"Recovered data with error corr. & retries applied"},
+    {0x18,0x02,"Recovered data - data auto-reallocated"},
+    {0x18,0x03,"Recovered data with CIRC"},
+    {0x18,0x04,"Recovered data with L-EC"},
+    {0x18,0x05,"Recovered data - recommend reassignment"},
+    {0x18,0x06,"Recovered data - recommend rewrite"},
+    {0x18,0x07,"Recovered data with ECC - data rewritten"},
+    {0x18,0x08,"Recovered data with linking"},
+    {0x19,0x00,"Defect list error"},
+    {0x19,0x01,"Defect list not available"},
+    {0x19,0x02,"Defect list error in primary list"},
+    {0x19,0x03,"Defect list error in grown list"},
+    {0x1A,0x00,"Parameter list length error"},
+    {0x1B,0x00,"Synchronous data transfer error"},
+    {0x1C,0x00,"Defect list not found"},
+    {0x1C,0x01,"Primary defect list not found"},
+    {0x1C,0x02,"Grown defect list not found"},
+    {0x1D,0x00,"Miscompare during verify operation"},
+    {0x1D,0x01,"Miscompare verify of unmapped lba"},
+    {0x1E,0x00,"Recovered id with ECC correction"},
+    {0x1F,0x00,"Partial defect list transfer"},
+    {0x20,0x00,"Invalid command operation code"},
+    {0x20,0x01,"Access denied - initiator pending-enrolled"},
+    {0x20,0x02,"Access denied - no access rights"},
+    {0x20,0x03,"Access denied - invalid mgmt id key"},
+    {0x20,0x04,"Illegal command while in write capable state"},
+    {0x20,0x05,"Write type operation while in read capable state (obs)"},
+    {0x20,0x06,"Illegal command while in explicit address mode"},
+    {0x20,0x07,"Illegal command while in implicit address mode"},
+    {0x20,0x08,"Access denied - enrollment conflict"},
+    {0x20,0x09,"Access denied - invalid LU identifier"},
+    {0x20,0x0A,"Access denied - invalid proxy token"},
+    {0x20,0x0B,"Access denied - ACL LUN conflict"},
+    {0x20,0x0C,"Illegal command when not in append-only mode"},
+    {0x20,0x0D,"Not an administrative logical unit"},
+    {0x20,0x0E,"Not a subsidiary logical unit"},
+    {0x20,0x0F,"Not a conglomerate logical unit"},
+    {0x21,0x00,"Logical block address out of range"},
+    {0x21,0x01,"Invalid element address"},
+    {0x21,0x02,"Invalid address for write"},
+    {0x21,0x03,"Invalid write crossing layer jump"},
+    {0x21,0x04,"Unaligned write command"},
+    {0x21,0x05,"Write boundary violation"},
+    {0x21,0x06,"Attempt to read invalid data"},
+    {0x21,0x07,"Read boundary violation"},
+    {0x21,0x08,"Misaligned write command"},
+    {0x22,0x00,"Illegal function (use 20 00, 24 00, or 26 00)"},
+    {0x23,0x00,"Invalid token operation, cause not reportable"},
+    {0x23,0x01,"Invalid token operation, unsupported token type"},
+    {0x23,0x02,"Invalid token operation, remote token usage not supported"},
+    {0x23,0x03,"invalid token operation, remote rod token creation not "
+               "supported"},
+    {0x23,0x04,"Invalid token operation, token unknown"},
+    {0x23,0x05,"Invalid token operation, token corrupt"},
+    {0x23,0x06,"Invalid token operation, token revoked"},
+    {0x23,0x07,"Invalid token operation, token expired"},
+    {0x23,0x08,"Invalid token operation, token cancelled"},
+    {0x23,0x09,"Invalid token operation, token deleted"},
+    {0x23,0x0a,"Invalid token operation, invalid token length"},
+    {0x24,0x00,"Invalid field in cdb"},
+    {0x24,0x01,"CDB decryption error"},
+    {0x24,0x02,"Invalid cdb field while in explicit block model (obs)"},
+    {0x24,0x03,"Invalid cdb field while in implicit block model (obs)"},
+    {0x24,0x04,"Security audit value frozen"},
+    {0x24,0x05,"Security working key frozen"},
+    {0x24,0x06,"Nonce not unique"},
+    {0x24,0x07,"Nonce timestamp out of range"},
+    {0x24,0x08,"Invalid xcdb"},
+    {0x24,0x09,"Invalid fast format"},
+    {0x25,0x00,"Logical unit not supported"},
+    {0x26,0x00,"Invalid field in parameter list"},
+    {0x26,0x01,"Parameter not supported"},
+    {0x26,0x02,"Parameter value invalid"},
+    {0x26,0x03,"Threshold parameters not supported"},
+    {0x26,0x04,"Invalid release of persistent reservation"},
+    {0x26,0x05,"Data decryption error"},
+    {0x26,0x06,"Too many target descriptors"},
+    {0x26,0x07,"Unsupported target descriptor type code"},
+    {0x26,0x08,"Too many segment descriptors"},
+    {0x26,0x09,"Unsupported segment descriptor type code"},
+    {0x26,0x0A,"Unexpected inexact segment"},
+    {0x26,0x0B,"Inline data length exceeded"},
+    {0x26,0x0C,"Invalid operation for copy source or destination"},
+    {0x26,0x0D,"Copy segment granularity violation"},
+    {0x26,0x0E,"Invalid parameter while port is enabled"},
+    {0x26,0x0F,"Invalid data-out buffer integrity check value"},
+    {0x26,0x10,"Data decryption key fail limit reached"},
+    {0x26,0x11,"Incomplete key-associated data set"},
+    {0x26,0x12,"Vendor specific key reference not found"},
+    {0x26,0x13,"Application tag mode page is invalid"},
+    {0x26,0x14,"Tape stream mirroring prevented"},
+    {0x26,0x15,"Copy source or copy destination not authorized"},
+    {0x27,0x00,"Write protected"},
+    {0x27,0x01,"Hardware write protected"},
+    {0x27,0x02,"Logical unit software write protected"},
+    {0x27,0x03,"Associated write protect"},
+    {0x27,0x04,"Persistent write protect"},
+    {0x27,0x05,"Permanent write protect"},
+    {0x27,0x06,"Conditional write protect"},
+    {0x27,0x07,"Space allocation failed write protect"},
+    {0x27,0x08,"Zone is read only"},
+    {0x28,0x00,"Not ready to ready change, medium may have changed"},
+    {0x28,0x01,"Import or export element accessed"},
+    {0x28,0x02,"Format-layer may have changed"},
+    {0x28,0x03,"Import/export element accessed, medium changed"},
+    {0x29,0x00,"Power on, reset, or bus device reset occurred"},
+    {0x29,0x01,"Power on occurred"},
+    {0x29,0x02,"SCSI bus reset occurred"},
+    {0x29,0x03,"Bus device reset function occurred"},
+    {0x29,0x04,"Device internal reset"},
+    {0x29,0x05,"Transceiver mode changed to single-ended"},
+    {0x29,0x06,"Transceiver mode changed to lvd"},
+    {0x29,0x07,"I_T nexus loss occurred"},
+    {0x2A,0x00,"Parameters changed"},
+    {0x2A,0x01,"Mode parameters changed"},
+    {0x2A,0x02,"Log parameters changed"},
+    {0x2A,0x03,"Reservations preempted"},
+    {0x2A,0x04,"Reservations released"},
+    {0x2A,0x05,"Registrations preempted"},
+    {0x2A,0x06,"Asymmetric access state changed"},
+    {0x2A,0x07,"Implicit asymmetric access state transition failed"},
+    {0x2A,0x08,"Priority changed"},
+    {0x2A,0x09,"Capacity data has changed"},
+    {0x2A,0x0c, "Error recovery attributes have changed"},
+    {0x2A,0x0d, "Data encryption capabilities changed"},
+    {0x2A,0x10,"Timestamp changed"},
+    {0x2A,0x11,"Data encryption parameters changed by another i_t nexus"},
+    {0x2A,0x12,"Data encryption parameters changed by vendor specific event"},
+    {0x2A,0x13,"Data encryption key instance counter has changed"},
+    {0x2A,0x0a,"Error history i_t nexus cleared"},
+    {0x2A,0x0b,"Error history snapshot released"},
+    {0x2A,0x14,"SA creation capabilities data has changed"},
+    {0x2A,0x15,"Medium removal prevention preempted"},
+    {0x2A,0x16,"Zone reset write pointer recommended"},
+    {0x2B,0x00,"Copy cannot execute since host cannot disconnect"},
+    {0x2C,0x00,"Command sequence error"},
+    {0x2C,0x01,"Too many windows specified"},
+    {0x2C,0x02,"Invalid combination of windows specified"},
+    {0x2C,0x03,"Current program area is not empty"},
+    {0x2C,0x04,"Current program area is empty"},
+    {0x2C,0x05,"Illegal power condition request"},
+    {0x2C,0x06,"Persistent prevent conflict"},
+    {0x2C,0x07,"Previous busy status"},
+    {0x2C,0x08,"Previous task set full status"},
+    {0x2C,0x09,"Previous reservation conflict status"},
+    {0x2C,0x0A,"Partition or collection contains user objects"},
+    {0x2C,0x0B,"Not reserved"},
+    {0x2C,0x0C,"ORWRITE generation does not match"},
+    {0x2C,0x0D,"Reset write pointer not allowed"},
+    {0x2C,0x0E,"Zone is offline"},
+    {0x2C,0x0F,"Stream not open"},
+    {0x2C,0x10,"Unwritten data in zone"},
+    {0x2C,0x11,"Descriptor format sense data required"},
+    {0x2D,0x00,"Overwrite error on update in place"},
+    {0x2E,0x00,"Insufficient time for operation"},
+    {0x2E,0x01,"Command timeout before processing"},
+    {0x2E,0x02,"Command timeout during processing"},
+    {0x2E,0x03,"Command timeout during processing due to error recovery"},
+    {0x2F,0x00,"Commands cleared by another initiator"},
+    {0x2F,0x01,"Commands cleared by power loss notification"},
+    {0x2F,0x02,"Commands cleared by device server"},
+    {0x2F,0x03,"Some commands cleared by queuing layer event"},
+    {0x30,0x00,"Incompatible medium installed"},
+    {0x30,0x01,"Cannot read medium - unknown format"},
+    {0x30,0x02,"Cannot read medium - incompatible format"},
+    {0x30,0x03,"Cleaning cartridge installed"},
+    {0x30,0x04,"Cannot write medium - unknown format"},
+    {0x30,0x05,"Cannot write medium - incompatible format"},
+    {0x30,0x06,"Cannot format medium - incompatible medium"},
+    {0x30,0x07,"Cleaning failure"},
+    {0x30,0x08,"Cannot write - application code mismatch"},
+    {0x30,0x09,"Current session not fixated for append"},
+    {0x30,0x0A,"Cleaning request rejected"},
+    {0x30,0x0B,"Cleaning tape expired"},
+    {0x30,0x0C,"WORM medium - overwrite attempted"},
+    {0x30,0x0D,"WORM medium - integrity check"},
+    {0x30,0x10,"Medium not formatted"},
+    {0x30,0x11,"Incompatible volume type"},
+    {0x30,0x12,"Incompatible volume qualifier"},
+    {0x30,0x13,"Cleaning volume expired"},
+    {0x31,0x00,"Medium format corrupted"},
+    {0x31,0x01,"Format command failed"},
+    {0x31,0x02,"Zoned formatting failed due to spare linking"},
+    {0x31,0x03,"Sanitize command failed"},
+    {0x32,0x00,"No defect spare location available"},
+    {0x32,0x01,"Defect list update failure"},
+    {0x33,0x00,"Tape length error"},
+    {0x34,0x00,"Enclosure failure"},
+    {0x35,0x00,"Enclosure services failure"},
+    {0x35,0x01,"Unsupported enclosure function"},
+    {0x35,0x02,"Enclosure services unavailable"},
+    {0x35,0x03,"Enclosure services transfer failure"},
+    {0x35,0x04,"Enclosure services transfer refused"},
+    {0x35,0x05,"Enclosure services checksum error"},
+    {0x36,0x00,"Ribbon, ink, or toner failure"},
+    {0x37,0x00,"Rounded parameter"},
+    {0x38,0x00,"Event status notification"},
+    {0x38,0x02,"Esn - power management class event"},
+    {0x38,0x04,"Esn - media class event"},
+    {0x38,0x06,"Esn - device busy class event"},
+    {0x38,0x07,"Thin provisioning soft threshold reached"},
+    {0x39,0x00,"Saving parameters not supported"},
+    {0x3A,0x00,"Medium not present"},
+    {0x3A,0x01,"Medium not present - tray closed"},
+    {0x3A,0x02,"Medium not present - tray open"},
+    {0x3A,0x03,"Medium not present - loadable"},
+    {0x3A,0x04,"Medium not present - medium auxiliary memory accessible"},
+    {0x3B,0x00,"Sequential positioning error"},
+    {0x3B,0x01,"Tape position error at beginning-of-medium"},
+    {0x3B,0x02,"Tape position error at end-of-medium"},
+    {0x3B,0x03,"Tape or electronic vertical forms unit not ready"},
+    {0x3B,0x04,"Slew failure"},
+    {0x3B,0x05,"Paper jam"},
+    {0x3B,0x06,"Failed to sense top-of-form"},
+    {0x3B,0x07,"Failed to sense bottom-of-form"},
+    {0x3B,0x08,"Reposition error"},
+    {0x3B,0x09,"Read past end of medium"},
+    {0x3B,0x0A,"Read past beginning of medium"},
+    {0x3B,0x0B,"Position past end of medium"},
+    {0x3B,0x0C,"Position past beginning of medium"},
+    {0x3B,0x0D,"Medium destination element full"},
+    {0x3B,0x0E,"Medium source element empty"},
+    {0x3B,0x0F,"End of medium reached"},
+    {0x3B,0x11,"Medium magazine not accessible"},
+    {0x3B,0x12,"Medium magazine removed"},
+    {0x3B,0x13,"Medium magazine inserted"},
+    {0x3B,0x14,"Medium magazine locked"},
+    {0x3B,0x15,"Medium magazine unlocked"},
+    {0x3B,0x16,"Mechanical positioning or changer error"},
+    {0x3B,0x17,"Read past end of user object"},
+    {0x3B,0x18,"Element disabled"},
+    {0x3B,0x19,"Element enabled"},
+    {0x3B,0x1a,"Data transfer device removed"},
+    {0x3B,0x1b,"Data transfer device inserted"},
+    {0x3B,0x1c,"Too many logical objects on partition to support operation"},
+    {0x3D,0x00,"Invalid bits in identify message"},
+    {0x3E,0x00,"Logical unit has not self-configured yet"},
+    {0x3E,0x01,"Logical unit failure"},
+    {0x3E,0x02,"Timeout on logical unit"},
+    {0x3E,0x03,"Logical unit failed self-test"},
+    {0x3E,0x04,"Logical unit unable to update self-test log"},
+    {0x3F,0x00,"Target operating conditions have changed"},
+    {0x3F,0x01,"Microcode has been changed"},
+    {0x3F,0x02,"Changed operating definition"},
+    {0x3F,0x03,"Inquiry data has changed"},
+    {0x3F,0x04,"Component device attached"},
+    {0x3F,0x05,"Device identifier changed"},
+    {0x3F,0x06,"Redundancy group created or modified"},
+    {0x3F,0x07,"Redundancy group deleted"},
+    {0x3F,0x08,"Spare created or modified"},
+    {0x3F,0x09,"Spare deleted"},
+    {0x3F,0x0A,"Volume set created or modified"},
+    {0x3F,0x0B,"Volume set deleted"},
+    {0x3F,0x0C,"Volume set deassigned"},
+    {0x3F,0x0D,"Volume set reassigned"},
+    {0x3F,0x0E,"Reported luns data has changed"},
+    {0x3F,0x0F,"Echo buffer overwritten"},
+    {0x3F,0x10,"Medium loadable"},
+    {0x3F,0x11,"Medium auxiliary memory accessible"},
+    {0x3F,0x12,"iSCSI IP address added"},
+    {0x3F,0x13,"iSCSI IP address removed"},
+    {0x3F,0x14,"iSCSI IP address changed"},
+    {0x3F,0x15,"Inspect referrals sense descriptors"},
+    {0x3F,0x16,"Microcode has been changed without reset"},
+    {0x3F,0x17,"Zone transition to full"},
+    {0x3F,0x18,"Bind completed"},
+    {0x3F,0x19,"Bind redirected"},
+    {0x3F,0x1A,"Subsidiary binding changed"},
+
+    /*
+     * ASC 0x40, 0x41 and 0x42 overridden by "additional2" array entries
+     * for ascq > 1. Preferred error message for this group is
+     * "Diagnostic failure on component nn (80h-ffh)".
+     */
+    {0x40,0x00,"Ram failure (should use 40 nn)"},
+    {0x41,0x00,"Data path failure (should use 40 nn)"},
+    {0x42,0x00,"Power-on or self-test failure (should use 40 nn)"},
+
+    {0x43,0x00,"Message error"},
+    {0x44,0x00,"Internal target failure"},
+    {0x44,0x01,"Persistent reservation information lost"},
+    {0x44,0x71,"ATA device failed Set Features"},
+    {0x45,0x00,"Select or reselect failure"},
+    {0x46,0x00,"Unsuccessful soft reset"},
+    {0x47,0x00,"SCSI parity error"},
+    {0x47,0x01,"Data phase CRC error detected"},
+    {0x47,0x02,"SCSI parity error detected during st data phase"},
+    {0x47,0x03,"Information unit iuCRC error detected"},
+    {0x47,0x04,"Asynchronous information protection error detected"},
+    {0x47,0x05,"Protocol service CRC error"},
+    {0x47,0x06,"Phy test function in progress"},
+    {0x47,0x7F,"Some commands cleared by iSCSI protocol event"},
+    {0x48,0x00,"Initiator detected error message received"},
+    {0x49,0x00,"Invalid message error"},
+    {0x4A,0x00,"Command phase error"},
+    {0x4B,0x00,"Data phase error"},
+    {0x4B,0x01,"Invalid target port transfer tag received"},
+    {0x4B,0x02,"Too much write data"},
+    {0x4B,0x03,"Ack/nak timeout"},
+    {0x4B,0x04,"Nak received"},
+    {0x4B,0x05,"Data offset error"},
+    {0x4B,0x06,"Initiator response timeout"},
+    {0x4B,0x07,"Connection lost"},
+    {0x4B,0x08,"Data-in buffer overflow - data buffer size"},
+    {0x4B,0x09,"Data-in buffer overflow - data buffer descriptor area"},
+    {0x4B,0x0A,"Data-in buffer error"},
+    {0x4B,0x0B,"Data-out buffer overflow - data buffer size"},
+    {0x4B,0x0C,"Data-out buffer overflow - data buffer descriptor area"},
+    {0x4B,0x0D,"Data-out buffer error"},
+    {0x4B,0x0E,"PCIe fabric error"},
+    {0x4B,0x0f,"PCIe completion timeout"},
+    {0x4B,0x10,"PCIe completer abort"},
+    {0x4B,0x11,"PCIe poisoned tlp received"},
+    {0x4B,0x12,"PCIe ecrc check failed"},
+    {0x4B,0x13,"PCIe unsupported request"},
+    {0x4B,0x14,"PCIe acs violation"},
+    {0x4B,0x15,"PCIe tlp prefix blocked"},
+    {0x4C,0x00,"Logical unit failed self-configuration"},
+    /*
+     * ASC 0x4D overridden by an "additional2" array entry
+     * so there is no need to have them here.
+     */
+    /* {0x4D,0x00,"Tagged overlapped commands (nn = queue tag)"}, */
+
+    {0x4E,0x00,"Overlapped commands attempted"},
+    {0x50,0x00,"Write append error"},
+    {0x50,0x01,"Write append position error"},
+    {0x50,0x02,"Position error related to timing"},
+    {0x51,0x00,"Erase failure"},
+    {0x51,0x01,"Erase failure - incomplete erase operation detected"},
+    {0x52,0x00,"Cartridge fault"},
+    {0x53,0x00,"Media load or eject failed"},
+    {0x53,0x01,"Unload tape failure"},
+    {0x53,0x02,"Medium removal prevented"},
+    {0x53,0x03,"Medium removal prevented by data transfer element"},
+    {0x53,0x04,"Medium thread or unthread failure"},
+    {0x53,0x05,"Volume identifier invalid"},
+    {0x53,0x06,"Volume identifier missing"},
+    {0x53,0x07,"Duplicate volume identifier"},
+    {0x53,0x08,"Element status unknown"},
+    {0x53,0x09,"Data transfer device error - load failed"},
+    {0x53,0x0A,"Data transfer device error - unload failed"},
+    {0x53,0x0B,"Data transfer device error - unload missing"},
+    {0x53,0x0C,"Data transfer device error - eject failed"},
+    {0x53,0x0D,"Data transfer device error - library communication failed"},
+    {0x54,0x00,"SCSI to host system interface failure"},
+    {0x55,0x00,"System resource failure"},
+    {0x55,0x01,"System buffer full"},
+    {0x55,0x02,"Insufficient reservation resources"},
+    {0x55,0x03,"Insufficient resources"},
+    {0x55,0x04,"Insufficient registration resources"},
+    {0x55,0x05,"Insufficient access control resources"},
+    {0x55,0x06,"Auxiliary memory out of space"},
+    {0x55,0x07,"Quota error"},
+    {0x55,0x08,"Maximum number of supplemental decryption keys exceeded"},
+    {0x55,0x09,"Medium auxiliary memory not accessible"},
+    {0x55,0x0a,"Data currently unavailable"},
+    {0x55,0x0b,"Insufficient power for operation"},
+    {0x55,0x0c,"Insufficient resources to create rod"},
+    {0x55,0x0d,"Insufficient resources to create rod token"},
+    {0x55,0x0e,"Insufficient zone resources"},
+    {0x55,0x0f,"Insufficient zone resources to complete write"},
+    {0x55,0x10,"Maximum number of streams open"},
+    {0x55,0x11,"Insufficient resources to bind"},
+    {0x57,0x00,"Unable to recover table-of-contents"},
+    {0x58,0x00,"Generation does not exist"},
+    {0x59,0x00,"Updated block read"},
+    {0x5A,0x00,"Operator request or state change input"},
+    {0x5A,0x01,"Operator medium removal request"},
+    {0x5A,0x02,"Operator selected write protect"},
+    {0x5A,0x03,"Operator selected write permit"},
+    {0x5B,0x00,"Log exception"},
+    {0x5B,0x01,"Threshold condition met"},
+    {0x5B,0x02,"Log counter at maximum"},
+    {0x5B,0x03,"Log list codes exhausted"},
+    {0x5C,0x00,"Rpl status change"},
+    {0x5C,0x01,"Spindles synchronized"},
+    {0x5C,0x02,"Spindles not synchronized"},
+    {0x5D,0x00,"Failure prediction threshold exceeded"},
+    {0x5D,0x01,"Media failure prediction threshold exceeded"},
+    {0x5D,0x02,"Logical unit failure prediction threshold exceeded"},
+    {0x5D,0x03,"spare area exhaustion prediction threshold exceeded"},
+    {0x5D,0x10,"Hardware impending failure general hard drive failure"},
+    {0x5D,0x11,"Hardware impending failure drive error rate too high" },
+    {0x5D,0x12,"Hardware impending failure data error rate too high" },
+    {0x5D,0x13,"Hardware impending failure seek error rate too high" },
+    {0x5D,0x14,"Hardware impending failure too many block reassigns"},
+    {0x5D,0x15,"Hardware impending failure access times too high" },
+    {0x5D,0x16,"Hardware impending failure start unit times too high" },
+    {0x5D,0x17,"Hardware impending failure channel parametrics"},
+    {0x5D,0x18,"Hardware impending failure controller detected"},
+    {0x5D,0x19,"Hardware impending failure throughput performance"},
+    {0x5D,0x1A,"Hardware impending failure seek time performance"},
+    {0x5D,0x1B,"Hardware impending failure spin-up retry count"},
+    {0x5D,0x1C,"Hardware impending failure drive calibration retry count"},
+    {0x5D,0x1D,"Hardware impending failure power loss protection circuit"},
+    {0x5D,0x20,"Controller impending failure general hard drive failure"},
+    {0x5D,0x21,"Controller impending failure drive error rate too high" },
+    {0x5D,0x22,"Controller impending failure data error rate too high" },
+    {0x5D,0x23,"Controller impending failure seek error rate too high" },
+    {0x5D,0x24,"Controller impending failure too many block reassigns"},
+    {0x5D,0x25,"Controller impending failure access times too high" },
+    {0x5D,0x26,"Controller impending failure start unit times too high" },
+    {0x5D,0x27,"Controller impending failure channel parametrics"},
+    {0x5D,0x28,"Controller impending failure controller detected"},
+    {0x5D,0x29,"Controller impending failure throughput performance"},
+    {0x5D,0x2A,"Controller impending failure seek time performance"},
+    {0x5D,0x2B,"Controller impending failure spin-up retry count"},
+    {0x5D,0x2C,"Controller impending failure drive calibration retry count"},
+    {0x5D,0x30,"Data channel impending failure general hard drive failure"},
+    {0x5D,0x31,"Data channel impending failure drive error rate too high" },
+    {0x5D,0x32,"Data channel impending failure data error rate too high" },
+    {0x5D,0x33,"Data channel impending failure seek error rate too high" },
+    {0x5D,0x34,"Data channel impending failure too many block reassigns"},
+    {0x5D,0x35,"Data channel impending failure access times too high" },
+    {0x5D,0x36,"Data channel impending failure start unit times too high" },
+    {0x5D,0x37,"Data channel impending failure channel parametrics"},
+    {0x5D,0x38,"Data channel impending failure controller detected"},
+    {0x5D,0x39,"Data channel impending failure throughput performance"},
+    {0x5D,0x3A,"Data channel impending failure seek time performance"},
+    {0x5D,0x3B,"Data channel impending failure spin-up retry count"},
+    {0x5D,0x3C,"Data channel impending failure drive calibration retry count"},
+    {0x5D,0x40,"Servo impending failure general hard drive failure"},
+    {0x5D,0x41,"Servo impending failure drive error rate too high" },
+    {0x5D,0x42,"Servo impending failure data error rate too high" },
+    {0x5D,0x43,"Servo impending failure seek error rate too high" },
+    {0x5D,0x44,"Servo impending failure too many block reassigns"},
+    {0x5D,0x45,"Servo impending failure access times too high" },
+    {0x5D,0x46,"Servo impending failure start unit times too high" },
+    {0x5D,0x47,"Servo impending failure channel parametrics"},
+    {0x5D,0x48,"Servo impending failure controller detected"},
+    {0x5D,0x49,"Servo impending failure throughput performance"},
+    {0x5D,0x4A,"Servo impending failure seek time performance"},
+    {0x5D,0x4B,"Servo impending failure spin-up retry count"},
+    {0x5D,0x4C,"Servo impending failure drive calibration retry count"},
+    {0x5D,0x50,"Spindle impending failure general hard drive failure"},
+    {0x5D,0x51,"Spindle impending failure drive error rate too high" },
+    {0x5D,0x52,"Spindle impending failure data error rate too high" },
+    {0x5D,0x53,"Spindle impending failure seek error rate too high" },
+    {0x5D,0x54,"Spindle impending failure too many block reassigns"},
+    {0x5D,0x55,"Spindle impending failure access times too high" },
+    {0x5D,0x56,"Spindle impending failure start unit times too high" },
+    {0x5D,0x57,"Spindle impending failure channel parametrics"},
+    {0x5D,0x58,"Spindle impending failure controller detected"},
+    {0x5D,0x59,"Spindle impending failure throughput performance"},
+    {0x5D,0x5A,"Spindle impending failure seek time performance"},
+    {0x5D,0x5B,"Spindle impending failure spin-up retry count"},
+    {0x5D,0x5C,"Spindle impending failure drive calibration retry count"},
+    {0x5D,0x60,"Firmware impending failure general hard drive failure"},
+    {0x5D,0x61,"Firmware impending failure drive error rate too high" },
+    {0x5D,0x62,"Firmware impending failure data error rate too high" },
+    {0x5D,0x63,"Firmware impending failure seek error rate too high" },
+    {0x5D,0x64,"Firmware impending failure too many block reassigns"},
+    {0x5D,0x65,"Firmware impending failure access times too high" },
+    {0x5D,0x66,"Firmware impending failure start unit times too high" },
+    {0x5D,0x67,"Firmware impending failure channel parametrics"},
+    {0x5D,0x68,"Firmware impending failure controller detected"},
+    {0x5D,0x69,"Firmware impending failure throughput performance"},
+    {0x5D,0x6A,"Firmware impending failure seek time performance"},
+    {0x5D,0x6B,"Firmware impending failure spin-up retry count"},
+    {0x5D,0x6C,"Firmware impending failure drive calibration retry count"},
+    {0x5D,0x73,"Media impending failure endurance limit met"},
+    {0x5D,0xFF,"Failure prediction threshold exceeded (false)"},
+    {0x5E,0x00,"Low power condition on"},
+    {0x5E,0x01,"Idle condition activated by timer"},
+    {0x5E,0x02,"Standby condition activated by timer"},
+    {0x5E,0x03,"Idle condition activated by command"},
+    {0x5E,0x04,"Standby condition activated by command"},
+    {0x5E,0x05,"Idle_b condition activated by timer"},
+    {0x5E,0x06,"Idle_b condition activated by command"},
+    {0x5E,0x07,"Idle_c condition activated by timer"},
+    {0x5E,0x08,"Idle_c condition activated by command"},
+    {0x5E,0x09,"Standby_y condition activated by timer"},
+    {0x5E,0x0a,"Standby_y condition activated by command"},
+    {0x5E,0x41,"Power state change to active"},
+    {0x5E,0x42,"Power state change to idle"},
+    {0x5E,0x43,"Power state change to standby"},
+    {0x5E,0x45,"Power state change to sleep"},
+    {0x5E,0x47,"Power state change to device control"},
+    {0x60,0x00,"Lamp failure"},
+    {0x61,0x00,"Video acquisition error"},
+    {0x61,0x01,"Unable to acquire video"},
+    {0x61,0x02,"Out of focus"},
+    {0x62,0x00,"Scan head positioning error"},
+    {0x63,0x00,"End of user area encountered on this track"},
+    {0x63,0x01,"Packet does not fit in available space"},
+    {0x64,0x00,"Illegal mode for this track"},
+    {0x64,0x01,"Invalid packet size"},
+    {0x65,0x00,"Voltage fault"},
+    {0x66,0x00,"Automatic document feeder cover up"},
+    {0x66,0x01,"Automatic document feeder lift up"},
+    {0x66,0x02,"Document jam in automatic document feeder"},
+    {0x66,0x03,"Document miss feed automatic in document feeder"},
+    {0x67,0x00,"Configuration failure"},
+    {0x67,0x01,"Configuration of incapable logical units failed"},
+    {0x67,0x02,"Add logical unit failed"},
+    {0x67,0x03,"Modification of logical unit failed"},
+    {0x67,0x04,"Exchange of logical unit failed"},
+    {0x67,0x05,"Remove of logical unit failed"},
+    {0x67,0x06,"Attachment of logical unit failed"},
+    {0x67,0x07,"Creation of logical unit failed"},
+    {0x67,0x08,"Assign failure occurred"},
+    {0x67,0x09,"Multiply assigned logical unit"},
+    {0x67,0x0A,"Set target port groups command failed"},
+    {0x67,0x0B,"ATA device feature not enabled"},
+    {0x67,0x0C,"Command rejected"},
+    {0x67,0x0D,"Explicit bind not allowed"},
+    {0x68,0x00,"Logical unit not configured"},
+    {0x68,0x01,"Subsidiary logical unit not configured"},
+    {0x69,0x00,"Data loss on logical unit"},
+    {0x69,0x01,"Multiple logical unit failures"},
+    {0x69,0x02,"Parity/data mismatch"},
+    {0x6A,0x00,"Informational, refer to log"},
+    {0x6B,0x00,"State change has occurred"},
+    {0x6B,0x01,"Redundancy level got better"},
+    {0x6B,0x02,"Redundancy level got worse"},
+    {0x6C,0x00,"Rebuild failure occurred"},
+    {0x6D,0x00,"Recalculate failure occurred"},
+    {0x6E,0x00,"Command to logical unit failed"},
+    {0x6F,0x00,"Copy protection key exchange failure - authentication "
+               "failure"},
+    {0x6F,0x01,"Copy protection key exchange failure - key not present"},
+    {0x6F,0x02,"Copy protection key exchange failure - key not established"},
+    {0x6F,0x03,"Read of scrambled sector without authentication"},
+    {0x6F,0x04,"Media region code is mismatched to logical unit region"},
+    {0x6F,0x05,"Drive region must be permanent/region reset count error"},
+    {0x6F,0x06,"Insufficient block count for binding nonce recording"},
+    {0x6F,0x07,"Conflict in binding nonce recording"},
+    {0x6F,0x08,"Insufficient permission"},
+    {0x6F,0x09,"Invalid drive-host pairing server"},
+    {0x6F,0x0A,"Drive-host pairing suspended"},
+    /*
+     * ASC 0x70 overridden by an "additional2" array entry
+     * so there is no need to have them here.
+     */
+    /* {0x70,0x00,"Decompression exception short algorithm id of nn"}, */
+
+    {0x71,0x00,"Decompression exception long algorithm id"},
+    {0x72,0x00,"Session fixation error"},
+    {0x72,0x01,"Session fixation error writing lead-in"},
+    {0x72,0x02,"Session fixation error writing lead-out"},
+    {0x72,0x03,"Session fixation error - incomplete track in session"},
+    {0x72,0x04,"Empty or partially written reserved track"},
+    {0x72,0x05,"No more track reservations allowed"},
+    {0x72,0x06,"RMZ extension is not allowed"},
+    {0x72,0x07,"No more test zone extensions are allowed"},
+    {0x73,0x00,"CD control error"},
+    {0x73,0x01,"Power calibration area almost full"},
+    {0x73,0x02,"Power calibration area is full"},
+    {0x73,0x03,"Power calibration area error"},
+    {0x73,0x04,"Program memory area update failure"},
+    {0x73,0x05,"Program memory area is full"},
+    {0x73,0x06,"RMA/PMA is almost full"},
+    {0x73,0x10,"Current power calibration area almost full"},
+    {0x73,0x11,"Current power calibration area is full"},
+    {0x73,0x17,"RDZ is full"},
+    {0x74,0x00,"Security error"},
+    {0x74,0x01,"Unable to decrypt data"},
+    {0x74,0x02,"Unencrypted data encountered while decrypting"},
+    {0x74,0x03,"Incorrect data encryption key"},
+    {0x74,0x04,"Cryptographic integrity validation failed"},
+    {0x74,0x05,"Error decrypting data"},
+    {0x74,0x06,"Unknown signature verification key"},
+    {0x74,0x07,"Encryption parameters not useable"},
+    {0x74,0x08,"Digital signature validation failure"},
+    {0x74,0x09,"Encryption mode mismatch on read"},
+    {0x74,0x0a,"Encrypted block not raw read enabled"},
+    {0x74,0x0b,"Incorrect Encryption parameters"},
+    {0x74,0x0c,"Unable to decrypt parameter list"},
+    {0x74,0x0d,"Encryption algorithm disabled"},
+    {0x74,0x10,"SA creation parameter value invalid"},
+    {0x74,0x11,"SA creation parameter value rejected"},
+    {0x74,0x12,"Invalid SA usage"},
+    {0x74,0x21,"Data encryption configuration prevented"},
+    {0x74,0x30,"SA creation parameter not supported"},
+    {0x74,0x40,"Authentication failed"},
+    {0x74,0x61,"External data encryption key manager access error"},
+    {0x74,0x62,"External data encryption key manager error"},
+    {0x74,0x63,"External data encryption key not found"},
+    {0x74,0x64,"External data encryption request not authorized"},
+    {0x74,0x6e,"External data encryption control timeout"},
+    {0x74,0x6f,"External data encryption control error"},
+    {0x74,0x71,"Logical unit access not authorized"},
+    {0x74,0x79,"Security conflict in translated device"},
+    {0, 0, NULL}
+};
+
+#else   /* SG_SCSI_STRINGS */
+
+struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] =
+{
+    {0, 0, 0, NULL}
+};
+
+struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] =
+{
+    {0, 0, NULL}
+};
+#endif /* SG_SCSI_STRINGS */
+
+const char * sg_lib_sense_key_desc[] = {
+    "No Sense",                 /* Filemark, ILI and/or EOM; progress
+                                   indication (during FORMAT); power
+                                   condition sensing (REQUEST SENSE) */
+    "Recovered Error",          /* The last command completed successfully
+                                   but used error correction */
+    "Not Ready",                /* The addressed target is not ready */
+    "Medium Error",             /* Data error detected on the medium */
+    "Hardware Error",           /* Controller or device failure */
+    "Illegal Request",
+    "Unit Attention",           /* Removable medium was changed, or
+                                   the target has been reset */
+    "Data Protect",             /* Access to the data is blocked */
+    "Blank Check",              /* Reached unexpected written or unwritten
+                                   region of the medium */
+    "Vendor specific(9)",       /* Vendor specific */
+    "Copy Aborted",             /* COPY or COMPARE was aborted */
+    "Aborted Command",          /* The target aborted the command */
+    "Equal",                    /* SEARCH DATA found data equal (obsolete) */
+    "Volume Overflow",          /* Medium full with data to be written */
+    "Miscompare",               /* Source data and data on the medium
+                                   do not agree */
+    "Completed"                 /* may occur for successful cmd (spc4r23) */
+};
+
+const char * sg_lib_pdt_strs[32] = {    /* should have 2**5 elements */
+    /* 0 */ "disk",
+    "tape",
+    "printer",                  /* obsolete, spc5r01 */
+    "processor",        /* often SAF-TE device, copy manager */
+    "write once optical disk",  /* obsolete, spc5r01 */
+    /* 5 */ "cd/dvd",
+    "scanner",                  /* obsolete */
+    "optical memory device",
+    "medium changer",
+    "communications",           /* obsolete */
+    /* 0xa */ "graphics [0xa]", /* obsolete */
+    "graphics [0xb]",           /* obsolete */
+    "storage array controller",
+    "enclosure services device",
+    "simplified direct access device",
+    "optical card reader/writer device",
+    /* 0x10 */ "bridge controller commands",
+    "object based storage",
+    "automation/driver interface",
+    "security manager device",  /* obsolete, spc5r01 */
+    "zoned block commands",
+    "0x15", "0x16", "0x17", "0x18",
+    "0x19", "0x1a", "0x1b", "0x1c", "0x1d",
+    "well known logical unit",
+    "no physical device on this lu",
+};
+
+const char * sg_lib_transport_proto_strs[] =
+{
+    "Fibre Channel Protocol for SCSI (FCP-4)",
+    "SCSI Parallel Interface (SPI-5)",  /* obsolete in spc5r01 */
+    "Serial Storage Architecture SCSI-3 Protocol (SSA-S3P)",
+    "Serial Bus Protocol for IEEE 1394 (SBP-3)",
+    "SCSI RDMA Protocol (SRP)",
+    "Internet SCSI (iSCSI)",
+    "Serial Attached SCSI Protocol (SPL-4)",
+    "Automation/Drive Interface Transport (ADT-2)",
+    "AT Attachment Interface (ACS-2)",          /* 0x8 */
+    "USB Attached SCSI (UAS-2)",
+    "SCSI over PCI Express (SOP)",
+    "PCIe",                             /* added in spc5r02 */
+    "Oxc", "Oxd", "Oxe",
+    "No specific protocol"
+};
+
+/* SCSI Feature Sets array. code->value, pdt->peri_dev_type (-1 for SPC) */
+struct sg_lib_value_name_t sg_lib_scsi_feature_sets[] =
+{
+    {SCSI_FS_SPC_DISCOVERY_2016, -1, "Discovery 2016"},
+    {SCSI_FS_SBC_BASE_2010, PDT_DISK, "SBC Base 2010"},
+    {SCSI_FS_SBC_BASE_2016, PDT_DISK, "SBC Base 2016"},
+    {SCSI_FS_SBC_BASIC_PROV_2016, PDT_DISK, "Basic provisioning 2016"},
+    {SCSI_FS_SBC_DRIVE_MAINT_2016, PDT_DISK, "Drive maintenance 2016"},
+    {0x0, 0, NULL},     /* 0x0 is reserved sfs; trailing sentinel */
+};
+
+#if (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME))
+
+/* .value is completion queue's DW3 as follows: ((DW3 >> 17) & 0x3ff)
+ * .peri_dev_type is an index for the sg_lib_scsi_status_sense_arr[]
+ * .name is taken from NVMe 1.3a document, section 4.6.1.2.1 with less
+ *   capitalization.
+ * NVMe term bits 31:17 of DW3 in the completion field as the "Status
+ * Field" (SF). Bit 31 is "Do not retry" (DNR) and bit 30 is "More" (M).
+ * Bits 29:28 are reserved, bit 27:25 are the "Status Code Type" (SCT)
+ * and bits 24:17 are the Status Code (SC). This table is in ascending
+ * order of its .value field so a binary search could be done on it.  */
+struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] =
+{
+    /* Generic command status values, Status Code Type (SCT): 0h
+     * Lowest 8 bits are the Status Code (SC), in this case:
+     *   00h - 7Fh: Applicable to Admin Command Set, or across multiple
+     *              command sets
+     *   80h - BFh: I/O Command Set Specific status codes
+     *   c0h - FFh: I/O Vendor Specific status codes            */
+    {0x0,   0, "Successful completion"},
+    {0x1,   1, "Invalid command opcode"},
+    {0x2,   2, "Invalid field in command"},
+    {0x3,   2, "Command id conflict"},
+    {0x4,   3, "Data transfer error"},
+    {0x5,   4, "Command aborted due to power loss notication"},
+    {0x6,   5, "Internal error"},
+    {0x7,   6, "Command abort requested"},
+    {0x8,   6, "Command aborted due to SQ deletion"},
+    {0x9,   6, "Command aborted due to failed fused command"},
+    {0xa,   6, "Command aborted due to missing fused command"},
+    {0xb,   7, "Invalid namespace or format"},
+    {0xc,   5, "Command sequence error"},
+    {0xd,   5, "Invalid SGL segment descriptor"},
+    {0xe,   5, "Invalid number of SGL descriptors"},
+    {0xf,   5, "Data SGL length invalid"},
+    {0x10,  5, "Matadata SGL length invalid"},
+    {0x11,  5, "SGL descriptor type invalid"},
+    {0x12,  5, "Invalid use of controller memory buffer"},
+    {0x13,  5, "PRP offset invalid"},
+    {0x14,  2, "Atomic write unit exceeded"},
+    {0x15,  8, "Operation denied"},
+    {0x16,  5, "SGL offset invalid"},
+    {0x17,  5, "Reserved [0x17]"},
+    {0x18,  5, "Host identifier inconsistent format"},
+    {0x19,  5, "Keep alive timeout expired"},
+    {0x1a,  5, "Keep alive timeout invalid"},
+    {0x1b,  6, "Command aborted due to Preempt and Abort"},
+    {0x1c, 10, "Sanitize failed"},
+    {0x1d, 11, "Sanitize in progress"},
+    {0x1e,  5, "SGL data block granularity invalid"},
+    {0x1f,  5, "Command not supported for queue in CMB"},
+
+    /* Generic command status values, NVM (I/O) Command Set */
+    {0x80, 12, "LBA out of range"},
+    {0x81,  3, "Capacity exceeded"},
+    {0x82, 13, "Namespace not ready"},
+    {0x83, 14, "Reservation conflict"},
+    {0x84, 15, "Format in progress"},
+    /* 0xc0 - 0xff: vendor specific */
+
+    /* Command specific status values, Status Code Type (SCT): 1h */
+    {0x100, 5, "Completion queue invalid"},
+    {0x101, 5, "Invalid queue identifier"},
+    {0x102, 5, "Invalid queue size"},
+    {0x103, 5, "Abort command limit exceeded"},
+    {0x104, 5, "Reserved [0x104]"},
+    {0x105, 5, "Asynchronous event request limit exceeded"},
+    {0x106, 5, "Invalid firmware slot"},
+    {0x107, 5, "Invalid firmware image"},
+    {0x108, 5, "Invalid interrupt vector"},
+    {0x109, 5, "Invalid log page"},
+    {0x10a,16, "Invalid format"},
+    {0x10b, 5, "Firmware activation requires conventional reset"},
+    {0x10c, 5, "Invalid queue deletion"},
+    {0x10d, 5, "Feature identifier not saveable"},
+    {0x10e, 5, "Feature not changeable"},
+    {0x10f, 5, "Feature not namespace specific"},
+    {0x110, 5, "Firmware activation requires NVM subsystem reset"},
+    {0x111, 5, "Firmware activation requires reset"},
+    {0x112, 5, "Firmware activation requires maximum time violation"},
+    {0x113, 5, "Firmware activation prohibited"},
+    {0x114, 5, "Overlapping range"},
+    {0x115, 5, "Namespace insufficient capacity"},
+    {0x116, 5, "Namespace identifier unavailable"},
+    {0x117, 5, "Reserved [0x107]"},
+    {0x118, 5, "Namespace already attached"},
+    {0x119, 5, "Namespace is private"},
+    {0x11a, 5, "Namespace not attached"},
+    {0x11b, 3, "Thin provisioning not supported"},
+    {0x11c, 3, "Controller list invalid"},
+    {0x11d,17, "Device self-test in progress"},
+    {0x11e,18, "Boot partition write prohibited"},
+    {0x11f, 5, "Invalid controller identifier"},
+    {0x120, 5, "Invalid secondary controller state"},
+    {0x121, 5, "Invalid number of controller resorces"},
+    {0x122, 5, "Invalid resorce identifier"},
+
+    /* Command specific status values, Status Code Type (SCT): 1h
+     * for NVM (I/O) Command Set */
+    {0x180, 2, "Conflicting attributes"},
+    {0x181,19, "Invalid protection information"},
+    {0x182,18, "Attempted write to read only range"},
+    /* 0x1c0 - 0x1ff: vendor specific */
+
+    /* Media and Data Integrity error values, Status Code Type (SCT): 2h */
+    {0x280,20, "Write fault"},
+    {0x281,21, "Unrecovered read error"},
+    {0x282,22, "End-to-end guard check error"},
+    {0x283,23, "End-to-end application tag check error"},
+    {0x284,24, "End-to-end reference tag check error"},
+    {0x285,25, "Compare failure"},
+    {0x286, 8, "Access denied"},
+    {0x287,26, "Deallocated or unwritten logical block"},
+    /* 0x2c0 - 0x2ff: vendor specific */
+
+    /* Leave this Sentinel value at end of this array */
+    {0x3ff, 0, NULL},
+};
+
+/* The sg_lib_nvme_cmd_status_arr[n].peri_dev_type field is an index
+ * to this array. It allows an NVMe status (error) value to be mapped
+ * to this SCSI tuple: status, sense_key, additional sense code (asc) and
+ * asc qualifier (ascq). For brevity SAM_STAT_CHECK_CONDITION is written
+ * as 0x2. */
+struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] =
+{
+    {SAM_STAT_GOOD, SPC_SK_NO_SENSE, 0, 0},     /* it's all good */ /* 0 */
+    {SAM_STAT_CHECK_CONDITION, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x0},/* opcode */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x24, 0x0},   /* field in cdb */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x0, 0x0},
+    {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0xb, 0x8},
+    {0x2, SPC_SK_HARDWARE_ERROR, 0x44, 0x0},   /* internal error */ /* 5 */
+    {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0x0, 0x0},
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x9},   /* invalid LU */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x2},   /* access denied */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x2c, 0x0},   /* cmd sequence error */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x31, 0x3},   /* sanitize failed */ /* 10 */
+    {0x2, SPC_SK_NOT_READY, 0x4, 0x1b}, /* sanitize in progress */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x21, 0x0},   /* LBA out of range */
+    {0x2, SPC_SK_NOT_READY, 0x4, 0x0},  /* not reportable; 0x1: becoming */
+    {SAM_STAT_RESERVATION_CONFLICT, 0x0, 0x0, 0x0},
+    {0x2, SPC_SK_NOT_READY, 0x4, 0x4},  /* format in progress */  /* 15 */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x31, 0x1},  /* format failed */
+    {0x2, SPC_SK_NOT_READY, 0x4, 0x9},  /* self-test in progress */
+    {0x2, SPC_SK_DATA_PROTECT, 0x27, 0x0},      /* write prohibited */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x10, 0x5},  /* protection info */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x3, 0x0}, /* periph dev w fault */ /* 20 */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x11, 0x0},      /* unrecoc rd */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x1},      /* PI guard */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2},      /* PI app tag */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2},      /* PI app tag */
+    {0x2, SPC_SK_MISCOMPARE, 0x1d, 0x0},        /* during verify */ /* 25 */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x21, 0x6},      /* read invalid data */
+
+    /* Leave this Sentinel value at end of this array */
+    {0xff, 0xff, 0xff, 0xff},
+};
+
+
+#else           /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */
+struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] =
+{
+
+    /* Leave this Sentinel value at end of this array */
+    {0x3ff, 0, NULL},
+};
+
+struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] =
+{
+
+    /* Leave this Sentinel value at end of this array */
+    {0xff, 0xff, 0xff, 0xff},
+};
+
+#endif           /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */
diff --git a/tools/sg_write_buffer/sg_pt_common.c b/tools/sg_write_buffer/sg_pt_common.c
new file mode 100644
index 0000000..85bc191
--- /dev/null
+++ b/tools/sg_write_buffer/sg_pt_common.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2009-2018 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_pt_nvme.h"
+
+
+static const char * scsi_pt_version_str = "3.03 20180115";
+
+static const char * nvme_scsi_vendor_str = "NVMe    ";
+
+
+const char *
+scsi_pt_version()
+{
+    return scsi_pt_version_str;
+}
+
+/* Given the NVMe Identify controller response and optionally the NVMe
+ * Identify namespace response (NULL otherwise), generate the SCSI VPD
+ * page 0x83 (device identification) descriptor(s) in dop. Return the
+ * number of bytes written which will not exceed max_do_len. Probably use
+ * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport
+ * protocol (tproto) should be -1 if not known, else SCSI value.
+ * N.B. Does not write total VPD page length into dop[2:3] . */
+int
+sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p,
+                           const uint8_t * nvme_id_ns_p, int pdt,
+                           int tproto, uint8_t * dop, int max_do_len)
+{
+    bool have_nguid, have_eui64;
+    int k, n;
+    char b[4];
+
+    if ((NULL == nvme_id_ctl_p) || (NULL == dop) || (max_do_len < 56))
+        return 0;
+
+    memset(dop, 0, max_do_len);
+    dop[0] = 0x1f & pdt;  /* (PQ=0)<<5 | (PDT=pdt); 0 or 0xd (SES) */
+    dop[1] = 0x83;      /* Device Identification VPD page number */
+    /* Build a T10 Vendor ID based designator (desig_id=1) for controller */
+    if (tproto >= 0) {
+        dop[4] = ((0xf & tproto) << 4) | 0x2;
+        dop[5] = 0xa1; /* PIV=1, ASSOC=2 (target device), desig_id=1 */
+    } else {
+        dop[4] = 0x2;  /* Prococol id=0, code_set=2 (ASCII) */
+        dop[5] = 0x21; /* PIV=0, ASSOC=2 (target device), desig_id=1 */
+    }
+    memcpy(dop + 8, nvme_scsi_vendor_str, 8); /* N.B. this is "NVMe    " */
+    memcpy(dop + 16, nvme_id_ctl_p + 24, 40);  /* MN */
+    for (k = 40; k > 0; --k) {
+        if (' ' == dop[15 + k])
+            dop[15 + k] = '_'; /* convert trailing spaces */
+        else
+            break;
+    }
+    if (40 == k)
+        --k;
+    n = 16 + 1 + k;
+    if (max_do_len < (n + 20))
+        return 0;
+    memcpy(dop + n, nvme_id_ctl_p + 4, 20); /* SN */
+    for (k = 20; k > 0; --k) {  /* trim trailing spaces */
+        if (' ' == dop[n + k - 1])
+            dop[n + k - 1] = '\0';
+        else
+            break;
+    }
+    n += k;
+    if (0 != (n % 4))
+        n = ((n / 4) + 1) * 4;  /* round up to next modulo 4 */
+    dop[7] = n - 8;
+    if (NULL == nvme_id_ns_p)
+        return n;
+
+    /* Look for NGUID (16 byte identifier) or EUI64 (8 byte) fields in
+     * NVME Identify for namespace. If found form a EUI and a SCSI string
+     * descriptor for non-zero NGUID or EUI64 (prefer NGUID if both). */
+    have_nguid = ! sg_all_zeros(nvme_id_ns_p + 104, 16);
+    have_eui64 = ! sg_all_zeros(nvme_id_ns_p + 120, 8);
+    if ((! have_nguid) && (! have_eui64))
+        return n;
+    if (have_nguid) {
+        if (max_do_len < (n + 20))
+            return n;
+        dop[n + 0] = 0x1;  /* Prococol id=0, code_set=1 (binary) */
+        dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */
+        dop[n + 3] = 16;
+        memcpy(dop + n + 4, nvme_id_ns_p + 104, 16);
+        n += 20;
+        if (max_do_len < (n + 40))
+            return n;
+        dop[n + 0] = 0x3;  /* Prococol id=0, code_set=3 (utf8) */
+        dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */
+        dop[n + 3] = 36;
+        memcpy(dop + n + 4, "eui.", 4);
+        for (k = 0; k < 16; ++k) {
+            snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[104 + k]);
+            memcpy(dop + n + 8 + (2 * k), b, 2);
+        }
+        return n + 40;
+    } else {    /* have_eui64 is true, 8 byte identifier */
+        if (max_do_len < (n + 12))
+            return n;
+        dop[n + 0] = 0x1;  /* Prococol id=0, code_set=1 (binary) */
+        dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */
+        dop[n + 3] = 8;
+        memcpy(dop + n + 4, nvme_id_ns_p + 120, 8);
+        n += 12;
+        if (max_do_len < (n + 24))
+            return n;
+        dop[n + 0] = 0x3;  /* Prococol id=0, code_set=3 (utf8) */
+        dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */
+        dop[n + 3] = 20;
+        memcpy(dop + n + 4, "eui.", 4);
+        for (k = 0; k < 8; ++k) {
+            snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[120 + k]);
+            memcpy(dop + n + 8 + (2 * k), b, 2);
+        }
+        return n + 24;
+    }
+}
diff --git a/tools/sg_write_buffer/sg_pt_linux.c b/tools/sg_write_buffer/sg_pt_linux.c
new file mode 100644
index 0000000..22ea068
--- /dev/null
+++ b/tools/sg_write_buffer/sg_pt_linux.c
@@ -0,0 +1,964 @@
+/*
+ * Copyright (c) 2005-2018 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 version 1.37 20180126 */
+
+
+#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"
+
+
+#ifdef major
+#define SG_DEV_MAJOR major
+#else
+#ifdef HAVE_LINUX_KDEV_T_H
+#include <linux/kdev_t.h>
+#endif
+#define SG_DEV_MAJOR MAJOR  /* MAJOR() macro faulty if > 255 minors */
+#endif
+
+#ifndef BLOCK_EXT_MAJOR
+#define BLOCK_EXT_MAJOR 259
+#endif
+
+#define DEF_TIMEOUT 60000       /* 60,000 millisecs (60 seconds) */
+
+static const char * linux_host_bytes[] = {
+    "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT",
+    "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR",
+    "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR",
+    "DID_IMM_RETRY", "DID_REQUEUE" /* 0xd */,
+    "DID_TRANSPORT_DISRUPTED", "DID_TRANSPORT_FAILFAST",
+    "DID_TARGET_FAILURE" /* 0x10 */,
+    "DID_NEXUS_FAILURE (reservation conflict)",
+    "DID_ALLOC_FAILURE",
+    "DID_MEDIUM_ERROR",
+};
+
+#define LINUX_HOST_BYTES_SZ \
+        (int)(sizeof(linux_host_bytes) / sizeof(linux_host_bytes[0]))
+
+static const char * linux_driver_bytes[] = {
+    "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA",
+    "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD",
+    "DRIVER_SENSE"
+};
+
+#define LINUX_DRIVER_BYTES_SZ \
+    (int)(sizeof(linux_driver_bytes) / sizeof(linux_driver_bytes[0]))
+
+#if 0
+static const char * linux_driver_suggests[] = {
+    "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP",
+    "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN",
+    "SUGGEST_SENSE"
+};
+
+#define LINUX_DRIVER_SUGGESTS_SZ \
+    (int)(sizeof(linux_driver_suggests) / sizeof(linux_driver_suggests[0]))
+#endif
+
+/*
+ * These defines are for constants that should be visible in the
+ * /usr/include/scsi directory (brought in by sg_linux_inc.h).
+ * Redefined and aliased here to decouple this code from
+ * sg_io_linux.h  N.B. the SUGGEST_* constants are no longer used.
+ */
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0
+#endif
+#ifndef DRIVER_SENSE
+#define DRIVER_SENSE 0x08
+#endif
+#define SG_LIB_DRIVER_MASK      DRIVER_MASK
+#define SG_LIB_SUGGEST_MASK     SUGGEST_MASK
+#define SG_LIB_DRIVER_SENSE    DRIVER_SENSE
+
+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__)
+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;
+}
+
+/* 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). */
+void
+sg_find_bsg_nvme_char_major(int verbose)
+{
+    bool got_one = false;
+    int n;
+    const char * proc_devices = "/proc/devices";
+    char * cp;
+    FILE *fp;
+    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));
+        return;
+    }
+    while ((cp = fgets(b, sizeof(b), fp))) {
+        if ((1 == sscanf(b, "%126s", a)) &&
+            (0 == memcmp(a, "Character", 9)))
+            break;
+    }
+    while (cp && (cp = fgets(b, sizeof(b), fp))) {
+        if (2 == sscanf(b, "%d %126s", &n, a)) {
+            if (0 == strcmp("bsg", a)) {
+                sg_bsg_major = n;
+                if (got_one)
+                    break;
+                got_one = true;
+            } else if (0 == strcmp("nvme", a)) {
+                sg_nvme_char_major = n;
+                if (got_one)
+                    break;
+                got_one = true;
+            }
+        } else
+            break;
+    }
+    if (verbose > 3) {
+        if (cp) {
+            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 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. */
+static bool
+check_file_type(int dev_fd, struct stat * dev_statp, bool * is_bsg_p,
+                bool * is_nvme_p, uint32_t * nsid_p, int * os_err_p,
+                int verbose)
+{
+    bool is_nvme = false;
+    bool is_sg = false;
+    bool is_bsg = false;
+    bool is_block = false;
+    int os_err = 0;
+    int major_num;
+    uint32_t nsid = 0;          /* invalid NSID */
+
+    if (dev_fd >= 0) {
+        if (fstat(dev_fd, dev_statp) < 0) {
+            os_err = errno;
+            if (verbose)
+                pr2ws("%s: fstat() failed: %s (errno=%d)\n", __func__,
+                      safe_strerror(os_err), os_err);
+            goto skip_out;
+        }
+        major_num = (int)SG_DEV_MAJOR(dev_statp->st_rdev);
+        if (S_ISCHR(dev_statp->st_mode)) {
+            if (SCSI_GENERIC_MAJOR == major_num)
+                is_sg = true;
+            else if (sg_bsg_major == major_num)
+                is_bsg = true;
+            else if (sg_nvme_char_major == major_num)
+                is_nvme = true;
+        } else if (S_ISBLK(dev_statp->st_mode)) {
+            is_block = true;
+            if (BLOCK_EXT_MAJOR == major_num) {
+                is_nvme = true;
+                nsid = ioctl(dev_fd, NVME_IOCTL_ID, NULL);
+                if (SG_NVME_BROADCAST_NSID == nsid) {  /* means ioctl error */
+                    os_err = errno;
+                    if (verbose)
+                        pr2ws("%s: ioctl(NVME_IOCTL_ID) failed: %s "
+                              "(errno=%d)\n", __func__, safe_strerror(os_err),
+                              os_err);
+                } else
+                    os_err = 0;
+            }
+        }
+    } else {
+        os_err = EBADF;
+        if (verbose)
+            pr2ws("%s: invalid file descriptor (%d)\n", __func__, dev_fd);
+    }
+skip_out:
+    if (verbose > 3) {
+        pr2ws("%s: file descriptor is ", __func__);
+        if (is_sg)
+            pr2ws("sg device\n");
+        else if (is_bsg)
+            pr2ws("bsg device\n");
+        else if (is_nvme && (0 == nsid))
+            pr2ws("NVMe char device\n");
+        else if (is_nvme)
+            pr2ws("NVMe block device, nsid=%lld\n",
+                  ((uint32_t)-1 == nsid) ? -1LL : (long long)nsid);
+        else if (is_block)
+            pr2ws("block device\n");
+        else
+            pr2ws("undetermined device, could be regular file\n");
+    }
+    if (is_bsg_p)
+        *is_bsg_p = is_bsg;
+    if (is_nvme_p)
+        *is_nvme_p = is_nvme;
+    if (nsid_p)
+        *nsid_p = nsid;
+    if (os_err_p)
+        *os_err_p = os_err;
+    return is_sg;
+}
+
+/* Assumes dev_fd is an "open" file handle associated with device_name. If
+ * the implementation (possibly for one OS) cannot determine from dev_fd if
+ * a SCSI or NVMe pass-through is referenced, then it might guess based on
+ * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if
+ * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is
+ * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0.
+ * If error, returns negated errno (operating system) value. */
+int
+check_pt_file_handle(int dev_fd, const char * device_name, int verbose)
+{
+    if (verbose > 4)
+        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 (! 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;
+        int err;
+        uint32_t nsid;
+        struct stat a_stat;
+
+        is_sg = check_file_type(dev_fd, &a_stat, &is_bsg, &is_nvme, &nsid,
+                                &err, verbose);
+        if (err)
+            return -err;
+        else if (is_sg)
+            return 1;
+        else if (is_bsg)
+            return 2;
+        else if (is_nvme && (0 == nsid))
+            return 3;
+        else if (is_nvme)
+            return 4;
+        else
+            return 0;
+    } else
+        return 0;
+}
+
+/*
+ * 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()
+ *      matches the char major number of the bsg entry in /proc/devices
+ * Otherwise the sg v3 interface is used.
+ *
+ * Note that in either case we prepare the data in a sg v4 structure. If
+ * the runtime tests indicate that the v3 interface is needed then
+ * do_scsi_pt_v3() transfers the input data into a v3 structure and
+ * then the output data is transferred back into a sg v4 structure.
+ * That implementation detail could change in the future.
+ *
+ * [20120806] Only use MAJOR() macro in kdev_t.h if that header file is
+ * available and major() macro [N.B. lower case] is not available.
+ */
+
+
+#ifdef major
+#define SG_DEV_MAJOR major
+#else
+#ifdef HAVE_LINUX_KDEV_T_H
+#include <linux/kdev_t.h>
+#endif
+#define SG_DEV_MAJOR MAJOR  /* MAJOR() macro faulty if > 255 minors */
+#endif
+
+
+/* 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 (! 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);
+    }
+    fd = open(device_name, flags);
+    if (fd < 0) {
+        fd = -errno;
+        if (verbose > 1)
+            pr2ws("%s: open(%s, 0x%x) failed: %s\n", __func__, device_name,
+                  flags, safe_strerror(-fd));
+    }
+    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.guard = 'Q';
+#ifdef BSG_PROTOCOL_SCSI
+            ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI;
+#endif
+#ifdef BSG_SUB_PROTOCOL_SCSI_CMD
+            ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+#endif
+        }
+    } 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_nvme_id_ctlp) {
+        free(ptp->free_nvme_id_ctlp);
+        ptp->free_nvme_id_ctlp = NULL;
+        ptp->nvme_id_ctlp = NULL;
+    }
+    if (ptp)
+        free(ptp);
+}
+
+/* Remembers previous device file descriptor */
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+    bool is_sg, is_bsg, is_nvme;
+    int fd;
+    uint32_t nvme_nsid;
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    if (ptp) {
+        fd = ptp->dev_fd;
+        is_sg = ptp->is_sg;
+        is_bsg = ptp->is_bsg;
+        is_nvme = ptp->is_nvme;
+        nvme_nsid = ptp->nvme_nsid;
+        memset(ptp, 0, sizeof(struct sg_pt_linux_scsi));
+        ptp->io_hdr.guard = 'Q';
+#ifdef BSG_PROTOCOL_SCSI
+        ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI;
+#endif
+#ifdef BSG_SUB_PROTOCOL_SCSI_CMD
+        ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+#endif
+        ptp->dev_fd = fd;
+        ptp->is_sg = is_sg;
+        ptp->is_bsg = is_bsg;
+        ptp->is_nvme = is_nvme;
+        ptp->nvme_direct = false;
+        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 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 (! 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)
+        ptp->is_sg = check_file_type(dev_fd, &a_stat, &ptp->is_bsg,
+                                     &ptp->is_nvme, &ptp->nvme_nsid,
+                                     &ptp->os_err, verbose);
+    else {
+        ptp->is_sg = false;
+        ptp->is_bsg = false;
+        ptp->is_nvme = false;
+        ptp->nvme_direct = 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.request)
+        ++ptp->in_err;
+    ptp->io_hdr.request = (__u64)(sg_uintptr_t)cdb;
+    ptp->io_hdr.request_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.response)
+        ++ptp->in_err;
+    memset(sense, 0, max_sense_len);
+    ptp->io_hdr.response = (__u64)(sg_uintptr_t)sense;
+    ptp->io_hdr.max_response_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->io_hdr.din_xferp)
+        ++ptp->in_err;
+    if (dxfer_ilen > 0) {
+        ptp->io_hdr.din_xferp = (__u64)(sg_uintptr_t)dxferp;
+        ptp->io_hdr.din_xfer_len = dxfer_ilen;
+    }
+}
+
+/* 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->io_hdr.dout_xferp)
+        ++ptp->in_err;
+    if (dxfer_olen > 0) {
+        ptp->io_hdr.dout_xferp = (__u64)(sg_uintptr_t)dxferp;
+        ptp->io_hdr.dout_xfer_len = dxfer_olen;
+    }
+}
+
+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 (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.spare_in = pack_id;
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag)
+{
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    ptp->io_hdr.request_tag = tag;
+}
+
+/* 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->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_uintptr_t)(&(ptp->tmf_request[0]));
+    ptp->io_hdr.request_len = 1;
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority)
+{
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    ptp->io_hdr.request_attr = attribute;
+    ptp->io_hdr.request_priority = priority;
+}
+
+#ifndef BSG_FLAG_Q_AT_TAIL
+#define BSG_FLAG_Q_AT_TAIL 0x10
+#endif
+#ifndef BSG_FLAG_Q_AT_HEAD
+#define BSG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+/* Need this later if translated to v3 interface */
+#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 bsg driver (sg v4) 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 |= BSG_FLAG_Q_AT_HEAD;
+        ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_TAIL;
+    } else if (SCSI_PT_FLAGS_QUEUE_AT_TAIL & flags) {
+        ptp->io_hdr.flags |= BSG_FLAG_Q_AT_TAIL;
+        ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_HEAD;
+    }
+}
+
+/* N.B. Returns din_resid and ignores dout_resid */
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    if (NULL == ptp)
+        return 0;
+    return ptp->nvme_direct ? 0 : ptp->io_hdr.din_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 (int)(ptp->nvme_direct ? ptp->nvme_status :
+                                    ptp->io_hdr.device_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->nvme_direct ? ptp->nvme_result :
+                              ptp->io_hdr.device_status;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    return ptp->io_hdr.response_len;
+}
+
+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.transport_status;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    ptp->io_hdr.transport_status = err;
+}
+
+/* Returns b which will contain a null char terminated string (if
+ * max_b_len > 0). Combined driver and transport (called "host" in Linux
+ * kernel) statuses */
+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.transport_status;
+    int n, m;
+    char * cp = b;
+    int driv;
+    const char * driv_cp = "invalid";
+
+    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 invalid\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;
+}
+
+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.device_status & 0x7e;
+
+    if (ptp->os_err)
+        return SCSI_PT_RESULT_OS_ERR;
+    else if (ptp->io_hdr.transport_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_os_err(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    return ptp->os_err;
+}
+
+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;
+}
+
+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;
+}
+
+/* Executes SCSI command using sg v3 interface */
+static int
+do_scsi_pt_v3(struct sg_pt_linux_scsi * ptp, int fd, int time_secs,
+              int verbose)
+{
+    struct sg_io_hdr v3_hdr;
+
+    memset(&v3_hdr, 0, sizeof(v3_hdr));
+    /* convert v4 to v3 header */
+    v3_hdr.interface_id = 'S';
+    v3_hdr.dxfer_direction = SG_DXFER_NONE;
+    v3_hdr.cmdp = (unsigned char *)(long)ptp->io_hdr.request;
+    v3_hdr.cmd_len = (unsigned char)ptp->io_hdr.request_len;
+    if (ptp->io_hdr.din_xfer_len > 0) {
+        if (ptp->io_hdr.dout_xfer_len > 0) {
+            if (verbose)
+                pr2ws("sgv3 doesn't support bidi\n");
+            return SCSI_PT_DO_BAD_PARAMS;
+        }
+        v3_hdr.dxferp = (void *)(long)ptp->io_hdr.din_xferp;
+        v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.din_xfer_len;
+        v3_hdr.dxfer_direction =  SG_DXFER_FROM_DEV;
+    } else if (ptp->io_hdr.dout_xfer_len > 0) {
+        v3_hdr.dxferp = (void *)(long)ptp->io_hdr.dout_xferp;
+        v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.dout_xfer_len;
+        v3_hdr.dxfer_direction =  SG_DXFER_TO_DEV;
+    }
+    if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) {
+        v3_hdr.sbp = (unsigned char *)(long)ptp->io_hdr.response;
+        v3_hdr.mx_sb_len = (unsigned char)ptp->io_hdr.max_response_len;
+    }
+    v3_hdr.pack_id = (int)ptp->io_hdr.spare_in;
+    if (BSG_FLAG_Q_AT_HEAD & ptp->io_hdr.flags)
+        v3_hdr.flags |= SG_FLAG_Q_AT_HEAD;      /* favour AT_HEAD */
+    else if (BSG_FLAG_Q_AT_TAIL & ptp->io_hdr.flags)
+        v3_hdr.flags |= SG_FLAG_Q_AT_TAIL;
+
+    if (NULL == v3_hdr.cmdp) {
+        if (verbose)
+            pr2ws("No SCSI command (cdb) given\n");
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    /* io_hdr.timeout is in milliseconds, if greater than zero */
+    v3_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT);
+    /* Finally do the v3 SG_IO ioctl */
+    if (ioctl(fd, SG_IO, &v3_hdr) < 0) {
+        ptp->os_err = errno;
+        if (verbose > 1)
+            pr2ws("ioctl(SG_IO v3) failed: %s (errno=%d)\n",
+                  safe_strerror(ptp->os_err), ptp->os_err);
+        return -ptp->os_err;
+    }
+    ptp->io_hdr.device_status = (__u32)v3_hdr.status;
+    ptp->io_hdr.driver_status = (__u32)v3_hdr.driver_status;
+    ptp->io_hdr.transport_status = (__u32)v3_hdr.host_status;
+    ptp->io_hdr.response_len = (__u32)v3_hdr.sb_len_wr;
+    ptp->io_hdr.duration = (__u32)v3_hdr.duration;
+    ptp->io_hdr.din_resid = (__s32)v3_hdr.resid;
+    /* v3_hdr.info not passed back since no mapping defined (yet) */
+    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);
+
+    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)
+            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;
+    } else
+        fd = ptp->dev_fd;
+    if (! have_checked_for_type) {
+        err = set_pt_file_handle(vp, ptp->dev_fd, verbose);
+        if (err)
+            return -ptp->os_err;
+    }
+    if (ptp->os_err)
+        return -ptp->os_err;
+    if (ptp->is_nvme)
+        return sg_do_nvme_pt(vp, -1, 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 */
+    else
+        return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+
+    if (! ptp->io_hdr.request) {
+        if (verbose)
+            pr2ws("No SCSI command (cdb) given (v4)\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 0
+    /* sense buffer already zeroed */
+    if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) {
+        void * p;
+
+        p = (void *)(long)ptp->io_hdr.response;
+        memset(p, 0, ptp->io_hdr.max_response_len);
+    }
+#endif
+    if (ioctl(fd, SG_IO, &ptp->io_hdr) < 0) {
+        ptp->os_err = errno;
+        if (verbose > 1)
+            pr2ws("ioctl(SG_IO v4) failed: %s (errno=%d)\n",
+                  safe_strerror(ptp->os_err), ptp->os_err);
+        return -ptp->os_err;
+    }
+    return 0;
+}
diff --git a/tools/sg_write_buffer/sg_pt_linux_nvme.c b/tools/sg_write_buffer/sg_pt_linux_nvme.c
new file mode 100644
index 0000000..5b08f6d
--- /dev/null
+++ b/tools/sg_write_buffer/sg_pt_linux_nvme.c
@@ -0,0 +1,1185 @@
+/*
+ * Copyright (c) 2017-2018 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.
+ *
+ * The code to use the NVMe Management Interface (MI) SES pass-through
+ * was provided by WDC in November 2017.
+ */
+
+/*
+ * Copyright 2017, Western Digital Corporation
+ *
+ * Written by Berck Nash
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * Based on the NVM-Express command line utility, which bore the following
+ * notice:
+ *
+ * Copyright (c) 2014-2015, Intel Corporation.
+ *
+ * Written by Keith Busch <keith.busch@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *                   MA 02110-1301, USA.
+ */
+
+/* sg_pt_linux_nvme version 1.04 20180115 */
+
+
+#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>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.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_REPORT_LUNS_OPC 0xa0
+#define SCSI_TEST_UNIT_READY_OPC  0x0
+#define SCSI_REQUEST_SENSE_OPC  0x3
+#define SCSI_SEND_DIAGNOSTIC_OPC  0x1d
+#define SCSI_RECEIVE_DIAGNOSTIC_OPC  0x1c
+#define SCSI_MAINT_IN_OPC  0xa3
+#define SCSI_REP_SUP_OPCS_OPC  0xc
+#define SCSI_REP_SUP_TMFS_OPC  0xd
+
+/* 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 LOW_POWER_COND_ON_ASC  0x5e     /* ASCQ=0 */
+#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
+
+
+static inline bool is_aligned(const void * 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;
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5)
+ * to the name of its associated char device (e.g. /dev/nvme0). If this
+ * occurs true is returned and the char device name is placed in 'b' (as
+ * long as b_len is sufficient). Otherwise false is returned. */
+bool
+sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len,
+                         char * b)
+{
+    uint32_t n, tlen;
+    const char * cp;
+    char buff[8];
+
+    if ((NULL == b) || (b_len < 5))
+        return false;   /* degenerate cases */
+    cp = strstr(nvme_block_devname, "nvme");
+    if (NULL == cp)
+        return false;   /* expected to find "nvme" in given name */
+    if (1 != sscanf(cp, "nvme%u", &n))
+        return false;   /* didn't find valid "nvme<number>" */
+    snprintf(buff, sizeof(buff), "%u", n);
+    tlen = (cp - nvme_block_devname) + 4 + strlen(buff);
+    if ((tlen + 1) > b_len)
+        return false;           /* b isn't long enough to fit output */
+    memcpy(b, nvme_block_devname, tlen);
+    b[tlen] = '\0';
+    return true;
+}
+
+static void
+build_sense_buffer(bool desc, uint8_t *buf, uint8_t skey, uint8_t asc,
+                   uint8_t ascq)
+{
+    if (desc) {
+        buf[0] = 0x72;  /* descriptor, current */
+        buf[1] = skey;
+        buf[2] = asc;
+        buf[3] = ascq;
+        buf[7] = 0;
+    } else {
+        buf[0] = 0x70;  /* fixed, current */
+        buf[2] = skey;
+        buf[7] = 0xa;   /* Assumes length is 18 bytes */
+        buf[12] = asc;
+        buf[13] = ascq;
+    }
+}
+
+/* Set in_bit to -1 to indicate no bit position of invalid field */
+static void
+mk_sense_asc_ascq(struct sg_pt_linux_scsi * ptp, int sk, int asc, int ascq,
+                  int vb)
+{
+    bool dsense = ptp->scsi_dsense;
+    int n;
+    uint8_t * sbp = (uint8_t *)ptp->io_hdr.response;
+
+    ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION;
+    n = ptp->io_hdr.max_response_len;
+    if ((n < 8) || ((! dsense) && (n < 14))) {
+        if (vb)
+            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, sk, asc, ascq);
+    if (vb > 3)
+        pr2ws("%s:  [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, sk,
+              asc, ascq);
+}
+
+static void
+mk_sense_from_nvme_status(struct sg_pt_linux_scsi * ptp, int vb)
+{
+    bool ok;
+    bool dsense = ptp->scsi_dsense;
+    int n;
+    uint8_t sstatus, sk, asc, ascq;
+    uint8_t * sbp = (uint8_t *)ptp->io_hdr.response;
+
+    ok = sg_nvme_status2scsi(ptp->nvme_status, &sstatus, &sk, &asc, &ascq);
+    if (! ok) { /* can't find a mapping to a SCSI error, so ... */
+        sstatus = SAM_STAT_CHECK_CONDITION;
+        sk = SPC_SK_ILLEGAL_REQUEST;
+        asc = 0xb;
+        ascq = 0x0;     /* asc: "WARNING" purposely vague */
+    }
+
+    ptp->io_hdr.device_status = sstatus;
+    n = ptp->io_hdr.max_response_len;
+    if ((n < 8) || ((! dsense) && (n < 14))) {
+        pr2ws("%s: sense_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, sk, asc, ascq);
+    if (vb > 3)
+        pr2ws("%s: [status, sense_key,asc,ascq]: [0x%x, 0x%x,0x%x,0x%x]\n",
+              __func__, sstatus, sk, asc, 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))) {
+        if (vb)
+            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 > 3)
+        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);
+}
+
+/* Returns 0 for success. Returns SG_LIB_NVME_STATUS if there is non-zero
+ * NVMe status (from the completion queue) with the value placed in
+ * ptp->nvme_status. If Unix error from ioctl then return negated value
+ * (equivalent -errno from basic Unix system functions like open()).
+ * CDW0 from the completion queue is placed in ptp->nvme_result in the
+ * absence of a Unix error. If time_secs is negative it is treated as
+ * a timeout in milliseconds (of abs(time_secs) ). */
+static int
+do_nvme_admin_cmd(struct sg_pt_linux_scsi * ptp,
+                  struct sg_nvme_passthru_cmd *cmdp, void * dp, bool is_read,
+                  int time_secs, int vb)
+{
+    const uint32_t cmd_len = sizeof(struct sg_nvme_passthru_cmd);
+    int res;
+    uint32_t n;
+    uint16_t sct_sc;
+    const uint8_t * up = ((const uint8_t *)cmdp) + SG_NVME_PT_OPCODE;
+
+    cmdp->timeout_ms = (time_secs < 0) ? (-time_secs) : (1000 * time_secs);
+    ptp->os_err = 0;
+    if (vb > 2) {
+        pr2ws("NVMe command:\n");
+        hex2stderr((const uint8_t *)cmdp, cmd_len, 1);
+        if ((vb > 3) && (! is_read) && dp) {
+            uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN);
+
+            if (len > 0) {
+                n = len;
+                if ((len < 512) || (vb > 5))
+                    pr2ws("\nData-out buffer (%u bytes):\n", n);
+                else {
+                    pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n);
+                    n = 512;
+                }
+                hex2stderr((const uint8_t *)dp, n, 0);
+            }
+        }
+    }
+    res = ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, cmdp);
+    if (res < 0) {  /* OS error (errno negated) */
+        ptp->os_err = -res;
+        if (vb > 1) {
+            pr2ws("%s: ioctl opcode=0x%x failed: %s "
+                  "(errno=%d)\n", __func__, *up, strerror(-res), -res);
+        }
+        return res;
+    }
+
+    /* Now res contains NVMe completion queue CDW3 31:17 (15 bits) */
+    ptp->nvme_result = cmdp->result;
+    if (ptp->nvme_direct && ptp->io_hdr.response &&
+        (ptp->io_hdr.max_response_len > 3)) {
+        /* build 16 byte "sense" buffer */
+        uint8_t * sbp = (uint8_t *)ptp->io_hdr.response;
+        uint16_t st = (uint16_t)res;
+
+        n = ptp->io_hdr.max_response_len;
+        n = (n < 16) ? n : 16;
+        memset(sbp, 0 , n);
+        ptp->io_hdr.response_len = n;
+        sg_put_unaligned_le32(cmdp->result,
+                              sbp + SG_NVME_PT_CQ_RESULT);
+        if (n > 15) /* LSBit will be 0 (Phase bit) after (st << 1) */
+            sg_put_unaligned_le16(st << 1, sbp + SG_NVME_PT_CQ_STATUS_P);
+    }
+    /* clear upper bits (DNR and More) leaving ((SCT << 8) | SC) */
+    sct_sc = 0x3ff & res;
+    ptp->nvme_status = sct_sc;
+    if (sct_sc) {  /* when non-zero, treat as command error */
+        if (vb > 1) {
+            char b[80];
+
+            pr2ws("%s: ioctl opcode=0x%x failed: NVMe status: %s [0x%x]\n",
+                   __func__, *up,
+                  sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b), sct_sc);
+        }
+        return SG_LIB_NVME_STATUS;      /* == SCSI_PT_DO_NVME_STATUS */
+    }
+    if ((vb > 3) && is_read && dp) {
+        uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN);
+
+        if (len > 0) {
+            n = len;
+            if ((len < 1024) || (vb > 5))
+                pr2ws("\nData-in buffer (%u bytes):\n", n);
+            else {
+                pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n);
+                n = 1024;
+            }
+            hex2stderr((const uint8_t *)dp, n, 0);
+        }
+    }
+    return 0;
+}
+
+/* Returns 0 on success; otherwise a positive value is returned */
+static int
+sntl_cache_identity(struct sg_pt_linux_scsi * ptp, int time_secs, int vb)
+{
+    struct sg_nvme_passthru_cmd cmd;
+    uint32_t pg_sz = sg_get_page_size();
+    uint8_t * up;
+
+    up = sg_memalign(pg_sz, pg_sz, &ptp->free_nvme_id_ctlp, vb > 3);
+    ptp->nvme_id_ctlp = up;
+    if (NULL == up) {
+        pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+        return -ENOMEM;
+    }
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0x6;   /* Identify */
+    cmd.cdw10 = 0x1;    /* CNS=0x1 Identify controller */
+    cmd.addr = (uint64_t)(sg_uintptr_t)ptp->nvme_id_ctlp;
+    cmd.data_len = pg_sz;
+    return do_nvme_admin_cmd(ptp, &cmd, up, true, time_secs, vb);
+}
+
+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 time_secs,
+         int vb)
+{
+    bool evpd;
+    bool cp_id_ctl = false;
+    int res;
+    uint16_t n, alloc_len, pg_cd;
+    uint32_t pg_sz = sg_get_page_size();
+    uint8_t * nvme_id_ns = NULL;
+    uint8_t * free_nvme_id_ns = NULL;
+    uint8_t inq_dout[256];
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+
+    if (0x2 & cdbp[1]) {        /* Reject CmdDt=1 */
+        mk_sense_invalid_fld(ptp, true, 1, 1, vb);
+        return 0;
+    }
+    if (NULL == ptp->nvme_id_ctlp) {
+        res = sntl_cache_identity(ptp, time_secs, vb);
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else if (res) /* should be negative errno */
+            return res;
+    }
+    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 */
+        switch (pg_cd) {
+        case 0:
+            /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+            inq_dout[1] = pg_cd;
+            n = 8;
+            sg_put_unaligned_be16(n - 4, inq_dout + 2);
+            inq_dout[4] = 0x0;
+            inq_dout[5] = 0x80;
+            inq_dout[6] = 0x83;
+            inq_dout[n - 1] = 0xde;     /* last VPD number */
+            break;
+        case 0x80:
+            /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+            inq_dout[1] = pg_cd;
+            sg_put_unaligned_be16(20, inq_dout + 2);
+            memcpy(inq_dout + 4, ptp->nvme_id_ctlp + 4, 20);    /* SN */
+            n = 24;
+            break;
+        case 0x83:
+            if ((ptp->nvme_nsid > 0) &&
+                (ptp->nvme_nsid < SG_NVME_BROADCAST_NSID)) {
+                nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns,
+                                         vb > 3);
+                if (nvme_id_ns) {
+                    struct sg_nvme_passthru_cmd cmd;
+
+                    memset(&cmd, 0, sizeof(cmd));
+                    cmd.opcode = 0x6;   /* Identify */
+                    cmd.nsid = ptp->nvme_nsid;
+                    cmd.cdw10 = 0x0;    /* CNS=0x0 Identify namespace */
+                    cmd.addr = (uint64_t)(sg_uintptr_t)nvme_id_ns;
+                    cmd.data_len = pg_sz;
+                    res = do_nvme_admin_cmd(ptp, &cmd, nvme_id_ns, true,
+                                            time_secs, vb > 3);
+                    if (res) {
+                        free(free_nvme_id_ns);
+                        free_nvme_id_ns = NULL;
+                        nvme_id_ns = NULL;
+                    }
+                }
+            }
+            n = sg_make_vpd_devid_for_nvme(ptp->nvme_id_ctlp, nvme_id_ns,
+                                           0 /* pdt */, -1 /*tproto */,
+                                           inq_dout, sizeof(inq_dout));
+            if (n > 3)
+                sg_put_unaligned_be16(n - 4, inq_dout + 2);
+            if (free_nvme_id_ns) {
+                free(free_nvme_id_ns);
+                free_nvme_id_ns = NULL;
+                nvme_id_ns = NULL;
+            }
+            break;
+        case 0xde:
+            inq_dout[1] = pg_cd;
+            sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2);
+            n = 16 + 4096;
+            cp_id_ctl = true;
+            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;
+            ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+            if (n > 0) {
+                if (cp_id_ctl) {
+                    memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout,
+                           (n < 16 ? n : 16));
+                    if (n > 16)
+                        memcpy((uint8_t *)ptp->io_hdr.din_xferp + 16,
+                               ptp->nvme_id_ctlp, n - 16);
+                } else
+                    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;
+            ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+            if (n > 0)
+                memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, n);
+        }
+    }
+    return 0;
+}
+
+static int
+sntl_rluns(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs,
+           int vb)
+{
+    int res;
+    uint16_t sel_report;
+    uint32_t alloc_len, k, n, num, max_nsid;
+    uint8_t * rl_doutp;
+    uint8_t * up;
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+
+    sel_report = cdbp[2];
+    alloc_len = sg_get_unaligned_be32(cdbp + 6);
+    if (NULL == ptp->nvme_id_ctlp) {
+        res = sntl_cache_identity(ptp, time_secs, vb);
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else if (res)
+            return res;
+    }
+    max_nsid = sg_get_unaligned_le32(ptp->nvme_id_ctlp + 516);
+    switch (sel_report) {
+    case 0:
+    case 2:
+        num = max_nsid;
+        break;
+    case 1:
+    case 0x10:
+    case 0x12:
+        num = 0;
+        break;
+    case 0x11:
+        num = (1 == ptp->nvme_nsid) ? max_nsid :  0;
+        break;
+    default:
+        if (vb > 1)
+            pr2ws("%s: bad select_report value: 0x%x\n", __func__,
+                  sel_report);
+        mk_sense_invalid_fld(ptp, true, 2, 7, vb);
+        return 0;
+    }
+    rl_doutp = (uint8_t *)calloc(num + 1, 8);
+    if (NULL == rl_doutp) {
+        pr2ws("%s: calloc() failed to get memory\n", __func__);
+        return -ENOMEM;
+    }
+    for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8)
+        sg_put_unaligned_be16(k, up);
+    n = num * 8;
+    sg_put_unaligned_be32(n, rl_doutp);
+    n+= 8;
+    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;
+        ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+        if (n > 0)
+            memcpy((uint8_t *)ptp->io_hdr.din_xferp, rl_doutp, n);
+    }
+    res = 0;
+    free(rl_doutp);
+    return res;
+}
+
+static int
+sntl_tur(struct sg_pt_linux_scsi * ptp, int time_secs, int vb)
+{
+    int res;
+    uint32_t pow_state;
+    struct sg_nvme_passthru_cmd cmd;
+
+    if (vb > 4)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+    if (NULL == ptp->nvme_id_ctlp) {
+        res = sntl_cache_identity(ptp, time_secs, vb);
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else if (res)
+            return res;
+    }
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0xa;   /* Get feature */
+    cmd.nsid = SG_NVME_BROADCAST_NSID;
+    cmd.cdw10 = 0x2;    /* SEL=0 (current), Feature=2 Power Management */
+    cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs);
+    res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb);
+    if (0 != res) {
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else
+            return res;
+    } else {
+        ptp->os_err = 0;
+        ptp->nvme_status = 0;
+    }
+    pow_state = (0x1f & ptp->nvme_result);
+    if (vb > 3)
+        pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+#if 0   /* pow_state bounces around too much on laptop */
+    if (pow_state)
+        mk_sense_asc_ascq(ptp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0,
+                          vb);
+#endif
+    return 0;
+}
+
+static int
+sntl_req_sense(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+               int time_secs, int vb)
+{
+    bool desc;
+    int res;
+    uint32_t pow_state, alloc_len, n;
+    struct sg_nvme_passthru_cmd cmd;
+    uint8_t rs_dout[64];
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+    if (NULL == ptp->nvme_id_ctlp) {
+        res = sntl_cache_identity(ptp, time_secs, vb);
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else if (res)
+            return res;
+    }
+    desc = !!(0x1 & cdbp[1]);
+    alloc_len = cdbp[4];
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0xa;   /* Get feature */
+    cmd.nsid = SG_NVME_BROADCAST_NSID;
+    cmd.cdw10 = 0x2;    /* SEL=0 (current), Feature=2 Power Management */
+    cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs);
+    res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb);
+    if (0 != res) {
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else
+            return res;
+    } else {
+        ptp->os_err = 0;
+        ptp->nvme_status = 0;
+    }
+    ptp->io_hdr.response_len = 0;
+    pow_state = (0x1f & ptp->nvme_result);
+    if (vb > 3)
+        pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+    memset(rs_dout, 0, sizeof(rs_dout));
+    if (pow_state)
+        build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+                           LOW_POWER_COND_ON_ASC, 0);
+    else
+        build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+                           NO_ADDITIONAL_SENSE, 0);
+    n = desc ? 8 : 18;
+    n = (n < alloc_len) ? n : alloc_len;
+    n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len;
+    ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+    if (n > 0)
+        memcpy((uint8_t *)ptp->io_hdr.din_xferp, rs_dout, n);
+    return 0;
+}
+
+/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI
+ * has a special command (SES Send) to tunnel through pages to an
+ * enclosure. The NVMe enclosure is meant to understand the SES
+ * (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_senddiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+              int time_secs, int vb)
+{
+    bool pf, self_test;
+    int res;
+    uint8_t st_cd, dpg_cd;
+    uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst;
+    uint32_t pg_sz = sg_get_page_size();
+    uint8_t * dop;
+    struct sg_nvme_passthru_cmd cmd;
+    uint8_t * cmd_up = (uint8_t *)&cmd;
+
+    st_cd = 0x7 & (cdbp[1] >> 5);
+    self_test = !! (0x4 & cdbp[1]);
+    pf = !! (0x10 & cdbp[1]);
+    if (vb > 3)
+        pr2ws("%s: pf=%d, self_test=%d (st_code=%d)\n", __func__, (int)pf,
+              (int)self_test, (int)st_cd);
+    if (self_test || st_cd) {
+        memset(cmd_up, 0, sizeof(cmd));
+        cmd_up[SG_NVME_PT_OPCODE] = 0x14;   /* Device self-test */
+        /* just this namespace (if there is one) and controller */
+        sg_put_unaligned_le32(ptp->nvme_nsid, cmd_up + SG_NVME_PT_NSID);
+        switch (st_cd) {
+        case 0: /* Here if self_test is set, do short self-test */
+        case 1: /* Background short */
+        case 5: /* Foreground short */
+            nvme_dst = 1;
+            break;
+        case 2: /* Background extended */
+        case 6: /* Foreground extended */
+            nvme_dst = 2;
+            break;
+        case 4: /* Abort self-test */
+            nvme_dst = 0xf;
+            break;
+        default:
+            pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd);
+            mk_sense_invalid_fld(ptp, true, 1, 7, vb);
+            return 0;
+        }
+        sg_put_unaligned_le32(nvme_dst, cmd_up + SG_NVME_PT_CDW10);
+        res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb);
+        if (0 != res) {
+            if (SG_LIB_NVME_STATUS == res) {
+                mk_sense_from_nvme_status(ptp, vb);
+                return 0;
+            } else
+                return res;
+        }
+    }
+    alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+    dout_len = ptp->io_hdr.dout_xfer_len;
+    if (pf) {
+        if (0 == alloc_len) {
+            mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+            if (vb)
+                pr2ws("%s: PF bit set bit param_list_len=0\n", __func__);
+            return 0;
+        }
+    } else {    /* PF bit clear */
+        if (alloc_len) {
+            mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+            if (vb)
+                pr2ws("%s: param_list_len>0 but PF clear\n", __func__);
+            return 0;
+        } else
+            return 0;     /* nothing to do */
+        if (dout_len > 0) {
+            if (vb)
+                pr2ws("%s: dout given but PF clear\n", __func__);
+            return SCSI_PT_DO_BAD_PARAMS;
+        }
+    }
+    if (dout_len < 4) {
+        if (vb)
+            pr2ws("%s: dout length (%u bytes) too short\n", __func__,
+                  dout_len);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    n = dout_len;
+    n = (n < alloc_len) ? n : alloc_len;
+    dop = (uint8_t *)ptp->io_hdr.dout_xferp;
+    if (! is_aligned(dop, pg_sz)) {  /* caller best use sg_memalign(,pg_sz) */
+        if (vb)
+            pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__,
+                  (uint64_t)ptp->io_hdr.dout_xferp);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    dpg_cd = dop[0];
+    dpg_len = sg_get_unaligned_be16(dop + 2) + 4;
+    /* should we allow for more than one D_PG is dout ?? */
+    n = (n < dpg_len) ? n : dpg_len;    /* not yet ... */
+
+    if (vb)
+        pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n",
+              __func__, dpg_cd, dpg_len);
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0x1d;  /* MI send; hmmm same opcode as SEND DIAG */
+    cmd.addr = (uint64_t)(sg_uintptr_t)dop;
+    cmd.data_len = 0x1000;   /* NVMe 4k page size. Maybe determine this? */
+                             /* dout_len > 0x1000, is this a problem?? */
+    cmd.cdw10 = 0x0804;      /* NVMe Message Header */
+    cmd.cdw11 = 0x9;         /* nvme_mi_ses_send; (0x8 -> mi_ses_recv) */
+    cmd.cdw13 = n;
+    res = do_nvme_admin_cmd(ptp, &cmd, dop, false, time_secs, vb);
+    if (0 != res) {
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        }
+    }
+    return res;
+}
+
+/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1)
+ * NVMe-MI has a special command (SES Receive) to read pages through a
+ * tunnel from an enclosure. The NVMe enclosure is meant to understand the
+ * SES (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_recvdiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+              int time_secs, int vb)
+{
+    bool pcv;
+    int res;
+    uint8_t dpg_cd;
+    uint32_t alloc_len, n, din_len;
+    uint32_t pg_sz = sg_get_page_size();
+    uint8_t * dip;
+    struct sg_nvme_passthru_cmd cmd;
+
+    pcv = !! (0x1 & cdbp[1]);
+    dpg_cd = cdbp[2];
+    alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+    if (vb > 3)
+        pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__,
+              dpg_cd, (int)pcv, alloc_len);
+    din_len = ptp->io_hdr.din_xfer_len;
+    n = din_len;
+    n = (n < alloc_len) ? n : alloc_len;
+    dip = (uint8_t *)ptp->io_hdr.din_xferp;
+    if (! is_aligned(dip, pg_sz)) {  /* caller best use sg_memalign(,pg_sz) */
+        if (vb)
+            pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__,
+                  (uint64_t)ptp->io_hdr.din_xferp);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+
+    if (vb)
+        pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__,
+              dpg_cd);
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0x1e;  /* MI receive */
+    cmd.addr = (uint64_t)(sg_uintptr_t)dip;
+    cmd.data_len = 0x1000;   /* NVMe 4k page size. Maybe determine this? */
+                             /* din_len > 0x1000, is this a problem?? */
+    cmd.cdw10 = 0x0804;      /* NVMe Message Header */
+    cmd.cdw11 = 0x8;         /* nvme_mi_ses_receive */
+    cmd.cdw12 = dpg_cd;
+    cmd.cdw13 = n;
+    res = do_nvme_admin_cmd(ptp, &cmd, dip, true, time_secs, vb);
+    if (0 != res) {
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else
+            return res;
+    }
+    ptp->io_hdr.din_resid = din_len - n;
+    return res;
+}
+
+#define F_SA_LOW                0x80    /* cdb byte 1, bits 4 to 0 */
+#define F_SA_HIGH               0x100   /* as used by variable length cdbs */
+#define FF_SA (F_SA_HIGH | F_SA_LOW)
+#define F_INV_OP                0x200
+
+static struct opcode_info_t {
+        uint8_t opcode;
+        uint16_t sa;            /* service action, 0 for none */
+        uint32_t flags;         /* OR-ed set of F_* flags */
+        uint8_t len_mask[16];   /* len=len_mask[0], then mask for cdb[1]... */
+                                /* ignore cdb bytes after position 15 */
+    } opcode_info_arr[] = {
+    {0x0, 0, 0, {6,              /* TEST UNIT READY */
+      0, 0, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0x3, 0, 0, {6,             /* REQUEST SENSE */
+      0xe1, 0, 0, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0x12, 0, 0, {6,            /* INQUIRY */
+      0xe3, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0x1c, 0, 0, {6,            /* RECEIVE DIAGNOSTIC RESULTS */
+      0x1, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0x1d, 0, 0, {6,            /* SEND DIAGNOSTIC */
+      0xf7, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0xa0, 0, 0, {12,           /* REPORT LUNS */
+      0xe3, 0xff, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} },
+    {0xa3, 0xc, F_SA_LOW, {12,  /* REPORT SUPPORTED OPERATION CODES */
+      0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0,
+      0} },
+    {0xa3, 0xd, F_SA_LOW, {12,  /* REPORT SUPPORTED TASK MAN. FUNCTIONS */
+      0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} },
+
+    {0xff, 0xffff, 0xffff, {0,  /* Sentinel, keep as last element */
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+};
+
+static int
+sntl_rep_opcodes(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+                 int time_secs, int vb)
+{
+    bool rctd;
+    uint8_t reporting_opts, req_opcode, supp;
+    uint16_t req_sa, u;
+    uint32_t alloc_len, offset, a_len;
+    uint32_t pg_sz = sg_get_page_size();
+    int k, len, count, bump;
+    const struct opcode_info_t *oip;
+    uint8_t *arr;
+    uint8_t *free_arr;
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+    rctd = !!(cdbp[2] & 0x80);      /* report command timeout desc. */
+    reporting_opts = cdbp[2] & 0x7;
+    req_opcode = cdbp[3];
+    req_sa = sg_get_unaligned_be16(cdbp + 4);
+    alloc_len = sg_get_unaligned_be32(cdbp + 6);
+    if (alloc_len < 4 || alloc_len > 0xffff) {
+        mk_sense_invalid_fld(ptp, true, 6, -1, vb);
+        return 0;
+    }
+    a_len = pg_sz - 72;
+    arr = sg_memalign(pg_sz, pg_sz, &free_arr, vb > 3);
+    if (NULL == arr) {
+        pr2ws("%s: calloc() failed to get memory\n", __func__);
+        return -ENOMEM;
+    }
+    switch (reporting_opts) {
+    case 0: /* all commands */
+        count = 0;
+        bump = rctd ? 20 : 8;
+        for (offset = 4, oip = opcode_info_arr;
+             (oip->flags != 0xffff) && (offset < a_len); ++oip) {
+            if (F_INV_OP & oip->flags)
+                continue;
+            ++count;
+            arr[offset] = oip->opcode;
+            sg_put_unaligned_be16(oip->sa, arr + offset + 2);
+            if (rctd)
+                arr[offset + 5] |= 0x2;
+            if (FF_SA & oip->flags)
+                arr[offset + 5] |= 0x1;
+            sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6);
+            if (rctd)
+                sg_put_unaligned_be16(0xa, arr + offset + 8);
+            offset += bump;
+        }
+        sg_put_unaligned_be32(count * bump, arr + 0);
+        break;
+    case 1: /* one command: opcode only */
+    case 2: /* one command: opcode plus service action */
+    case 3: /* one command: if sa==0 then opcode only else opcode+sa */
+        for (oip = opcode_info_arr; oip->flags != 0xffff; ++oip) {
+            if ((req_opcode == oip->opcode) && (req_sa == oip->sa))
+                break;
+        }
+        if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) {
+            supp = 1;
+            offset = 4;
+        } else {
+            if (1 == reporting_opts) {
+                if (FF_SA & oip->flags) {
+                    mk_sense_invalid_fld(ptp, true, 2, 2, vb);
+                    free(free_arr);
+                    return 0;
+                }
+                req_sa = 0;
+            } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) {
+                mk_sense_invalid_fld(ptp, true, 4, -1, vb);
+                free(free_arr);
+                return 0;
+            }
+            if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode))
+                supp = 3;
+            else if (0 == (FF_SA & oip->flags))
+                supp = 1;
+            else if (req_sa != oip->sa)
+                supp = 1;
+            else
+                supp = 3;
+            if (3 == supp) {
+                u = oip->len_mask[0];
+                sg_put_unaligned_be16(u, arr + 2);
+                arr[4] = oip->opcode;
+                for (k = 1; k < u; ++k)
+                    arr[4 + k] = (k < 16) ?
+                oip->len_mask[k] : 0xff;
+                offset = 4 + u;
+            } else
+                offset = 4;
+        }
+        arr[1] = (rctd ? 0x80 : 0) | supp;
+        if (rctd) {
+            sg_put_unaligned_be16(0xa, arr + offset);
+            offset += 12;
+        }
+        break;
+    default:
+        mk_sense_invalid_fld(ptp, true, 2, 2, vb);
+        free(free_arr);
+        return 0;
+    }
+    offset = (offset < a_len) ? offset : a_len;
+    len = (offset < alloc_len) ? offset : alloc_len;
+    ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len;
+    if (len > 0)
+        memcpy((uint8_t *)ptp->io_hdr.din_xferp, arr, len);
+    free(free_arr);
+    return 0;
+}
+
+static int
+sntl_rep_tmfs(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+              int time_secs, int vb)
+{
+    bool repd;
+    uint32_t alloc_len, len;
+    uint8_t arr[16];
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+    memset(arr, 0, sizeof(arr));
+    repd = !!(cdbp[2] & 0x80);
+    alloc_len = sg_get_unaligned_be32(cdbp + 6);
+    if (alloc_len < 4) {
+        mk_sense_invalid_fld(ptp, true, 6, -1, vb);
+        return 0;
+    }
+    arr[0] = 0xc8;          /* ATS | ATSS | LURS */
+    arr[1] = 0x1;           /* ITNRS */
+    if (repd) {
+        arr[3] = 0xc;
+        len = 16;
+    } else
+        len = 4;
+
+    len = (len < alloc_len) ? len : alloc_len;
+    ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len;
+    if (len > 0)
+        memcpy((uint8_t *)ptp->io_hdr.din_xferp, arr, len);
+    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_cdb;
+    bool is_read = false;
+    int n, len;
+    uint16_t sa;
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+    struct sg_nvme_passthru_cmd cmd;
+    const uint8_t * cdbp;
+    void * dp = NULL;
+
+    if (! ptp->io_hdr.request) {
+        if (vb)
+            pr2ws("No NVMe command given (set_scsi_pt_cdb())\n");
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    if (fd >= 0) {
+        if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) {
+            if (vb)
+                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 (vb)
+            pr2ws("%s: invalid file descriptors\n", __func__);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    n = ptp->io_hdr.request_len;
+    cdbp = (const uint8_t *)ptp->io_hdr.request;
+    if (vb > 3)
+        pr2ws("%s: opcode=0x%x, fd=%d, time_secs=%d\n", __func__, cdbp[0],
+              fd, time_secs);
+    scsi_cdb = sg_is_scsi_cdb(cdbp, n);
+    /* direct NVMe command (i.e. 64 bytes long) or SNTL */
+    ptp->nvme_direct = ! scsi_cdb;
+    if (scsi_cdb) {
+        switch (cdbp[0]) {
+        case SCSI_INQUIRY_OPC:
+            return sntl_inq(ptp, cdbp, time_secs, vb);
+        case SCSI_REPORT_LUNS_OPC:
+            return sntl_rluns(ptp, cdbp, time_secs, vb);
+        case SCSI_TEST_UNIT_READY_OPC:
+            return sntl_tur(ptp, time_secs, vb);
+        case SCSI_REQUEST_SENSE_OPC:
+            return sntl_req_sense(ptp, cdbp, time_secs, vb);
+        case SCSI_SEND_DIAGNOSTIC_OPC:
+            return sntl_senddiag(ptp, cdbp, time_secs, vb);
+        case SCSI_RECEIVE_DIAGNOSTIC_OPC:
+            return sntl_recvdiag(ptp, cdbp, time_secs, vb);
+        case SCSI_MAINT_IN_OPC:
+            sa = 0x1f & cdbp[1];        /* service action */
+            if (SCSI_REP_SUP_OPCS_OPC == sa)
+                return sntl_rep_opcodes(ptp, cdbp, time_secs, vb);
+            else if (SCSI_REP_SUP_TMFS_OPC == sa)
+                return sntl_rep_tmfs(ptp, cdbp, time_secs, vb);
+            /* fall through */
+        default:
+            if (vb > 2) {
+                char b[64];
+
+                sg_get_command_name(cdbp, -1, sizeof(b), b);
+                pr2ws("%s: no translation to NVMe for SCSI %s command\n",
+                      __func__, b);
+            }
+            mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE,
+                              0, vb);
+            return 0;
+        }
+    }
+    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, (const uint8_t *)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;
+        dp = (void *)ptp->io_hdr.din_xferp;
+        cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+        is_read = true;
+    } else if (ptp->io_hdr.dout_xfer_len > 0) {
+        cmd.data_len = ptp->io_hdr.dout_xfer_len;
+        dp = (void *)ptp->io_hdr.dout_xferp;
+        cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+        is_read = false;
+    }
+    return do_nvme_admin_cmd(ptp, &cmd, dp, is_read, time_secs, vb);
+}
+
+#else           /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+int
+sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb)
+{
+    if (vb)
+        pr2ws("%s: not supported\n", __func__);
+    if (vp) { ; }               /* suppress warning */
+    if (fd) { ; }               /* suppress warning */
+    if (time_secs) { ; }        /* suppress warning */
+    return -ENOTTY;             /* inappropriate ioctl error */
+}
+
+#endif          /* (HAVE_NVME && (! IGNORE_NVME)) */
diff --git a/tools/sg_write_buffer/sg_write_buffer.c b/tools/sg_write_buffer/sg_write_buffer.c
new file mode 100644
index 0000000..18d8f6f
--- /dev/null
+++ b/tools/sg_write_buffer/sg_write_buffer.c
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2006-2018 Luben Tuikov and 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 <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <string.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+#include "sg_pt.h"      /* needed for scsi_pt_win32_direct() */
+#endif
+#endif
+
+/*
+ * This utility issues the SCSI WRITE BUFFER command to the given device.
+ */
+
+static const char * version_str = "1.24 20180111";    /* spc5r18 */
+
+#define ME "sg_write_buffer: "
+#define DEF_XFER_LEN (8 * 1024 * 1024)
+#define EBUFF_SZ 256
+
+#define WRITE_BUFFER_CMD 0x3b
+#define WRITE_BUFFER_CMDLEN 10
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 300      /* 300 seconds, 5 minutes */
+
+static struct option long_options[] = {
+        {"bpw", required_argument, 0, 'b'},
+        {"dry-run", no_argument, 0, 'd'},
+        {"dry_run", no_argument, 0, 'd'},
+        {"help", no_argument, 0, 'h'},
+        {"id", required_argument, 0, 'i'},
+        {"in", required_argument, 0, 'I'},
+        {"length", required_argument, 0, 'l'},
+        {"mode", required_argument, 0, 'm'},
+        {"offset", required_argument, 0, 'o'},
+        {"read-stdin", no_argument, 0, 'r'},
+        {"read_stdin", no_argument, 0, 'r'},
+        {"raw", no_argument, 0, 'r'},
+        {"skip", required_argument, 0, 's'},
+        {"specific", required_argument, 0, 'S'},
+        {"timeout", required_argument, 0, 't' },
+        {"verbose", no_argument, 0, 'v'},
+        {"version", no_argument, 0, 'V'},
+        {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+    pr2serr("Usage: "
+            "sg_write_buffer [--bpw=CS] [--dry-run] [--help] [--id=ID] "
+            "[--in=FILE]\n"
+            "                       [--length=LEN] [--mode=MO] "
+            "[--offset=OFF]\n"
+            "                       [--read-stdin] [--skip=SKIP] "
+            "[--specific=MS]\n"
+            "                       [--timeout=TO] [--verbose] [--version] "
+            "DEVICE\n"
+            "  where:\n"
+            "    --bpw=CS|-b CS         CS is chunk size: bytes per write "
+            "buffer\n"
+            "                           command (def: 0 -> as many as "
+            "possible)\n"
+            "    --dry-run|-d           skip WRITE BUFFER commands, do "
+            "everything else\n"
+            "    --help|-h              print out usage message then exit\n"
+            "    --id=ID|-i ID          buffer identifier (0 (default) to "
+            "255)\n"
+            "    --in=FILE|-I FILE      read from FILE ('-I -' read "
+            "from stdin)\n"
+            "    --length=LEN|-l LEN    length in bytes to write; may be "
+            "deduced from\n"
+            "                           FILE\n"
+            "    --mode=MO|-m MO        write buffer mode, MO is number or "
+            "acronym\n"
+            "                           (def: 0 -> 'combined header and "
+            "data' (obs))\n"
+            "    --offset=OFF|-o OFF    buffer offset (unit: bytes, def: 0)\n"
+            "    --read-stdin|-r        read from stdin (same as '-I -')\n"
+            "    --skip=SKIP|-s SKIP    bytes in file FILE to skip before "
+            "reading\n"
+            "    --specific=MS|-S MS    mode specific value; 3 bit field "
+            "(0 to 7)\n"
+            "    --timeout=TO|-t TO     command timeout in seconds (def: "
+            "300)\n"
+            "    --verbose|-v           increase verbosity\n"
+            "    --version|-V           print version string and exit\n\n"
+            "Performs one or more SCSI WRITE BUFFER commands. Use '-m xxx' "
+            "to list\navailable modes. A chunk size of 4 KB ('--bpw=4k') "
+            "seems to work well.\nExample: sg_write_buffer -b 4k -I xxx.lod "
+            "-m 7 /dev/sg3\n"
+          );
+
+}
+
+#define MODE_HEADER_DATA        0
+#define MODE_VENDOR             1
+#define MODE_DATA               2
+#define MODE_DNLD_MC            4
+#define MODE_DNLD_MC_SAVE       5
+#define MODE_DNLD_MC_OFFS       6
+#define MODE_DNLD_MC_OFFS_SAVE  7
+#define MODE_ECHO_BUFFER        0x0A
+#define MODE_DNLD_MC_EV_OFFS_DEFER 0x0D
+#define MODE_DNLD_MC_OFFS_DEFER 0x0E
+#define MODE_ACTIVATE_MC        0x0F
+#define MODE_EN_EX_ECHO         0x1A
+#define MODE_DIS_EX             0x1B
+#define MODE_DNLD_ERR_HISTORY   0x1C
+
+
+struct mode_s {
+        const char *mode_string;
+        int   mode;
+        const char *comment;
+};
+
+static struct mode_s mode_arr[] = {
+        {"hd",         MODE_HEADER_DATA, "combined header and data "
+                "(obsolete)"},
+        {"vendor",     MODE_VENDOR,    "vendor specific"},
+        {"data",       MODE_DATA,      "data"},
+        {"dmc",        MODE_DNLD_MC,   "download microcode and activate"},
+        {"dmc_save",   MODE_DNLD_MC_SAVE, "download microcode, save and "
+                "activate"},
+        {"dmc_offs",   MODE_DNLD_MC_OFFS, "download microcode with offsets "
+                "and activate"},
+        {"dmc_offs_save", MODE_DNLD_MC_OFFS_SAVE, "download microcode with "
+                "offsets, save and\n\t\t\t\tactivate"},
+        {"echo",       MODE_ECHO_BUFFER, "write data to echo buffer"},
+        {"dmc_offs_ev_defer", MODE_DNLD_MC_EV_OFFS_DEFER, "download "
+                "microcode with offsets, select\n\t\t\t\tactivation event, "
+                "save and defer activation"},
+        {"dmc_offs_defer", MODE_DNLD_MC_OFFS_DEFER, "download microcode "
+                "with offsets, save and\n\t\t\t\tdefer activation"},
+        {"activate_mc", MODE_ACTIVATE_MC, "activate deferred microcode"},
+        {"en_ex",      MODE_EN_EX_ECHO, "enable expander communications "
+                "protocol and\n\t\t\t\techo buffer (obsolete)"},
+        {"dis_ex",     MODE_DIS_EX, "disable expander communications "
+                "protocol\n\t\t\t\t(obsolete)"},
+        {"deh",        MODE_DNLD_ERR_HISTORY, "download application client "
+                "error history "},
+        {NULL, 0, NULL},
+};
+
+static void
+print_modes(void)
+{
+    const struct mode_s * mp;
+
+    pr2serr("The modes parameter argument can be numeric (hex or decimal)\n"
+            "or symbolic:\n");
+    for (mp = mode_arr; mp->mode_string; ++mp) {
+        pr2serr(" %2d (0x%02x)  %-18s%s\n", mp->mode, mp->mode,
+                mp->mode_string, mp->comment);
+    }
+    pr2serr("\nAdditionally '--bpw=<val>,act' does a activate deferred "
+            "microcode after\nsuccessful dmc_offs_defer and "
+            "dmc_offs_ev_defer mode downloads.\n");
+}
+
+
+int
+main(int argc, char * argv[])
+{
+    bool bpw_then_activate = false;
+    bool dry_run = false;
+    bool got_stdin = false;
+    bool wb_len_given = false;
+    int sg_fd, infd, res, c, len, k, n;
+    int bpw = 0;
+    int do_help = 0;
+    int ret = 0;
+    int verbose = 0;
+    int wb_id = 0;
+    int wb_len = 0;
+    int wb_mode = 0;
+    int wb_offset = 0;
+    int wb_skip = 0;
+    int wb_timeout = DEF_PT_TIMEOUT;
+    int wb_mspec = 0;
+    const char * device_name = NULL;
+    const char * file_name = NULL;
+    unsigned char * dop = NULL;
+    char * cp;
+    const struct mode_s * mp;
+    char ebuff[EBUFF_SZ];
+
+    while (1) {
+        int option_index = 0;
+
+        c = getopt_long(argc, argv, "b:dhi:I:l:m:o:rs:S:t:vV", long_options,
+                        &option_index);
+        if (c == -1)
+            break;
+
+        switch (c) {
+        case 'b':
+            bpw = sg_get_num(optarg);
+            if (bpw < 0) {
+                pr2serr("argument to '--bpw' should be in a positive "
+                        "number\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            if ((cp = strchr(optarg, ','))) {
+                if (0 == strncmp("act", cp + 1, 3))
+                    bpw_then_activate = true;
+            }
+            break;
+        case 'd':
+            dry_run = true;
+            break;
+        case 'h':
+        case '?':
+            ++do_help;
+            break;
+        case 'i':
+            wb_id = sg_get_num(optarg);
+            if ((wb_id < 0) || (wb_id > 255)) {
+                pr2serr("argument to '--id' should be in the range 0 to "
+                        "255\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'I':
+            file_name = optarg;
+            break;
+        case 'l':
+            wb_len = sg_get_num(optarg);
+            if (wb_len < 0) {
+                pr2serr("bad argument to '--length'\n");
+                return SG_LIB_SYNTAX_ERROR;
+             }
+             wb_len_given = true;
+             break;
+        case 'm':
+            if (isdigit(*optarg)) {
+                wb_mode = sg_get_num(optarg);
+                if ((wb_mode < 0) || (wb_mode > 31)) {
+                    pr2serr("argument to '--mode' should be in the range 0 "
+                            "to 31\n");
+                    return SG_LIB_SYNTAX_ERROR;
+                }
+            } else {
+                len = strlen(optarg);
+                for (mp = mode_arr; mp->mode_string; ++mp) {
+                    if (0 == strncmp(mp->mode_string, optarg, len)) {
+                        wb_mode = mp->mode;
+                        break;
+                    }
+                }
+                if (! mp->mode_string) {
+                    print_modes();
+                    return SG_LIB_SYNTAX_ERROR;
+                }
+            }
+            break;
+        case 'o':
+           wb_offset = sg_get_num(optarg);
+           if (wb_offset < 0) {
+                pr2serr("bad argument to '--offset'\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'r':       /* --read-stdin and --raw (previous name) */
+            file_name = "-";
+            break;
+        case 's':
+           wb_skip = sg_get_num(optarg);
+           if (wb_skip < 0) {
+                pr2serr("bad argument to '--skip'\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'S':
+            wb_mspec = sg_get_num(optarg);
+            if ((wb_mspec < 0) || (wb_mspec > 7)) {
+                pr2serr("expected argument to '--specific' to be 0 to 7\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 't':
+            wb_timeout = sg_get_num(optarg);
+            if (wb_timeout < 0) {
+                pr2serr("Invalid argument to '--timeout'\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'v':
+            ++verbose;
+            break;
+        case 'V':
+            pr2serr(ME "version: %s\n", version_str);
+            return 0;
+        default:
+            pr2serr("unrecognised option code 0x%x ??\n", c);
+            usage();
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+    if (do_help) {
+        if (do_help > 1) {
+            usage();
+            pr2serr("\n");
+            print_modes();
+        } else
+            usage();
+        return 0;
+    }
+    if (optind < argc) {
+        if (NULL == device_name) {
+            device_name = argv[optind];
+            ++optind;
+        }
+        if (optind < argc) {
+            for (; optind < argc; ++optind)
+                pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+            usage();
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+
+    if (NULL == device_name) {
+        pr2serr("missing device name!\n");
+        usage();
+        return SG_LIB_SYNTAX_ERROR;
+    }
+
+    if ((wb_len > 0) && (bpw > wb_len)) {
+        pr2serr("trim chunk size (CS) to be the same as LEN\n");
+        bpw = wb_len;
+    }
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+    if (verbose > 4)
+        pr2serr("Initial win32 SPT interface state: %s\n",
+                scsi_pt_win32_spt_state() ? "direct" : "indirect");
+    scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
+#endif
+#endif
+
+    sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+    if (sg_fd < 0) {
+        pr2serr(ME "open error: %s: %s\n", device_name,
+                safe_strerror(-sg_fd));
+        return SG_LIB_FILE_ERROR;
+    }
+    if (file_name || (wb_len > 0)) {
+        if (0 == wb_len)
+            wb_len = DEF_XFER_LEN;
+        if (NULL == (dop = (unsigned char *)malloc(wb_len))) {
+            pr2serr(ME "out of memory\n");
+            ret = SG_LIB_SYNTAX_ERROR;
+            goto err_out;
+        }
+        memset(dop, 0xff, wb_len);
+        if (file_name) {
+            got_stdin = (0 == strcmp(file_name, "-"));
+            if (got_stdin) {
+                if (wb_skip > 0) {
+                    pr2serr("Can't skip on stdin\n");
+                    ret = SG_LIB_FILE_ERROR;
+                    goto err_out;
+                }
+                infd = STDIN_FILENO;
+            } else {
+                if ((infd = open(file_name, O_RDONLY)) < 0) {
+                    snprintf(ebuff, EBUFF_SZ,
+                             ME "could not open %s for reading", file_name);
+                    perror(ebuff);
+                    ret = SG_LIB_FILE_ERROR;
+                    goto err_out;
+                } else if (sg_set_binary_mode(infd) < 0)
+                    perror("sg_set_binary_mode");
+                if (wb_skip > 0) {
+                    if (lseek(infd, wb_skip, SEEK_SET) < 0) {
+                        snprintf(ebuff,  EBUFF_SZ, ME "couldn't skip to "
+                                 "required position on %s", file_name);
+                        perror(ebuff);
+                        close(infd);
+                        ret = SG_LIB_FILE_ERROR;
+                        goto err_out;
+                    }
+                }
+            }
+            res = read(infd, dop, wb_len);
+            if (res < 0) {
+                snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
+                         file_name);
+                perror(ebuff);
+                if (! got_stdin)
+                    close(infd);
+                ret = SG_LIB_FILE_ERROR;
+                goto err_out;
+            }
+            if (res < wb_len) {
+                if (wb_len_given) {
+                    pr2serr("tried to read %d bytes from %s, got %d bytes\n",
+                            wb_len, file_name, res);
+                    pr2serr("pad with 0xff bytes and continue\n");
+                } else {
+                    if (verbose) {
+                        pr2serr("tried to read %d bytes from %s, got %d "
+                                "bytes\n", wb_len, file_name, res);
+                        pr2serr("will write %d bytes", res);
+                        if ((bpw > 0) && (bpw < wb_len))
+                            pr2serr(", %d bytes per WRITE BUFFER command\n",
+                                    bpw);
+                        else
+                            pr2serr("\n");
+                    }
+                    wb_len = res;
+                }
+            }
+            if (! got_stdin)
+                close(infd);
+        }
+    }
+
+    res = 0;
+    if (bpw > 0) {
+        for (k = 0; k < wb_len; k += n) {
+            n = wb_len - k;
+            if (n > bpw)
+                n = bpw;
+            if (verbose)
+                pr2serr("sending write buffer, mode=0x%x, mspec=%d, id=%d, "
+                        " offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
+                        wb_offset + k, n);
+            if (dry_run) {
+                if (verbose)
+                    pr2serr("skipping WRITE BUFFER command due to "
+                            "--dry-run\n");
+                res = 0;
+            } else
+                res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
+                                            wb_offset + k, dop + k, n,
+                                            wb_timeout, true, verbose);
+            if (res)
+                break;
+        }
+        if (bpw_then_activate) {
+            if (verbose)
+                pr2serr("sending Activate deferred microcode [0xf]\n");
+            if (dry_run) {
+                if (verbose)
+                    pr2serr("skipping WRITE BUFFER(ACTIVATE) command due to "
+                            "--dry-run\n");
+                res = 0;
+            } else
+                res = sg_ll_write_buffer_v2(sg_fd, MODE_ACTIVATE_MC,
+                                            0 /* buffer_id */,
+                                            0 /* buffer_offset */, 0,
+                                            NULL, 0, wb_timeout, true,
+                                            verbose);
+        }
+    } else {
+        if (verbose)
+            pr2serr("sending single write buffer, mode=0x%x, mpsec=%d, "
+                    "id=%d, offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
+                    wb_offset, wb_len);
+        if (dry_run) {
+            if (verbose)
+                pr2serr("skipping WRITE BUFFER(all in one) command due to "
+                        "--dry-run\n");
+            res = 0;
+        } else
+            res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
+                                        wb_offset, dop, wb_len, wb_timeout,
+                                        true, verbose);
+    }
+    if (0 != res) {
+        char b[80];
+
+        ret = res;
+        sg_get_category_sense_str(res, sizeof(b), b, verbose);
+        pr2serr("Write buffer failed: %s\n", b);
+    }
+
+err_out:
+    if (dop)
+        free(dop);
+    res = sg_cmds_close_device(sg_fd);
+    if (res < 0) {
+        pr2serr("close error: %s\n", safe_strerror(-res));
+        if (0 == ret)
+            return SG_LIB_FILE_ERROR;
+    }
+    return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
-- 
2.15.0.531.g2ccb3012c9-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU
  2018-03-14  0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim
@ 2018-03-14  2:10   ` Junling Zheng
  2018-03-14  4:27     ` [PATCH 2/2 v2] " Jaegeuk Kim
  2018-03-16  8:29   ` [PATCH 2/2] " Chao Yu
  1 sibling, 1 reply; 8+ messages in thread
From: Junling Zheng @ 2018-03-14  2:10 UTC (permalink / raw)
  To: Jaegeuk Kim, linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim

Hi, Hyojun, Jaegeuk,

On 2018/3/14 8:08, Jaegeuk Kim wrote:
> From: Hyojun Kim <hyojun@google.com>
> 
> sg_write_buffer sources are added for FFU.
> 
> Signed-off-by: Hyojun Kim <hyojun@google.com>
> Signed-off-by: Jaegeuk Kim <jaegeuk@google.com>
> ---
>  tools/sg_write_buffer/Android.bp                   |   27 +

Maybe it's better to change Android.bp into Makefile.am ?

Thanks,
Junling


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2 v2] tools: sg_write_buffer: add sg_write_buffer for FFU
  2018-03-14  2:10   ` Junling Zheng
@ 2018-03-14  4:27     ` Jaegeuk Kim
  0 siblings, 0 replies; 8+ messages in thread
From: Jaegeuk Kim @ 2018-03-14  4:27 UTC (permalink / raw)
  To: Junling Zheng; +Cc: Hyojun Kim, Jaegeuk Kim, linux-f2fs-devel

sg_write_buffer sources are added for FFU.

Signed-off-by: Hyojun Kim <hyojun@google.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@google.com>
---

v2:
- add Makefile.am
- fix some build errors

 configure.ac                                       |    1 +
 tools/Makefile.am                                  |    2 +
 tools/sg_write_buffer/Android.bp                   |   27 +
 tools/sg_write_buffer/Makefile.am                  |   18 +
 tools/sg_write_buffer/include/freebsd_nvme_ioctl.h |  156 +
 tools/sg_write_buffer/include/sg_cmds.h            |   21 +
 tools/sg_write_buffer/include/sg_cmds_basic.h      |  310 ++
 tools/sg_write_buffer/include/sg_cmds_extra.h      |  369 +++
 tools/sg_write_buffer/include/sg_cmds_mmc.h        |   52 +
 tools/sg_write_buffer/include/sg_io_linux.h        |  185 ++
 tools/sg_write_buffer/include/sg_lib.h             |  602 ++++
 tools/sg_write_buffer/include/sg_lib_data.h        |  121 +
 tools/sg_write_buffer/include/sg_linux_inc.h       |   56 +
 tools/sg_write_buffer/include/sg_pr2serr.h         |   30 +
 tools/sg_write_buffer/include/sg_pt.h              |  215 ++
 tools/sg_write_buffer/include/sg_pt_linux.h        |  171 +
 tools/sg_write_buffer/include/sg_pt_nvme.h         |  172 +
 tools/sg_write_buffer/include/sg_pt_win32.h        |  473 +++
 tools/sg_write_buffer/include/sg_unaligned.h       |  325 ++
 tools/sg_write_buffer/sg_cmds_basic.c              |  663 ++++
 tools/sg_write_buffer/sg_cmds_basic2.c             | 1069 ++++++
 tools/sg_write_buffer/sg_cmds_extra.c              | 2524 ++++++++++++++
 tools/sg_write_buffer/sg_cmds_mmc.c                |  382 +++
 tools/sg_write_buffer/sg_io_linux.c                |  256 ++
 tools/sg_write_buffer/sg_lib.c                     | 3494 ++++++++++++++++++++
 tools/sg_write_buffer/sg_lib_data.c                | 1688 ++++++++++
 tools/sg_write_buffer/sg_pt_common.c               |  141 +
 tools/sg_write_buffer/sg_pt_linux.c                |  964 ++++++
 tools/sg_write_buffer/sg_pt_linux_nvme.c           | 1185 +++++++
 tools/sg_write_buffer/sg_write_buffer.c            |  516 +++
 30 files changed, 16188 insertions(+)
 create mode 100644 tools/sg_write_buffer/Android.bp
 create mode 100644 tools/sg_write_buffer/Makefile.am
 create mode 100644 tools/sg_write_buffer/include/freebsd_nvme_ioctl.h
 create mode 100644 tools/sg_write_buffer/include/sg_cmds.h
 create mode 100644 tools/sg_write_buffer/include/sg_cmds_basic.h
 create mode 100644 tools/sg_write_buffer/include/sg_cmds_extra.h
 create mode 100644 tools/sg_write_buffer/include/sg_cmds_mmc.h
 create mode 100644 tools/sg_write_buffer/include/sg_io_linux.h
 create mode 100644 tools/sg_write_buffer/include/sg_lib.h
 create mode 100644 tools/sg_write_buffer/include/sg_lib_data.h
 create mode 100644 tools/sg_write_buffer/include/sg_linux_inc.h
 create mode 100644 tools/sg_write_buffer/include/sg_pr2serr.h
 create mode 100644 tools/sg_write_buffer/include/sg_pt.h
 create mode 100644 tools/sg_write_buffer/include/sg_pt_linux.h
 create mode 100644 tools/sg_write_buffer/include/sg_pt_nvme.h
 create mode 100644 tools/sg_write_buffer/include/sg_pt_win32.h
 create mode 100644 tools/sg_write_buffer/include/sg_unaligned.h
 create mode 100644 tools/sg_write_buffer/sg_cmds_basic.c
 create mode 100644 tools/sg_write_buffer/sg_cmds_basic2.c
 create mode 100644 tools/sg_write_buffer/sg_cmds_extra.c
 create mode 100644 tools/sg_write_buffer/sg_cmds_mmc.c
 create mode 100644 tools/sg_write_buffer/sg_io_linux.c
 create mode 100644 tools/sg_write_buffer/sg_lib.c
 create mode 100644 tools/sg_write_buffer/sg_lib_data.c
 create mode 100644 tools/sg_write_buffer/sg_pt_common.c
 create mode 100644 tools/sg_write_buffer/sg_pt_linux.c
 create mode 100644 tools/sg_write_buffer/sg_pt_linux_nvme.c
 create mode 100644 tools/sg_write_buffer/sg_write_buffer.c

diff --git a/configure.ac b/configure.ac
index ea57d54..64c156e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -196,6 +196,7 @@ AC_CONFIG_FILES([
 	mkfs/Makefile
 	fsck/Makefile
 	tools/Makefile
+	tools/sg_write_buffer/Makefile
 ])
 
 # export library version info for mkfs/libf2fs_format_la
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 81cf89b..25a8c6f 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -13,3 +13,5 @@ f2fscrypt_SOURCES = f2fscrypt.c sha512.c
 f2fscrypt_LDFLAGS = -luuid
 dist_man_MANS = f2fscrypt.8
 endif
+
+SUBDIRS = sg_write_buffer
diff --git a/tools/sg_write_buffer/Android.bp b/tools/sg_write_buffer/Android.bp
new file mode 100644
index 0000000..5222a59
--- /dev/null
+++ b/tools/sg_write_buffer/Android.bp
@@ -0,0 +1,27 @@
+cc_defaults {
+    name: "sg3-utils-defaults",
+    cflags: [
+        "-Wno-unused-function"
+    ],
+    local_include_dirs: [
+        "include",
+    ],
+}
+
+cc_binary {
+    name: "sg_write_buffer",
+    defaults: [ "sg3-utils-defaults" ],
+    srcs: [
+        "sg_write_buffer.c",
+        "sg_cmds_basic.c",
+        "sg_cmds_basic2.c",
+        "sg_cmds_extra.c",
+        "sg_cmds_mmc.c",
+        "sg_io_linux.c",
+        "sg_lib.c",
+        "sg_lib_data.c",
+        "sg_pt_common.c",
+        "sg_pt_linux.c",
+        "sg_pt_linux_nvme.c",
+    ],
+}
diff --git a/tools/sg_write_buffer/Makefile.am b/tools/sg_write_buffer/Makefile.am
new file mode 100644
index 0000000..922c328
--- /dev/null
+++ b/tools/sg_write_buffer/Makefile.am
@@ -0,0 +1,18 @@
+## Makefile.am
+
+if LINUX
+AM_CPPFLAGS = -I./include
+AM_CFLAGS = -Wall
+sbin_PROGRAMS = sg_write_buffer
+sg_write_buffer_SOURCES = sg_write_buffer.c \
+	sg_cmds_basic.c		\
+	sg_cmds_basic2.c	\
+	sg_cmds_extra.c		\
+	sg_cmds_mmc.c		\
+	sg_io_linux.c		\
+	sg_lib.c		\
+	sg_lib_data.c		\
+	sg_pt_common.c		\
+	sg_pt_linux.c		\
+	sg_pt_linux_nvme.c
+endif
diff --git a/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h b/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h
new file mode 100644
index 0000000..f5d2443
--- /dev/null
+++ b/tools/sg_write_buffer/include/freebsd_nvme_ioctl.h
@@ -0,0 +1,156 @@
+PROPS-END
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+
+#include <sys/param.h>
+
+#define	NVME_PASSTHROUGH_CMD	_IOWR('n', 0, struct nvme_pt_command)
+
+#if __FreeBSD_version < 1100110
+struct nvme_command
+{
+	/* dword 0 */
+	uint16_t opc	:  8;	/* opcode */
+	uint16_t fuse	:  2;	/* fused operation */
+	uint16_t rsvd1	:  6;
+	uint16_t cid;		/* command identifier */
+
+	/* dword 1 */
+	uint32_t nsid;		/* namespace identifier */
+
+	/* dword 2-3 */
+	uint32_t rsvd2;
+	uint32_t rsvd3;
+
+	/* dword 4-5 */
+	uint64_t mptr;		/* metadata pointer */
+
+	/* dword 6-7 */
+	uint64_t prp1;		/* prp entry 1 */
+
+	/* dword 8-9 */
+	uint64_t prp2;		/* prp entry 2 */
+
+	/* dword 10-15 */
+	uint32_t cdw10;		/* command-specific */
+	uint32_t cdw11;		/* command-specific */
+	uint32_t cdw12;		/* command-specific */
+	uint32_t cdw13;		/* command-specific */
+	uint32_t cdw14;		/* command-specific */
+	uint32_t cdw15;		/* command-specific */
+} __packed;
+
+struct nvme_status {
+
+	uint16_t p	:  1;	/* phase tag */
+	uint16_t sc	:  8;	/* status code */
+	uint16_t sct	:  3;	/* status code type */
+	uint16_t rsvd2	:  2;
+	uint16_t m	:  1;	/* more */
+	uint16_t dnr	:  1;	/* do not retry */
+} __packed;
+
+struct nvme_completion {
+
+	/* dword 0 */
+	uint32_t		cdw0;	/* command-specific */
+
+	/* dword 1 */
+	uint32_t		rsvd1;
+
+	/* dword 2 */
+	uint16_t		sqhd;	/* submission queue head pointer */
+	uint16_t		sqid;	/* submission queue identifier */
+
+	/* dword 3 */
+	uint16_t		cid;	/* command identifier */
+	struct nvme_status	status;
+} __packed;
+
+struct nvme_pt_command {
+
+	/*
+	 * cmd is used to specify a passthrough command to a controller or
+	 *  namespace.
+	 *
+	 * The following fields from cmd may be specified by the caller:
+	 *	* opc  (opcode)
+	 *	* nsid (namespace id) - for admin commands only
+	 *	* cdw10-cdw15
+	 *
+	 * Remaining fields must be set to 0 by the caller.
+	 */
+	struct nvme_command	cmd;
+
+	/*
+	 * cpl returns completion status for the passthrough command
+	 *  specified by cmd.
+	 *
+	 * The following fields will be filled out by the driver, for
+	 *  consumption by the caller:
+	 *	* cdw0
+	 *	* status (except for phase)
+	 *
+	 * Remaining fields will be set to 0 by the driver.
+	 */
+	struct nvme_completion	cpl;
+
+	/* buf is the data buffer associated with this passthrough command. */
+	void *			buf;
+
+	/*
+	 * len is the length of the data buffer associated with this
+	 *  passthrough command.
+	 */
+	uint32_t		len;
+
+	/*
+	 * is_read = 1 if the passthrough command will read data into the
+	 *  supplied buffer from the controller.
+	 *
+	 * is_read = 0 if the passthrough command will write data from the
+	 *  supplied buffer to the controller.
+	 */
+	uint32_t		is_read;
+
+	/*
+	 * driver_lock is used by the driver only.  It must be set to 0
+	 *  by the caller.
+	 */
+	struct mtx *		driver_lock;
+};
+#else
+#include <dev/nvme/nvme.h>
+#endif
+
+#define nvme_completion_is_error(cpl)					\
+	((cpl)->status.sc != 0 || (cpl)->status.sct != 0)
+
+#define NVME_CTRLR_PREFIX	"/dev/nvme"
+#define NVME_NS_PREFIX		"ns"
diff --git a/tools/sg_write_buffer/include/sg_cmds.h b/tools/sg_write_buffer/include/sg_cmds.h
new file mode 100644
index 0000000..690f53a
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_cmds.h
@@ -0,0 +1,21 @@
+#ifndef SG_CMDS_H
+#define SG_CMDS_H
+
+/********************************************************************
+ * This header did contain wrapper declarations for many SCSI commands
+ * up until sg3_utils version 1.22 . In that version, the command
+ * wrappers were broken into two groups, the 'basic' ones found in the
+ * "sg_cmds_basic.h" header and the 'extra' ones found in the
+ * "sg_cmds_extra.h" header. This header now simply includes those two
+ * headers.
+ * In sg3_utils version 1.26 the sg_cmds_mmc.h header was added and
+ * contains some MMC specific commands.
+ * The corresponding function definitions are found in the sg_cmds_basic.c,
+ * sg_cmds_extra.c and sg_cmds_mmc.c files.
+ ********************************************************************/
+
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_cmds_mmc.h"
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_cmds_basic.h b/tools/sg_write_buffer/include/sg_cmds_basic.h
new file mode 100644
index 0000000..507effa
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_cmds_basic.h
@@ -0,0 +1,310 @@
+#ifndef SG_CMDS_BASIC_H
+#define SG_CMDS_BASIC_H
+
+/*
+ * Copyright (c) 2004-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.
+ */
+
+/*
+ * Error, warning and verbose output is sent to the file pointed to by
+ * sg_warnings_strm which is declared in sg_lib.h and can be set with
+ * the sg_set_warnings_strm() function. If not given sg_warnings_strm
+ * defaults to stderr.
+ * If 'noisy' is false and 'verbose' is zero then following functions should
+ * not output anything to sg_warnings_strm. If 'noisy' is true and
+ * 'verbose' is zero then Unit Attention, Recovered, Medium and Hardware
+ * errors (sense keys) send output to sg_warnings_strm. Increasing values
+ * of 'verbose' send increasing amounts of (debug) output to
+ * sg_warnings_strm.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Invokes a SCSI INQUIRY command and yields the response
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other errors */
+int sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+                  int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so
+ * 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 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
+ * bytes have been written. */
+int
+sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp,
+                 int mx_resp_len, int timeout_secs, int * residp,
+                 bool noisy, int verbose);
+
+/* Invokes a SCSI LOG SELECT command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Log Select not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ABORTED_COMMAND, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code,
+                     int subpg_code, unsigned char * paramp, int param_len,
+                     bool noisy, int verbose);
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Log Sense not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+                    int subpg_code, int paramp, unsigned char * resp,
+                    int mx_resp_len, bool noisy, int verbose);
+
+/* Same as sg_ll_log_sense() apart from timeout_secs and residp. See
+ * sg_ll_inquiry_v2() for their description */
+int sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+                       int subpg_code, int paramp, unsigned char * resp,
+                       int mx_resp_len, int timeout_secs, int * residp,
+                       bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SELECT (6) command.  Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp,
+                        int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SELECT (10) command.  Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp,
+                        int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code,
+                      int sub_pg_code, void * resp, int mx_resp_len,
+                      bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+                       int sub_pg_code, void * resp, int mx_resp_len,
+                       bool noisy, int verbose);
+
+/* Same as sg_ll_mode_sense10() apart from timeout_secs and residp. See
+ * sg_ll_inquiry_v2() for their description */
+int sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc,
+                          int pg_code, int sub_pg_code, void * resp,
+                          int mx_resp_len, int timeout_secs, int * residp,
+                          bool noisy, int verbose);
+
+/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command (SPC-3)
+ * prevent==0 allows removal, prevent==1 prevents removal ...
+ * Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> command not supported
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose);
+
+/* Invokes a SCSI READ CAPACITY (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION
+ * -> perhaps media changed, SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp,
+                     int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success,
+ * SG_LIB_CAT_UNIT_ATTENTION -> media changed??, SG_LIB_CAT_INVALID_OP
+ *  -> cdb not supported, SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp,
+                     int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Luns not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */
+int sg_ll_report_luns(int sg_fd, int select_report, void * resp,
+                      int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI REQUEST SENSE command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Request Sense not supported??,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len,
+                        bool noisy, int verbose);
+
+/* Invokes a SCSI START STOP UNIT command (SBC + MMC).
+ * Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Start stop unit not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure
+ * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and
+ * format_layer_number(mmc) fields. They also overlap on the noflush(sbc)
+ * and fl(mmc) one bit field. This is the cause of the awkardly named
+ * pc_mod__fl_num and noflush__fl arguments to this function.  */
+int sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
+                          int power_cond, bool noflush__fl, bool loej,
+                          bool start, bool noisy, int verbose);
+
+/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_INVALID_OP -> cdb not supported,
+ * SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */
+int sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group,
+                        unsigned int lba, unsigned int count, bool noisy,
+                        int verbose);
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other failure */
+int sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose);
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Looks for progress indicator if 'progress' non-NULL;
+ * if found writes value [0..65535] else write -1.
+ * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ABORTED_COMMAND, SG_LIB_CAT_NOT_READY ->
+ * device not ready, -1 -> other failure */
+int sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress,
+                                   bool noisy, int verbose);
+
+
+struct sg_simple_inquiry_resp {
+    unsigned char peripheral_qualifier;
+    unsigned char peripheral_type;
+    unsigned char byte_1;       /* was 'rmb' prior to version 1.39 */
+                                /* now rmb == !!(0x80 & byte_1) */
+    unsigned char version;      /* as per recent drafts: whole of byte 2 */
+    unsigned char byte_3;
+    unsigned char byte_5;
+    unsigned char byte_6;
+    unsigned char byte_7;
+    char vendor[9];             /* T10 field is 8 bytes, NUL char appended */
+    char product[17];
+    char revision[5];
+};
+
+/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response.
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other errors */
+int sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data,
+                      bool noisy, int verbose);
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. In most cases users are
+ * interested in the first mode page. This function returns the (byte)
+ * offset of the start of the first mode page. Set mode_sense_6 to true for
+ * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful
+ * or -1 if failure. If there is a failure a message is written to err_buff
+ * if it is non-NULL and err_buff_len > 0. */
+int sg_mode_page_offset(const unsigned char * resp, int resp_len,
+                        bool mode_sense_6, char * err_buff, int err_buff_len);
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. This functions returns the
+ * length (in bytes) of those three components. Note that the return value
+ * can exceed resp_len in which case the MODE SENSE command should be
+ * re-issued with a larger response buffer. If bd_lenp is non-NULL and if
+ * successful the block descriptor length (in bytes) is written to *bd_lenp.
+ * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10)
+ * responses. Returns -1 if there is an error (e.g. response too short). */
+int sg_msense_calc_length(const unsigned char * resp, int resp_len,
+                          bool mode_sense_6, int * bd_lenp);
+
+/* Fetches current, changeable, default and/or saveable modes pages as
+ * indicated by pcontrol_arr for given pg_code and sub_pg_code. If
+ * mode6==0 then use MODE SENSE (10) else use MODE SENSE (6). If
+ * flexible set and mode data length seems wrong then try and
+ * fix (compensating hack for bad device or driver). pcontrol_arr
+ * should have 4 elements for output of current, changeable, default
+ * and saved values respectively. Each element should be NULL or
+ * at least mx_mpage_len bytes long.
+ * Return of 0 -> overall success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_MALFORMED -> bad response, -1 -> other failure.
+ * If success_mask pointer is not NULL then first zeros it. Then set bits
+ * 0, 1, 2 and/or 3 if the current, changeable, default and saved values
+ * respectively have been fetched. If error on current page
+ * then stops and returns that error; otherwise continues if an error is
+ * detected but returns the first error encountered.  */
+int sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code,
+                              int sub_pg_code, bool dbd, bool flexible,
+                              int mx_mpage_len, int * success_mask,
+                              void * pcontrol_arr[], int * reported_lenp,
+                              int verbose);
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+   negated errno. Implementation calls scsi_pt_open_device(). */
+int sg_cmds_open_device(const char * device_name, bool read_only, int verbose);
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+   negated errno. Implementation calls scsi_pt_open_flags(). */
+int sg_cmds_open_flags(const char * device_name, int flags, int verbose);
+
+/* Returns 0 if successful. If error in Unix returns negated errno.
+   Implementation calls scsi_pt_close_device(). */
+int sg_cmds_close_device(int device_fd);
+
+const char * sg_cmds_version();
+
+#define SG_NO_DATA_IN 0
+
+struct sg_pt_base;
+
+/* This is a helper function used by sg_cmds_* implementations after the
+ * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid
+ * sense data is found it is decoded and output to sg_warnings_strm (def:
+ * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for
+ * sense data (may not be fatal), -1 for failed, 0, or a positive number. If
+ * 'mx_di_len > 0' then asks pass-through for resid and returns
+ * (mx_di_len - resid); otherwise returns 0. So for data-in it should return
+ * the actual number of bytes received. For data-out (to device) or no data
+ * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category
+ * output via 'o_sense_cat' pointer (if not NULL). Note that several sense
+ * categories also have data in bytes received; -2 is still returned. */
+int sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin,
+                         int pt_res, int mx_di_len,
+                         const unsigned char * sense_b, bool noisy,
+                         int verbose, int * o_sense_cat);
+
+/* NVMe devices use a different command set. This function will return true
+ * if the device associated with 'pvtp' is a NVME device, else it will
+ * return false (e.g. for SCSI devices). */
+bool sg_cmds_is_nvme(const struct sg_pt_base * ptvp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_cmds_extra.h b/tools/sg_write_buffer/include/sg_cmds_extra.h
new file mode 100644
index 0000000..fbc2377
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_cmds_extra.h
@@ -0,0 +1,369 @@
+#ifndef SG_CMDS_EXTRA_H
+#define SG_CMDS_EXTRA_H
+
+/*
+ * Copyright (c) 2004-2018 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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Note: all functions that have an 'int timeout_secs' argument will use
+ * that value if it is > 0. Otherwise they will set an internal default
+ * which is currently 60 seconds. This timeout is typically applied in the
+ * SCSI stack above the initiator. If it goes off then the SCSI command is
+ * aborted and there can be other unwelcome side effects. Note that some
+ * commands (e.g. FORMAT UNIT and the Third Party copy commands) can take
+ * a lot longer than the default timeout. */
+
+
+/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is
+ * selected by the cdb_len argument that can take values of 12, 16 or 32
+ * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9
+ * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from
+ * the control byte, the rest is copied into an internal cdb which is then
+ * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15
+ * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the
+ * timeout is set to 60 seconds. For data in or out transfers set dinp or
+ * doutp, and dlen to the number of bytes to transfer. If dlen is zero then
+ * no data transfer is assumed. If sense buffer obtained then it is written
+ * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is
+ * obtained then written to ata_return_dp, else ata_return_dp[0] is set to
+ * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers.
+ * Returns SCSI status value (>= 0) or -1 if other error. Users are
+ * expected to check the sense buffer themselves. If available the data in
+ * resid is written to residp. Note in SAT-2 and later, fixed format sense
+ * data may be placed in *sensep in which case sensep[0]==0x70, prior to
+ * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72).
+ */
+int sg_ll_ata_pt(int sg_fd, const unsigned char * cdbp, int cdb_len,
+                 int timeout_secs,  void * dinp, void * doutp, int dlen,
+                 unsigned char * sensep, int max_sense_len,
+                 unsigned char * ata_return_dp, int max_ata_return_len,
+                 int * residp, int verbose);
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Format unit not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure. Note that sg_ll_format_unit2() and
+ * sg_ll_format_unit_v2() are the same, both add the ffmt argument. */
+int sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                      bool cmplist, int dlist_format, int timeout_secs,
+                      void * paramp, int param_len, bool noisy, int verbose);
+int sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                       bool cmplist, int dlist_format, int ffmt,
+                       int timeout_secs, void * paramp, int param_len,
+                       bool noisy, int verbose);
+int sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                         bool cmplist, int dlist_format, int ffmt,
+                         int timeout_secs, void * paramp, int param_len,
+                         bool noisy, int verbose);
+
+/* Invokes a SCSI GET LBA STATUS(16) or GET LBA STATUS(32) command (SBC).
+ * Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> GET LBA STATUS(16 or 32) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure.
+ * sg_ll_get_lba_status() calls the 16 byte variant with rt=0 . */
+int sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp,
+                         int alloc_len, bool noisy, int verbose);
+int sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt,
+                           void * resp, int alloc_len, bool noisy,
+                           int verbose);
+int sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len,
+                           uint32_t element_id, uint8_t rt,
+                           void * resp, int alloc_len, bool noisy,
+                           int verbose);
+
+/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0
+ * when successful, SG_LIB_CAT_INVALID_OP if command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp,
+                                int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0
+ * when successful, SG_LIB_CAT_INVALID_OP if command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope,
+                                 unsigned int rq_type, void * paramp,
+                                 int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ BLOCK LIMITS not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */
+int sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len,
+                            bool noisy, int verbose);
+
+/* Invokes a SCSI READ BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+                      void * resp, int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist,
+                        int dl_format, void * resp, int mx_resp_len,
+                        bool noisy, int verbose);
+
+/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ LONG(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba,
+                      void * resp, int xfer_len, int * offsetp, bool noisy,
+                      int verbose);
+
+/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ LONG(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ *  -1 -> other failure */
+int sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba,
+                      void * resp, int xfer_len, int * offsetp, bool noisy,
+                      int verbose);
+
+/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Read media serial number not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len,
+                                bool noisy, int verbose);
+
+/* Invokes a SCSI REASSIGN BLOCKS command.  Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */
+int sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist,
+                          void * paramp, int param_len, bool noisy,
+                          int verbose);
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Receive diagnostic results not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp,
+                       int mx_resp_len, bool noisy, int verbose);
+
+/* 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 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
+ * bytes have been written. */
+int sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp,
+                          int mx_resp_len, int timeout_secs, int * residp,
+                          bool noisy, int verbose);
+
+/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was
+ * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report identifying information not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len,
+                         bool noisy, int verbose);
+
+/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
+int sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len,
+                             bool noisy, int verbose);
+int sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len,
+                              bool extended, bool noisy, int verbose);
+
+/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
+int sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy,
+                          int verbose);
+
+/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Referrals not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
+int sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg,
+                           void * resp, int mx_resp_len, bool noisy,
+                           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 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,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit,
+                    bool devofl_bit, bool unitofl_bit, int long_duration,
+                    void * paramp, int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was
+ * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set identifying information not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len,
+                      bool noisy, int verbose);
+
+/* Invokes a SCSI UNMAP (SBC-3) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
+int sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp,
+                int param_len, bool noisy, int verbose);
+/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field
+ * (sbc3r22). Otherwise same as sg_ll_unmap() . */
+int sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs,
+                   void * paramp, int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI VERIFY (10) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Verify(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info,
+ * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */
+int sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytechk,
+                   unsigned int lba, int veri_len, void * data_out,
+                   int data_out_len, unsigned int * infop, bool noisy,
+                   int verbose);
+
+/* Invokes a SCSI VERIFY (16) command (SBC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Verify(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info,
+ * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */
+int sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytechk,
+                   uint64_t llba, int veri_len, int group_num,
+                   void * data_out, int data_out_len, uint64_t * infop,
+                   bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+                       void * paramp, int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure. Adds mode specific field (spc4r32) and timeout
+ *  to command abort to override default of 60 seconds. If timeout_secs is
+ *  0 or less then the default timeout is used instead. */
+int
+sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id,
+                      uint32_t buffer_offset, void * paramp,
+                      uint32_t param_len, int timeout_secs, bool noisy,
+                      int verbose);
+
+/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> WRITE LONG(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+                       unsigned int lba, void * data_out, int xfer_len,
+                       int * offsetp, bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> WRITE LONG(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+                       uint64_t llba, void * data_out, int xfer_len,
+                       int * offsetp, bool noisy, int verbose);
+
+/* Invokes a SPC-3 SCSI RECEIVE COPY RESULTS command. In SPC-4 this function
+ * supports all service action variants of the THIRD-PARTY COPY IN opcode.
+ * SG_LIB_CAT_INVALID_OP -> Receive copy results not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp,
+                               int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI EXTENDED COPY(LID1) command. For EXTENDED COPY(LID4)
+ * including POPULATE TOKEN and WRITE USING TOKEN use
+ * sg_ll_3party_copy_out().  Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Extended copy not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy,
+                        int verbose);
+
+/* Handles various service actions associated with opcode 0x83 which is
+ * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID4),
+ * POPULATE TOKEN and WRITE USING TOKEN commands. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> opcode 0x83 not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id,
+                          int group_num, int timeout_secs, void * paramp,
+                          int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC).
+ * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_*
+ * positive values or -1 -> other errors. Note that CONDITION MET status
+ * is returned when immed=true and num_blocks can fit in device's cache,
+ * somewaht strangely, GOOD status (return 0) is returned if num_blocks
+ * cannot fit in device's cache. If do_seek10==true then does a SEEK(10)
+ * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10)
+ * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then
+ * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */
+int sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed,
+                      uint64_t lba, uint32_t num_blocks, int group_num,
+                      int timeout_secs, bool noisy, int verbose);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_cmds_mmc.h b/tools/sg_write_buffer/include/sg_cmds_mmc.h
new file mode 100644
index 0000000..3988b1d
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_cmds_mmc.h
@@ -0,0 +1,52 @@
+#ifndef SG_CMDS_MMC_H
+#define SG_CMDS_MMC_H
+
+/*
+ * Copyright (c) 2008-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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Invokes a SCSI GET CONFIGURATION command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_get_config(int sg_fd, int rt, int starting, void * resp,
+                     int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba,
+                          int max_num_desc, int type, void * resp,
+                          int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI SET CD SPEED command (MMC).
+ * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed,
+                       int drv_write_speed, bool noisy, int verbose);
+
+/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len,
+                        bool noisy, int verbose);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_io_linux.h b/tools/sg_write_buffer/include/sg_io_linux.h
new file mode 100644
index 0000000..bd44bd6
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_io_linux.h
@@ -0,0 +1,185 @@
+#ifndef SG_IO_LINUX_H
+#define SG_IO_LINUX_H
+
+/*
+ * Copyright (c) 2004-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.
+ */
+
+/*
+ * Version 1.05 [20171009]
+ */
+
+/*
+ * This header file contains linux specific information related to the SCSI
+ * command pass through in the SCSI generic (sg) driver and the linux
+ * block layer.
+ */
+
+#include "sg_lib.h"
+#include "sg_linux_inc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The following are 'host_status' codes */
+#ifndef DID_OK
+#define DID_OK 0x00
+#endif
+#ifndef DID_NO_CONNECT
+#define DID_NO_CONNECT 0x01     /* Unable to connect before timeout */
+#define DID_BUS_BUSY 0x02       /* Bus remain busy until timeout */
+#define DID_TIME_OUT 0x03       /* Timed out for some other reason */
+#define DID_BAD_TARGET 0x04     /* Bad target (id?) */
+#define DID_ABORT 0x05          /* Told to abort for some other reason */
+#define DID_PARITY 0x06         /* Parity error (on SCSI bus) */
+#define DID_ERROR 0x07          /* Internal error */
+#define DID_RESET 0x08          /* Reset by somebody */
+#define DID_BAD_INTR 0x09       /* Received an unexpected interrupt */
+#define DID_PASSTHROUGH 0x0a    /* Force command past mid-level */
+#define DID_SOFT_ERROR 0x0b     /* The low-level driver wants a retry */
+#endif
+#ifndef DID_IMM_RETRY
+#define DID_IMM_RETRY 0x0c      /* Retry without decrementing retry count  */
+#endif
+#ifndef DID_REQUEUE
+#define DID_REQUEUE 0x0d        /* Requeue command (no immediate retry) also
+                                 * without decrementing the retry count    */
+#endif
+#ifndef DID_TRANSPORT_DISRUPTED
+#define DID_TRANSPORT_DISRUPTED 0xe
+#endif
+#ifndef DID_TRANSPORT_FAILFAST
+#define DID_TRANSPORT_FAILFAST 0xf
+#endif
+#ifndef DID_TARGET_FAILURE
+#define DID_TARGET_FAILURE 0x10
+#endif
+#ifndef DID_NEXUS_FAILURE
+#define DID_NEXUS_FAILURE 0x11
+#endif
+
+/* These defines are to isolate applications from kernel define changes */
+#define SG_LIB_DID_OK           DID_OK
+#define SG_LIB_DID_NO_CONNECT   DID_NO_CONNECT
+#define SG_LIB_DID_BUS_BUSY     DID_BUS_BUSY
+#define SG_LIB_DID_TIME_OUT     DID_TIME_OUT
+#define SG_LIB_DID_BAD_TARGET   DID_BAD_TARGET
+#define SG_LIB_DID_ABORT        DID_ABORT
+#define SG_LIB_DID_PARITY       DID_PARITY
+#define SG_LIB_DID_ERROR        DID_ERROR
+#define SG_LIB_DID_RESET        DID_RESET
+#define SG_LIB_DID_BAD_INTR     DID_BAD_INTR
+#define SG_LIB_DID_PASSTHROUGH  DID_PASSTHROUGH
+#define SG_LIB_DID_SOFT_ERROR   DID_SOFT_ERROR
+#define SG_LIB_DID_IMM_RETRY    DID_IMM_RETRY
+#define SG_LIB_DID_REQUEUE      DID_REQUEUE
+#define SG_LIB_TRANSPORT_DISRUPTED      DID_TRANSPORT_DISRUPTED
+#define SG_LIB_DID_TRANSPORT_FAILFAST   DID_TRANSPORT_FAILFAST
+#define SG_LIB_DID_TARGET_FAILURE       DID_TARGET_FAILURE
+#define SG_LIB_DID_NEXUS_FAILURE        DID_NEXUS_FAILURE
+
+/* The following are 'driver_status' codes */
+#ifndef DRIVER_OK
+#define DRIVER_OK 0x00
+#endif
+#ifndef DRIVER_BUSY
+#define DRIVER_BUSY 0x01
+#define DRIVER_SOFT 0x02
+#define DRIVER_MEDIA 0x03
+#define DRIVER_ERROR 0x04
+#define DRIVER_INVALID 0x05
+#define DRIVER_TIMEOUT 0x06
+#define DRIVER_HARD 0x07
+#define DRIVER_SENSE 0x08       /* Sense_buffer has been set */
+
+/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept
+ * to stop compilation breakages.
+ * Following "suggests" are "or-ed" with one of previous 8 entries */
+#define SUGGEST_RETRY 0x10
+#define SUGGEST_ABORT 0x20
+#define SUGGEST_REMAP 0x30
+#define SUGGEST_DIE 0x40
+#define SUGGEST_SENSE 0x80
+#define SUGGEST_IS_OK 0xff
+#endif
+
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0
+#endif
+
+/* These defines are to isolate applications from kernel define changes */
+#define SG_LIB_DRIVER_OK        DRIVER_OK
+#define SG_LIB_DRIVER_BUSY      DRIVER_BUSY
+#define SG_LIB_DRIVER_SOFT      DRIVER_SOFT
+#define SG_LIB_DRIVER_MEDIA     DRIVER_MEDIA
+#define SG_LIB_DRIVER_ERROR     DRIVER_ERROR
+#define SG_LIB_DRIVER_INVALID   DRIVER_INVALID
+#define SG_LIB_DRIVER_TIMEOUT   DRIVER_TIMEOUT
+#define SG_LIB_DRIVER_HARD      DRIVER_HARD
+#define SG_LIB_DRIVER_SENSE     DRIVER_SENSE
+
+
+/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept
+ * to stop compilation breakages. */
+#define SG_LIB_SUGGEST_RETRY    SUGGEST_RETRY
+#define SG_LIB_SUGGEST_ABORT    SUGGEST_ABORT
+#define SG_LIB_SUGGEST_REMAP    SUGGEST_REMAP
+#define SG_LIB_SUGGEST_DIE      SUGGEST_DIE
+#define SG_LIB_SUGGEST_SENSE    SUGGEST_SENSE
+#define SG_LIB_SUGGEST_IS_OK    SUGGEST_IS_OK
+#define SG_LIB_DRIVER_MASK      DRIVER_MASK
+#define SG_LIB_SUGGEST_MASK     SUGGEST_MASK
+
+void sg_print_masked_status(int masked_status);
+void sg_print_host_status(int host_status);
+void sg_print_driver_status(int driver_status);
+
+/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings
+   else it prints errors/warnings (prefixed by 'leadin') to
+   'sg_warnings_fd' and returns 0. raw_sinfo indicates whether the
+   raw sense buffer (in ASCII hex) should be printed. */
+int sg_chk_n_print(const char * leadin, int masked_status, int host_status,
+                   int driver_status, const unsigned char * sense_buffer,
+                   int sb_len, bool raw_sinfo);
+
+/* The following function declaration is for the sg version 3 driver. */
+struct sg_io_hdr;
+/* sg_chk_n_print3() returns 1 quietly if there are no errors/warnings;
+   else it prints errors/warnings (prefixed by 'leadin') to
+   'sg_warnings_fd' and returns 0. */
+int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp,
+                    bool raw_sinfo);
+
+/* Calls sg_scsi_normalize_sense() after obtaining the sense buffer and
+   its length from the struct sg_io_hdr pointer. If these cannot be
+   obtained, false is returned. */
+bool sg_normalize_sense(const struct sg_io_hdr * hp,
+                        struct sg_scsi_sense_hdr * sshp);
+
+int sg_err_category(int masked_status, int host_status, int driver_status,
+                    const unsigned char * sense_buffer, int sb_len);
+
+int sg_err_category_new(int scsi_status, int host_status, int driver_status,
+                        const unsigned char * sense_buffer, int sb_len);
+
+/* The following function declaration is for the sg version 3 driver. */
+int sg_err_category3(struct sg_io_hdr * hp);
+
+
+/* Note about SCSI status codes found in older versions of Linux.
+   Linux has traditionally used a 1 bit right shifted and masked
+   version of SCSI standard status codes. Now CHECK_CONDITION
+   and friends (in <scsi/scsi.h>) are deprecated. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_lib.h b/tools/sg_write_buffer/include/sg_lib.h
new file mode 100644
index 0000000..bc93d49
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_lib.h
@@ -0,0 +1,602 @@
+#ifndef SG_LIB_H
+#define SG_LIB_H
+
+/*
+ * Copyright (c) 2004-2018 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.
+ */
+
+/*
+ *
+ * On 5th October 2004 a FreeBSD license was added to this file.
+ * The intention is to keep this file and the related sg_lib.c file
+ * as open source and encourage their unencumbered use.
+ *
+ * Current version number is in the sg_lib.c file and can be accessed
+ * with the sg_lib_version() function.
+ */
+
+
+/*
+ * This header file contains defines and function declarations that may
+ * be useful to applications that communicate with devices that use a
+ * SCSI command set. These command sets have names like SPC-4, SBC-3,
+ * SSC-3, SES-2 and draft standards defining them can be found at
+ * http://www.t10.org . Virtually all devices in the Linux SCSI subsystem
+ * utilize SCSI command sets. Many devices in other Linux device subsystems
+ * utilize SCSI command sets either natively or via emulation (e.g. a
+ * parallel ATA disk in a USB enclosure).
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* SCSI Peripheral Device Types (PDT) [5 bit field] */
+#define PDT_DISK 0x0    /* direct access block device (disk) */
+#define PDT_TAPE 0x1    /* sequential access device (magnetic tape) */
+#define PDT_PRINTER 0x2 /* printer device (see SSC-1) */
+#define PDT_PROCESSOR 0x3       /* processor device (e.g. SAFTE device) */
+#define PDT_WO 0x4      /* write once device (some optical disks) */
+#define PDT_MMC 0x5     /* CD/DVD/BD (multi-media) */
+#define PDT_SCANNER 0x6 /* obsolete */
+#define PDT_OPTICAL 0x7 /* optical memory device (some optical disks) */
+#define PDT_MCHANGER 0x8        /* media changer device (e.g. tape robot) */
+#define PDT_COMMS 0x9   /* communications device (obsolete) */
+#define PDT_SAC 0xc     /* storage array controller device */
+#define PDT_SES 0xd     /* SCSI Enclosure Services (SES) device */
+#define PDT_RBC 0xe     /* Reduced Block Commands (simplified PDT_DISK) */
+#define PDT_OCRW 0xf    /* optical card read/write device */
+#define PDT_BCC 0x10    /* bridge controller commands */
+#define PDT_OSD 0x11    /* Object Storage Device (OSD) */
+#define PDT_ADC 0x12    /* Automation/drive commands (ADC) */
+#define PDT_SMD 0x13    /* Security Manager Device (SMD) */
+#define PDT_ZBC 0x14    /* Zoned Block Commands (ZBC) */
+#define PDT_WLUN 0x1e   /* Well known logical unit (WLUN) */
+#define PDT_UNKNOWN 0x1f        /* Unknown or no device type */
+
+#ifndef SAM_STAT_GOOD
+/* The SCSI status codes as found in SAM-4 at www.t10.org */
+#define SAM_STAT_GOOD 0x0
+#define SAM_STAT_CHECK_CONDITION 0x2
+#define SAM_STAT_CONDITION_MET 0x4
+#define SAM_STAT_BUSY 0x8
+#define SAM_STAT_INTERMEDIATE 0x10              /* obsolete in SAM-4 */
+#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14  /* obsolete in SAM-4 */
+#define SAM_STAT_RESERVATION_CONFLICT 0x18
+#define SAM_STAT_COMMAND_TERMINATED 0x22        /* obsolete in SAM-3 */
+#define SAM_STAT_TASK_SET_FULL 0x28
+#define SAM_STAT_ACA_ACTIVE 0x30
+#define SAM_STAT_TASK_ABORTED 0x40
+#endif
+
+/* The SCSI sense key codes as found in SPC-4 at www.t10.org */
+#define SPC_SK_NO_SENSE 0x0
+#define SPC_SK_RECOVERED_ERROR 0x1
+#define SPC_SK_NOT_READY 0x2
+#define SPC_SK_MEDIUM_ERROR 0x3
+#define SPC_SK_HARDWARE_ERROR 0x4
+#define SPC_SK_ILLEGAL_REQUEST 0x5
+#define SPC_SK_UNIT_ATTENTION 0x6
+#define SPC_SK_DATA_PROTECT 0x7
+#define SPC_SK_BLANK_CHECK 0x8
+#define SPC_SK_VENDOR_SPECIFIC 0x9
+#define SPC_SK_COPY_ABORTED 0xa
+#define SPC_SK_ABORTED_COMMAND 0xb
+#define SPC_SK_RESERVED 0xc
+#define SPC_SK_VOLUME_OVERFLOW 0xd
+#define SPC_SK_MISCOMPARE 0xe
+#define SPC_SK_COMPLETED 0xf
+
+/* Transport protocol identifiers or just Protocol identifiers */
+#define TPROTO_FCP 0
+#define TPROTO_SPI 1
+#define TPROTO_SSA 2
+#define TPROTO_1394 3
+#define TPROTO_SRP 4            /* SCSI over RDMA */
+#define TPROTO_ISCSI 5
+#define TPROTO_SAS 6
+#define TPROTO_ADT 7
+#define TPROTO_ATA 8
+#define TPROTO_UAS 9            /* USB attached SCSI */
+#define TPROTO_SOP 0xa          /* SCSI over PCIe */
+#define TPROTO_PCIE 0xb         /* includes NVMe */
+#define TPROTO_NONE 0xf
+
+/* SCSI Feature Sets (sfs) */
+#define SCSI_FS_SPC_DISCOVERY_2016 0x1
+#define SCSI_FS_SBC_BASE_2010 0x102
+#define SCSI_FS_SBC_BASE_2016 0x101
+#define SCSI_FS_SBC_BASIC_PROV_2016 0x103
+#define SCSI_FS_SBC_DRIVE_MAINT_2016 0x104
+
+/* Often SCSI responses use the highest integer that can fit in a field
+ * to indicate "unbounded" or limit does not apply. Sometimes represented
+ * in output as "-1" for brevity */
+#define SG_LIB_UNBOUNDED_16BIT 0xffff
+#define SG_LIB_UNBOUNDED_32BIT 0xffffffffU
+#define SG_LIB_UNBOUNDED_64BIT 0xffffffffffffffffULL
+
+#if (__STDC_VERSION__ >= 199901L)  /* C99 or later */
+    typedef uintptr_t sg_uintptr_t;
+#else
+    typedef unsigned long sg_uintptr_t;
+#endif
+
+
+/* The format of the version string is like this: "2.26 20170906" */
+const char * sg_lib_version();
+
+/* Returns length of SCSI command given the opcode (first byte).
+ * Yields the wrong answer for variable length commands (opcode=0x7f)
+ * and potentially some vendor specific commands. */
+int sg_get_command_size(unsigned char cdb_byte0);
+
+/* Command name given pointer to the cdb. Certain command names
+ * depend on peripheral type (give 0 or -1 if unknown). Places command
+ * name into buff and will write no more than buff_len bytes. */
+void sg_get_command_name(const unsigned char * cdbp, int peri_type,
+                         int buff_len, char * buff);
+
+/* Command name given only the first byte (byte 0) of a cdb and
+ * peripheral type (give 0 or -1 if unknown). */
+void sg_get_opcode_name(unsigned char cdb_byte0, int peri_type, int buff_len,
+                        char * buff);
+
+/* Command name given opcode (byte 0), service action and peripheral type.
+ * If no service action give 0, if unknown peripheral type give 0 or -1 . */
+void sg_get_opcode_sa_name(unsigned char cdb_byte0, int service_action,
+                           int peri_type, int buff_len, char * buff);
+
+/* Fetch scsi status string. */
+void sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff);
+
+/* This is a slightly stretched SCSI sense "descriptor" format header.
+ * The addition is to allow the 0x70 and 0x71 response codes. The idea
+ * is to place the salient data of both "fixed" and "descriptor" sense
+ * format into one structure to ease application processing.
+ * The original sense buffer should be kept around for those cases
+ * in which more information is required (e.g. the LBA of a MEDIUM ERROR). */
+struct sg_scsi_sense_hdr {
+    unsigned char response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */
+    unsigned char sense_key;
+    unsigned char asc;
+    unsigned char ascq;
+    unsigned char byte4;
+    unsigned char byte5;
+    unsigned char byte6;
+    unsigned char additional_length;
+};
+
+/* Maps the salient data from a sense buffer which is in either fixed or
+ * descriptor format into a structure mimicking a descriptor format
+ * header (i.e. the first 8 bytes of sense descriptor format).
+ * If zero response code returns false. Otherwise returns true and if 'sshp'
+ * is non-NULL then zero all fields and then set the appropriate fields in
+ * that structure. sshp::additional_length is always 0 for response
+ * codes 0x70 and 0x71 (fixed format). */
+bool sg_scsi_normalize_sense(const unsigned char * sensep, int sense_len,
+                             struct sg_scsi_sense_hdr * sshp);
+
+/* Attempt to find the first SCSI sense data descriptor that matches the
+ * given 'desc_type'. If found return pointer to start of sense data
+ * descriptor; otherwise (including fixed format sense data) returns NULL. */
+const unsigned char * sg_scsi_sense_desc_find(const unsigned char * sensep,
+                                              int sense_len, int desc_type);
+
+/* Get sense key from sense buffer. If successful returns a sense key value
+ * between 0 and 15. If sense buffer cannot be decode, returns -1 . */
+int sg_get_sense_key(const unsigned char * sensep, int sense_len);
+
+/* Yield string associated with sense_key value. Returns 'buff'. */
+char * sg_get_sense_key_str(int sense_key, int buff_len, char * buff);
+
+/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
+char * sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff);
+
+/* Returns true if valid bit set, false if valid bit clear. Irrespective the
+ * information field is written out via 'info_outp' (except when it is
+ * NULL). Handles both fixed and descriptor sense formats. */
+bool sg_get_sense_info_fld(const unsigned char * sensep, int sb_len,
+                           uint64_t * info_outp);
+
+/* Returns true if fixed format or command specific information descriptor
+ * is found in the descriptor sense; else false. If available the command
+ * specific information field (4 byte integer in fixed format, 8 byte
+ * integer in descriptor format) is written out via 'cmd_spec_outp'.
+ * Handles both fixed and descriptor sense formats. */
+bool sg_get_sense_cmd_spec_fld(const unsigned char * sensep, int sb_len,
+                               uint64_t * cmd_spec_outp);
+
+/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set.
+ * In descriptor format if the stream commands descriptor not found
+ * then returns false. Writes true or false corresponding to these bits to
+ * the last three arguments if they are non-NULL. */
+bool sg_get_sense_filemark_eom_ili(const unsigned char * sensep, int sb_len,
+                                   bool * filemark_p, bool * eom_p,
+                                   bool * ili_p);
+
+/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also
+ * returns true if progress indication sense data descriptor found. Places
+ * progress field from sense data where progress_outp points. If progress
+ * field is not available returns false. Handles both fixed and descriptor
+ * sense formats. N.B. App should multiply by 100 and divide by 65536
+ * to get percentage completion from given value. */
+bool sg_get_sense_progress_fld(const unsigned char * sensep, int sb_len,
+                               int * progress_outp);
+
+/* Closely related to sg_print_sense(). Puts decoded sense data in 'buff'.
+ * Usually multiline with multiple '\n' including one trailing. If
+ * 'raw_sinfo' set appends sense buffer in hex. 'leadin' is string prepended
+ * to each line written to 'buff', NULL treated as "". Returns the number of
+ * bytes written to 'buff' excluding the trailing '\0'.
+ * N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the first
+ * line output. Also this function returned type void. */
+int sg_get_sense_str(const char * leadin, const unsigned char * sense_buffer,
+                     int sb_len, bool raw_sinfo, int buff_len, char * buff);
+
+/* Decode descriptor format sense descriptors (assumes sense buffer is
+ * in descriptor format). 'leadin' is string prepended to each line written
+ * to 'b', NULL treated as "". Returns the number of bytes written to 'b'
+ * excluding the trailing '\0'. */
+int sg_get_sense_descriptors_str(const char * leadin,
+                                 const unsigned char * sense_buffer,
+                                 int sb_len, int blen, char * b);
+
+/* Decodes a designation descriptor (e.g. as found in the Device
+ * Identification VPD page (0x83)) into string 'b' whose maximum length is
+ * blen. 'leadin' is string prepended to each line written to 'b', NULL
+ * treated as "". Returns the number of bytes written to 'b' excluding the
+ * trailing '\0'. */
+int sg_get_designation_descriptor_str(const char * leadin,
+                                      const unsigned char * ddp, int dd_len,
+                                      bool print_assoc, bool do_long,
+                                      int blen, char * b);
+
+/* Yield string associated with peripheral device type (pdt). Returns
+ * 'buff'. If 'pdt' out of range yields "bad pdt" string. */
+char * sg_get_pdt_str(int pdt, int buff_len, char * buff);
+
+/* Some lesser used PDTs share a lot in common with a more used PDT.
+ * Examples are PDT_ADC decaying to PDT_TAPE and PDT_ZBC to PDT_DISK.
+ * If such a lesser used 'pdt' is given to this function, then it will
+ * return the more used PDT (i.e. "decays to"); otherwise 'pdt' is returned.
+ * Valid for 'pdt' 0 to 31, for other values returns 0. */
+int sg_lib_pdt_decay(int pdt);
+
+/* Yield string associated with transport protocol identifier (tpi). Returns
+ * 'buff'. If 'tpi' out of range yields "bad tpi" string. */
+char * sg_get_trans_proto_str(int tpi, int buff_len, char * buff);
+
+/* Decode TransportID pointed to by 'bp' of length 'bplen'. Place decoded
+ * string output in 'buff' which is also the return value. Each new line
+ * is prefixed by 'leadin'. If leadin NULL treat as "". */
+char * sg_decode_transportid_str(const char * leadin, unsigned char * bp,
+                                 int bplen, bool only_one, int buff_len,
+                                 char * buff);
+
+/* Returns a designator's type string given 'val' (0 to 15 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_type_str(int val);
+
+/* Returns a designator's code_set string given 'val' (0 to 15 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_code_set_str(int val);
+
+/* Returns a designator's association string given 'val' (0 to 3 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_assoc_str(int val);
+
+/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31)
+ * returns pointer to string (same as 'buff') associated with 'sfs_code'.
+ * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match
+ * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL
+ * then where it points is set to true if a match is found else it is set to
+ * false. If 'buff' is not NULL then in the case of a match a descriptive
+ * string is written to 'buff' while if there is not a not then a string
+ * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC
+ * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL.
+ * Example:
+ *    char b[64];
+ *    ...
+ *    printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0));
+ */
+const char * sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len,
+                            char * buff, bool * foundp, int verbose);
+
+/* This is a heuristic that takes into account the command bytes and length
+ * to decide whether the presented unstructured sequence of bytes could be
+ * a SCSI command. If so it returns true otherwise false. Vendor specific
+ * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed
+ * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The
+ * only SCSI commands considered above 16 bytes of length 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.
+ * No NVMe commands (64 bytes long plus some extra added by some OSes) have
+ * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS
+ * structures that are sent across the wire. The 'FIS register' structure is
+ * used to move a command from a SATA host to device, but the ATA 'command'
+ * is not the first byte. So it is harder to say what will happen if a
+ * FIS structure is presented as a SCSI command, hopfully there is a low
+ * probability this function will yield true in that case. */
+bool sg_is_scsi_cdb(const uint8_t * cdbp, int clen);
+
+/* Yield string associated with NVMe command status value in sct_sc. It
+ * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25
+ * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC).
+ * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found
+ * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated.
+ * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/
+char * sg_get_nvme_cmd_status_str(uint16_t sct_sc, int buff_len, char * buff);
+
+/* Attempts to map NVMe status value ((SCT << 8) | SC) n sct_sc to a SCSI
+ * status, sense_key, asc and ascq tuple. If successful returns true and
+ * writes to non-NULL pointer arguments; otherwise returns false. */
+bool sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p,
+                         uint8_t * asc_p, uint8_t * ascq_p);
+
+extern FILE * sg_warnings_strm;
+
+void sg_set_warnings_strm(FILE * warnings_strm);
+
+/* The following "print" functions send ACSII to 'sg_warnings_strm' file
+ * descriptor (default value is stderr). 'leadin' is string prepended to
+ * each line printed out, NULL treated as "". */
+void sg_print_command(const unsigned char * command);
+void sg_print_scsi_status(int scsi_status);
+
+/* 'leadin' is string prepended to each line printed out, NULL treated as
+ * "". N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the
+ * first line printed. */
+void sg_print_sense(const char * leadin, const unsigned char * sense_buffer,
+                    int sb_len, bool raw_info);
+
+/* Following examines exit_status and outputs a clear error message to
+ * warnings_strm (usually stderr) if one is known and returns true.
+ * Otherwise it doesn't print anything and returns false. Note that if
+ * exit_status==0 then returns true but prints nothing and if
+ * exit_status<0 ("some error occurred") false is returned. If leadin is
+ * non-NULL is will be printed before error message. */
+bool sg_if_can2stderr(const char * leadin, int exit_status);
+
+/* Utilities can use these exit status values for syntax errors and
+ * file (device node) problems (e.g. not found or permissions). */
+#define SG_LIB_SYNTAX_ERROR 1   /* command line syntax problem */
+#define SG_LIB_FILE_ERROR 15    /* device or other file problem */
+
+/* The sg_err_category_sense() function returns one of the following.
+ * These may be used as exit status values (from a process). Notice that
+ * some of the lower values correspond to SCSI sense key values. */
+#define SG_LIB_CAT_CLEAN 0      /* No errors or other information */
+/* Value 1 left unused for utilities to use SG_LIB_SYNTAX_ERROR */
+#define SG_LIB_CAT_NOT_READY 2  /* sense key, unit stopped? */
+                                /*       [sk,asc,ascq: 0x2,*,*] */
+#define SG_LIB_CAT_MEDIUM_HARD 3 /* medium or hardware error, blank check */
+                                /*       [sk,asc,ascq: 0x3/0x4/0x8,*,*] */
+#define SG_LIB_CAT_ILLEGAL_REQ 5 /* Illegal request (other than invalid */
+                                /* opcode):   [sk,asc,ascq: 0x5,*,*] */
+#define SG_LIB_CAT_UNIT_ATTENTION 6 /* sense key, device state changed */
+                                /*       [sk,asc,ascq: 0x6,*,*] */
+        /* was SG_LIB_CAT_MEDIA_CHANGED earlier [sk,asc,ascq: 0x6,0x28,*] */
+#define SG_LIB_CAT_DATA_PROTECT 7 /* sense key, media write protected? */
+                                /*       [sk,asc,ascq: 0x7,*,*] */
+#define SG_LIB_CAT_INVALID_OP 9 /* (Illegal request,) Invalid opcode: */
+                                /*       [sk,asc,ascq: 0x5,0x20,0x0] */
+#define SG_LIB_CAT_COPY_ABORTED 10 /* sense key, some data transferred */
+                                /*       [sk,asc,ascq: 0xa,*,*] */
+#define SG_LIB_CAT_ABORTED_COMMAND 11 /* interpreted from sense buffer */
+                                /*       [sk,asc,ascq: 0xb,! 0x10,*] */
+#define SG_LIB_CAT_MISCOMPARE 14 /* sense key, probably verify */
+                                /*       [sk,asc,ascq: 0xe,*,*] */
+#define SG_LIB_CAT_NO_SENSE 20  /* sense data with key of "no sense" */
+                                /*       [sk,asc,ascq: 0x0,*,*] */
+#define SG_LIB_CAT_RECOVERED 21 /* Successful command after recovered err */
+                                /*       [sk,asc,ascq: 0x1,*,*] */
+#define SG_LIB_CAT_RES_CONFLICT SAM_STAT_RESERVATION_CONFLICT
+                                /* 24: this is a SCSI status, not sense. */
+                                /* It indicates reservation by another */
+                                /* machine blocks this command */
+#define SG_LIB_CAT_CONDITION_MET 25 /* SCSI status, not sense key. */
+                                    /* Only from PRE-FETCH (SBC-4) */
+#define SG_LIB_CAT_BUSY       26 /* SCSI status, not sense. Invites retry */
+#define SG_LIB_CAT_TS_FULL    27 /* SCSI status, not sense. Wait then retry */
+#define SG_LIB_CAT_ACA_ACTIVE 28 /* SCSI status; ACA seldom used */
+#define SG_LIB_CAT_TASK_ABORTED 29 /* SCSI status, this command aborted by? */
+#define SG_LIB_CAT_PROTECTION 40 /* subset of aborted command (for PI, DIF) */
+                                /*       [sk,asc,ascq: 0xb,0x10,*] */
+#define SG_LIB_NVME_STATUS 48   /* NVMe Status Field (SF) other than 0 */
+#define SG_LIB_WILD_RESID 49    /* Residual value for data-in transfer of a */
+                                /* SCSI command is nonsensical */
+#define SG_LIB_OS_BASE_ERR 50   /* in Linux: values found in: */
+                                /* include/uapi/asm-generic/errno-base.h */
+                                /* Example: ENOMEM reported as 62 (=50+12) */
+#define SG_LIB_CAT_MALFORMED 97 /* Response to SCSI command malformed */
+#define SG_LIB_CAT_SENSE 98     /* Something else is in the sense buffer */
+#define SG_LIB_CAT_OTHER 99     /* Some other error/warning has occurred */
+                                /* (e.g. a transport or driver error) */
+
+/* Returns a SG_LIB_CAT_* value. If cannot decode sense_buffer or a less
+ * common sense key then return SG_LIB_CAT_SENSE .*/
+int sg_err_category_sense(const unsigned char * sense_buffer, int sb_len);
+
+/* Here are some additional sense data categories that are not returned
+ * by sg_err_category_sense() but are returned by some related functions. */
+#define SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO 17 /* Illegal request (other than */
+                                /* invalid opcode) plus 'info' field: */
+                                /*  [sk,asc,ascq: 0x5,*,*] */
+#define SG_LIB_CAT_MEDIUM_HARD_WITH_INFO 18 /* medium or hardware error */
+                                /* sense key plus 'info' field: */
+                                /*       [sk,asc,ascq: 0x3/0x4,*,*] */
+#define SG_LIB_CAT_PROTECTION_WITH_INFO 41 /* aborted command sense key, */
+                                /* protection plus 'info' field: */
+                                /*  [sk,asc,ascq: 0xb,0x10,*] */
+#define SG_LIB_CAT_TIMEOUT 33
+
+/* Yield string associated with sense category. Returns 'buff' (or pointer
+ * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then
+ * yield "Sense category: <sense_cat>" string. */
+const char * sg_get_category_sense_str(int sense_cat, int buff_len,
+                                       char * buff, int verbose);
+
+
+/* Iterates to next designation descriptor in the device identification
+ * VPD page. The 'initial_desig_desc' should point to start of first
+ * descriptor with 'page_len' being the number of valid bytes in that
+ * and following descriptors. To start, 'off' should point to a negative
+ * value, thereafter it should point to the value yielded by the previous
+ * call. If 0 returned then 'initial_desig_desc + *off' should be a valid
+ * descriptor; returns -1 if normal end condition and -2 for an abnormal
+ * termination. Matches association, designator_type and/or code_set when
+ * any of those values are greater than or equal to zero. */
+int sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len,
+                       int * off, int m_assoc, int m_desig_type,
+                       int m_code_set);
+
+
+/* <<< General purpose (i.e. not SCSI specific) utility functions >>> */
+
+/* Always returns valid string even if errnum is wild (or library problem).
+ * If errnum is negative, flip its sign. */
+char * safe_strerror(int errnum);
+
+
+/* Print (to stdout) 'str' of bytes in hex, 16 bytes per line optionally
+ * followed at the right hand side of the line with an ASCII interpretation.
+ * Each line is prefixed with an address, starting at 0 for str[0]..str[15].
+ * All output numbers are in hex. 'no_ascii' allows for 3 output types:
+ *     > 0     each line has address then up to 16 ASCII-hex bytes
+ *     = 0     in addition, the bytes are listed in ASCII to the right
+ *     < 0     only the ASCII-hex bytes are listed (i.e. without address)
+*/
+void dStrHex(const char * str, int len, int no_ascii);
+
+/* Print (to sg_warnings_strm (stderr)) 'str' of bytes in hex, 16 bytes per
+ * line optionally followed at right by its ASCII interpretation. Same
+ * logic as dStrHex() with different output stream (i.e. stderr). */
+void dStrHexErr(const char * str, int len, int no_ascii);
+
+/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space
+ * separated) to 'b' not to exceed 'b_len' characters. Each line
+ * starts with 'leadin' (NULL for no leadin) and there are 16 bytes
+ * per line with an extra space between the 8th and 9th bytes. 'format'
+ * is 0 for repeat in printable ASCII ('.' for non printable chars) to
+ * right of each line; 1 don't (so just output ASCII hex). Returns
+ * number of bytes written to 'b' excluding the trailing '\0'. */
+int dStrHexStr(const char * str, int len, const char * leadin, int format,
+               int cb_len, char * cbp);
+
+/* The following 3 functions are equivalent to dStrHex(), dStrHexErr() and
+ * dStrHexStr() respectively. The difference is the type of the first of
+ * argument: uint8_t instead of char. The name of the argument is changed
+ * to b_str to stress it is a pointer to the start of a binary string. */
+void hex2stdout(const uint8_t * b_str, int len, int no_ascii);
+void hex2stderr(const uint8_t * b_str, int len, int no_ascii);
+int hex2str(const uint8_t * b_str, int len, const char * leadin, int format,
+            int cb_len, char * cbp);
+
+/* Returns true when executed on big endian machine; else returns false.
+ * Useful for displaying ATA identify words (which need swapping on a
+ * big endian machine). */
+bool sg_is_big_endian();
+
+/* Returns true if byte sequence starting at bp with a length of b_len is
+ * all zeros (for sg_all_zeros()) or all 0xff_s (for sg_all_ffs());
+ * otherwise returns false. If bp is NULL ir b_len <= 0 returns false. */
+bool sg_all_zeros(const uint8_t * bp, int b_len);
+bool sg_all_ffs(const uint8_t * bp, int b_len);
+
+/* Extract character sequence from ATA words as in the model string
+ * in a IDENTIFY DEVICE response. Returns number of characters
+ * written to 'ochars' before 0 character is found or 'num' words
+ * are processed. */
+int sg_ata_get_chars(const uint16_t * word_arr, int start_word,
+                     int num_words, bool is_big_endian, char * ochars);
+
+/* Print (to stdout) 16 bit 'words' in hex, 8 words per line optionally
+ * followed at the right hand side of the line with an ASCII interpretation
+ * (pairs of ASCII characters in big endian order (upper first)).
+ * Each line is prefixed with an address, starting at 0.
+ * All output numbers are in hex. 'no_ascii' allows for 3 output types:
+ *     > 0     each line has address then up to 8 ASCII-hex words
+ *     = 0     in addition, the words are listed in ASCII pairs to the right
+ *     = -1    only the ASCII-hex words are listed (i.e. without address)
+ *     = -2    only the ASCII-hex words, formatted for "hdparm --Istdin"
+ *     < -2    same as -1
+ * If 'swapb' is true then bytes in each word swapped. Needs to be set
+ * for ATA IDENTIFY DEVICE response on big-endian machines.
+*/
+void dWordHex(const uint16_t * words, int num, int no_ascii, bool swapb);
+
+/* If the number in 'buf' can not be decoded or the multiplier is unknown
+ * then -1 is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H')
+ * suffix. Otherwise a decimal multiplier suffix may be given. Recognised
+ * multipliers: c C  *1;  w W  *2; b  B *512;  k K KiB  *1,024;
+ * KB  *1,000;  m M MiB  *1,048,576; MB *1,000,000; g G GiB *1,073,741,824;
+ * GB *1,000,000,000 and <n>x<m> which multiplies <n> by <m> . Ignore leading
+ * spaces and tabs; accept comma, hyphen, space, tab and hash as terminator.
+ */
+int sg_get_num(const char * buf);
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * negative numbers and '-1' must be treated separately. */
+int sg_get_num_nomult(const char * buf);
+
+/* If the number in 'buf' can not be decoded or the multiplier is unknown
+ * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H')
+ * suffix. Otherwise a decimal multiplier suffix may be given. In addition
+ * to supporting the multipliers of sg_get_num(), this function supports:
+ * t T TiB  *(2**40); TB *(10**12); p P PiB  *(2**50); PB  *(10**15) .
+ * Ignore leading spaces and tabs; accept comma, hyphen, space, tab and hash
+ * as terminator. */
+int64_t sg_get_llnum(const char * buf);
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * 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);
+
+/* If os_err_num is within bounds then the returned value is 'os_err_num +
+ * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0
+ * is returned. */
+int sg_convert_errno(int os_err_num);
+
+
+/* <<< Architectural support functions [is there a better place?] >>> */
+
+/* Non Unix OSes distinguish between text and binary files.
+ * Set text mode on fd. Does nothing in Unix. Returns negative number on
+ * failure. */
+int sg_set_text_mode(int fd);
+
+/* Set binary mode on fd. Does nothing in Unix. Returns negative number on
+ * failure. */
+int sg_set_binary_mode(int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif          /* SG_LIB_H */
diff --git a/tools/sg_write_buffer/include/sg_lib_data.h b/tools/sg_write_buffer/include/sg_lib_data.h
new file mode 100644
index 0000000..09cd53c
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_lib_data.h
@@ -0,0 +1,121 @@
+#ifndef SG_LIB_DATA_H
+#define SG_LIB_DATA_H
+
+/*
+ * Copyright (c) 2007-2018 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.
+ */
+
+/*
+ * This header file contains some structure declarations and array name
+ * declarations which are defined in the sg_lib_data.c .
+ * Typically this header does not need to be exposed to users of the
+ * sg_lib interface declared in sg_libs.h .
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Operation codes with associated service actions that change or qualify
+ * the command name */
+#define SG_EXTENDED_COPY 0x83 /* since spc4r34 became next entry */
+#define SG_3PARTY_COPY_OUT 0x83 /* new in spc4r34: Third party copy out */
+#define SG_RECEIVE_COPY 0x84  /* since spc4r34 became next entry */
+#define SG_3PARTY_COPY_IN 0x84 /* new in spc4r34: Third party copy in */
+#define SG_MAINTENANCE_IN 0xa3
+#define SG_MAINTENANCE_OUT 0xa4
+#define SG_PERSISTENT_RESERVE_IN 0x5e
+#define SG_PERSISTENT_RESERVE_OUT 0x5f
+#define SG_READ_ATTRIBUTE 0x8c
+#define SG_READ_BUFFER 0x3c     /* now READ BUFFER(10) */
+#define SG_READ_BUFFER_16 0x9b
+#define SG_READ_POSITION 0x34   /* SSC command with service actions */
+#define SG_SANITIZE 0x48
+#define SG_SERVICE_ACTION_BIDI 0x9d
+#define SG_SERVICE_ACTION_IN_12 0xab
+#define SG_SERVICE_ACTION_IN_16 0x9e
+#define SG_SERVICE_ACTION_OUT_12 0xa9
+#define SG_SERVICE_ACTION_OUT_16 0x9f
+#define SG_VARIABLE_LENGTH_CMD 0x7f
+#define SG_WRITE_BUFFER 0x3b
+#define SG_ZONING_OUT 0x94
+#define SG_ZONING_IN 0x95
+
+
+
+struct sg_lib_simple_value_name_t {
+    int value;
+    const char * name;
+};
+
+struct sg_lib_value_name_t {
+    int value;
+    int peri_dev_type; /* 0 -> SPC and/or PDT_DISK, >0 -> PDT */
+    const char * name;
+};
+
+struct sg_lib_asc_ascq_t {
+    unsigned char asc;          /* additional sense code */
+    unsigned char ascq;         /* additional sense code qualifier */
+    const char * text;
+};
+
+struct sg_lib_asc_ascq_range_t {
+    unsigned char asc;  /* additional sense code (ASC) */
+    unsigned char ascq_min;     /* ASCQ minimum in range */
+    unsigned char ascq_max;     /* ASCQ maximum in range */
+    const char * text;
+};
+
+/* First use: SCSI status, sense_key, asc, ascq tuple */
+struct sg_lib_4tuple_u8 {
+    uint8_t t1;
+    uint8_t t2;
+    uint8_t t3;
+    uint8_t t4;
+};
+
+
+extern const char * sg_lib_version_str;
+
+extern struct sg_lib_value_name_t sg_lib_normal_opcodes[];
+extern struct sg_lib_value_name_t sg_lib_read_buff_arr[];
+extern struct sg_lib_value_name_t sg_lib_write_buff_arr[];
+extern struct sg_lib_value_name_t sg_lib_maint_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_maint_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_pr_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_pr_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_in12_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_out12_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_in16_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_out16_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_bidi_arr[];
+extern struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_variable_length_arr[];
+extern struct sg_lib_value_name_t sg_lib_zoning_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_zoning_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_read_attr_arr[];
+extern struct sg_lib_value_name_t sg_lib_read_pos_arr[];
+extern struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[];
+extern struct sg_lib_asc_ascq_t sg_lib_asc_ascq[];
+extern struct sg_lib_value_name_t sg_lib_scsi_feature_sets[];
+extern const char * sg_lib_sense_key_desc[];
+extern const char * sg_lib_pdt_strs[];
+extern const char * sg_lib_transport_proto_strs[];
+extern int sg_lib_pdt_decay_arr[];
+
+extern struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[];
+extern struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_linux_inc.h b/tools/sg_write_buffer/include/sg_linux_inc.h
new file mode 100644
index 0000000..b587c6c
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_linux_inc.h
@@ -0,0 +1,56 @@
+#ifndef SG_LINUX_INC_H
+#define SG_LINUX_INC_H
+
+#ifdef SG_KERNEL_INCLUDES
+  #define __user
+  typedef unsigned char u8;
+  #include "/usr/src/linux/include/scsi/sg.h"
+  #include "/usr/src/linux/include/scsi/scsi.h"
+#else
+  #ifdef SG_TRICK_GNU_INCLUDES
+    #include <linux/../scsi/sg.h>
+    #include <linux/../scsi/scsi.h>
+  #else
+    #include <scsi/sg.h>
+    #include <scsi/scsi.h>
+  #endif
+#endif
+
+#ifdef BLKGETSIZE64
+  #ifndef u64
+    #include <stdint.h>   /* C99 header for exact integer types */
+    typedef uint64_t u64; /* problems with BLKGETSIZE64 ioctl in lk 2.4 */
+  #endif
+#endif
+
+/*
+  Getting the correct include files for the sg interface can be an ordeal.
+  In a perfect world, one would just write:
+    #include <scsi/sg.h>
+    #include <scsi/scsi.h>
+  This would include the files found in the /usr/include/scsi directory.
+  Those files are maintained with the GNU library which may or may not
+  agree with the kernel and version of sg driver that is running. Any
+  many cases this will not matter. However in some it might, for example
+  glibc 2.1's include files match the sg driver found in the lk 2.2
+  series. Hence if glibc 2.1 is used with lk 2.4 then the additional
+  sg v3 interface will not be visible.
+  If this is a problem then defining SG_KERNEL_INCLUDES will access the
+  kernel supplied header files (assuming they are in the normal place).
+  The GNU library maintainers and various kernel people don't like
+  this approach (but it does work).
+  The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and
+  was used) prior to glibc 2.2 . Prior to that version /usr/include/linux
+  was a symbolic link to /usr/src/linux/include/linux .
+
+  There are other approaches if this include "mixup" causes pain. These
+  would involve include files being copied or symbolic links being
+  introduced.
+
+  Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES
+  nor SG_TRICK_GNU_INCLUDES is defined.
+
+  dpg 20010415, 20030522
+*/
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_pr2serr.h b/tools/sg_write_buffer/include/sg_pr2serr.h
new file mode 100644
index 0000000..4419087
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pr2serr.h
@@ -0,0 +1,30 @@
+#ifndef SG_PR2SERR_H
+#define SG_PR2SERR_H
+
+/*
+ * Copyright (c) 2004-2018 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 <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#if defined(__GNUC__) || defined(__clang__)
+int pr2serr(const char * fmt, ...)
+        __attribute__ ((format (printf, 1, 2)));
+#else
+int pr2serr(const char * fmt, ...);
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_pt.h b/tools/sg_write_buffer/include/sg_pt.h
new file mode 100644
index 0000000..b01215c
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pt.h
@@ -0,0 +1,215 @@
+#ifndef SG_PT_H
+#define SG_PT_H
+
+/*
+ * Copyright (c) 2005-2018 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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This declaration hides the fact that each implementation has its own
+ * structure "derived" (using a C++ term) from this one. It compiles
+ * because 'struct sg_pt_base' is only referenced (by pointer: 'objp')
+ * in this interface. An instance of this structure represents the
+ * context of one SCSI command. */
+struct sg_pt_base;
+
+
+/* The format of the version string is like this: "2.01 20090201".
+ * The leading digit will be incremented if this interface changes
+ * in a way that may impact backward compatibility. */
+const char * scsi_pt_version();
+
+
+/* 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);
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
+ * together. Returns valid file descriptor( >= 0 ) if successful, otherwise
+ * returns -1 or a negated errno.
+ * In Win32 O_EXCL translated to equivalent. */
+int scsi_pt_open_flags(const char * device_name, int flags, int verbose);
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int scsi_pt_close_device(int device_fd);
+
+/* Assumes dev_fd is an "open" file handle associated with device_name. If
+ * the implementation (possibly for one OS) cannot determine from dev_fd if
+ * a SCSI or NVMe pass-through is referenced, then it might guess based on
+ * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if
+ * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is
+ * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0.
+ * If error, returns negated errno (operating system) value. */
+int check_pt_file_handle(int dev_fd, const char * device_name, int verbose);
+
+
+/* Creates an object that can be used to issue one or more SCSI commands
+ * (or task management functions). Returns NULL if problem.
+ * Once this object has been created it should be destroyed with
+ * destruct_scsi_pt_obj() when it is no longer needed. */
+struct sg_pt_base * construct_scsi_pt_obj(void);
+
+/* An alternate way to create an object that can be used to issue one or
+ * more SCSI commands (or task management functions). This variant
+ * associate a device file descriptor (handle) with the object and a
+ * verbose argument that causes error messages if errors occur. The
+ * reason for this is to optionally allow the detection of NVMe devices
+ * that will cause pt_device_is_nvme() to return true. Set dev_fd to
+ * -1 if no open device file descriptor is available. 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);
+
+/* 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 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);
+
+/* 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 * objp);
+
+/* Clear state information held in *objp . This allows this object to be
+ * used to issue more than one SCSI command. The dev_fd is remembered.
+ * Use set_pt_file_handle() to change dev_fd. */
+void clear_scsi_pt_obj(struct sg_pt_base * objp);
+
+/* Set the CDB (command descriptor block) */
+void set_scsi_pt_cdb(struct sg_pt_base * objp, const unsigned char * cdb,
+                     int cdb_len);
+/* Set the sense buffer and the maximum length that it can handle */
+void set_scsi_pt_sense(struct sg_pt_base * objp, unsigned char * sense,
+                       int max_sense_len);
+/* Set a pointer and length to be used for data transferred from device */
+void set_scsi_pt_data_in(struct sg_pt_base * objp,   /* from device */
+                         unsigned char * dxferp, int dxfer_ilen);
+/* Set a pointer and length to be used for data transferred to device */
+void set_scsi_pt_data_out(struct sg_pt_base * objp,    /* to device */
+                          const unsigned char * dxferp, int dxfer_olen);
+/* 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);
+/* 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);
+void set_scsi_pt_task_management(struct sg_pt_base * objp, int tmf_code);
+void set_scsi_pt_task_attr(struct sg_pt_base * objp, int attribute,
+                           int priority);
+
+/* Following is a guard which is defined when set_scsi_pt_flags() is
+ * present. Older versions of this library may not have this function. */
+#define SCSI_PT_FLAGS_FUNCTION 1
+/* If neither QUEUE_AT_HEAD nor QUEUE_AT_TAIL are given, or both
+ * 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 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);
+
+#define SCSI_PT_DO_START_OK 0
+#define SCSI_PT_DO_BAD_PARAMS 1
+#define SCSI_PT_DO_TIMEOUT 2
+#define SCSI_PT_DO_NVME_STATUS 48       /* == SG_LIB_NVME_STATUS */
+/* If OS error prior to or during command submission then returns negated
+ * error value (e.g. Unix '-errno'). This includes interrupted system calls
+ * (e.g. by a signal) in which case -EINTR would be returned. Note that
+ * system call errors also can be fetched with get_scsi_pt_os_err().
+ * Return 0 if okay (i.e. at the very least: command sent). Positive
+ * return values are errors (see SCSI_PT_DO_* defines). If a file descriptor
+ * has already been provided by construct_scsi_pt_obj_with_fd() then the
+ * given 'fd' can be -1 or the same value as given to the constructor. */
+int do_scsi_pt(struct sg_pt_base * objp, int fd, int timeout_secs,
+               int verbose);
+
+#define SCSI_PT_RESULT_GOOD 0
+#define SCSI_PT_RESULT_STATUS 1 /* other than GOOD and CHECK CONDITION */
+#define SCSI_PT_RESULT_SENSE 2
+#define SCSI_PT_RESULT_TRANSPORT_ERR 3
+#define SCSI_PT_RESULT_OS_ERR 4
+/* highest numbered applicable category returned */
+int get_scsi_pt_result_category(const struct sg_pt_base * objp);
+
+/* If not available return 0 which implies there is no residual
+ * value. If supported the number of bytes actually sent back by
+ * the device is 'dxfer_ilen - get_scsi_pt_len()' bytes.  */
+int get_scsi_pt_resid(const struct sg_pt_base * objp);
+
+/* Returns SCSI status value (from device that received the command). If an
+ * NVMe command was issued directly (i.e. through do_scsi_pt() then return
+ * NVMe status (i.e. ((SCT << 8) | SC)) */
+int get_scsi_pt_status_response(const struct sg_pt_base * objp);
+
+/* Returns SCSI status value or, if NVMe command given to do_scsi_pt(),
+ * then returns NVMe result (i.e. DWord(0) from completion queue). If
+ * 'objp' is NULL then returns 0xffffffff. */
+uint32_t get_pt_result(const struct sg_pt_base * objp);
+
+/* Actual sense length returned. If sense data is present but
+   actual sense length is not known, return 'max_sense_len' */
+int get_scsi_pt_sense_len(const struct sg_pt_base * objp);
+
+/* If not available return 0 (for success). */
+int get_scsi_pt_os_err(const struct sg_pt_base * objp);
+char * get_scsi_pt_os_err_str(const struct sg_pt_base * objp, int max_b_len,
+                              char * b);
+
+/* If not available return 0 (for success) */
+int get_scsi_pt_transport_err(const struct sg_pt_base * objp);
+void set_scsi_pt_transport_err(struct sg_pt_base * objp, int err);
+char * get_scsi_pt_transport_err_str(const struct sg_pt_base * objp,
+                                     int max_b_len, char * b);
+
+/* If not available return -1 */
+int get_scsi_pt_duration_ms(const struct sg_pt_base * objp);
+
+/* Return true if device associated with 'objp' uses NVMe command set. To
+ * be useful (in modifying the type of command sent (SCSI or NVMe) then
+ * construct_scsi_pt_obj_with_fd() should be used followed by an invocation
+ * of this function. */
+bool pt_device_is_nvme(const struct sg_pt_base * objp);
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'objp', 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 * objp);
+
+
+/* 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 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() ).  */
+void destruct_scsi_pt_obj(struct sg_pt_base * objp);
+
+#ifdef SG_LIB_WIN32
+#define SG_LIB_WIN32_DIRECT 1
+
+/* Request SPT direct interface when state_direct is 1, state_direct set
+ * to 0 for the SPT indirect interface. Default setting selected by build
+ * (i.e. library compile time) and is usually indirect. */
+void scsi_pt_win32_direct(int state_direct);
+
+/* Returns current SPT interface state, 1 for direct, 0 for indirect */
+int scsi_pt_win32_spt_state(void);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_pt_linux.h b/tools/sg_write_buffer/include/sg_pt_linux.h
new file mode 100644
index 0000000..5e22fd7
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pt_linux.h
@@ -0,0 +1,171 @@
+#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 */
+    /* Leave io_hdr in first place of this structure */
+    bool is_sg;
+    bool is_bsg;
+    bool is_nvme;	/* OS device type, if false ignore nvme_direct */
+    bool nvme_direct;	/* false: our SNTL; true: received NVMe command */
+    bool mdxfer_out;    /* direction of metadata xfer, true->data-out */
+    bool scsi_dsense;   /* SCSI descriptor sense active when true */
+    int dev_fd;                 /* -1 if not given (yet) */
+    int in_err;
+    int os_err;
+    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;       /* DW0 from completion queue */
+    uint32_t nvme_status;       /* SCT|SC: DW3 27:17 from completion queue,
+                                 * note: the DNR+More bit are not there.
+                                 * The whole 16 byte completion q entry is
+                                 * sent back as sense data */
+    uint32_t mdxfer_len;
+    void * mdxferp;
+    uint8_t * nvme_id_ctlp;     /* cached response to controller IDENTIFY */
+    uint8_t * free_nvme_id_ctlp;
+    unsigned char tmf_request[4];
+};
+
+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);
+
+/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5)
+ * to the name of its associated char device (e.g. /dev/nvme0). If this
+ * occurs true is returned and the char device name is placed in 'b' (as
+ * long as b_len is sufficient). Otherwise false is returned. */
+bool sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len,
+                              char * b);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif          /* end of SG_PT_LINUX_H */
diff --git a/tools/sg_write_buffer/include/sg_pt_nvme.h b/tools/sg_write_buffer/include/sg_pt_nvme.h
new file mode 100644
index 0000000..3df98b4
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pt_nvme.h
@@ -0,0 +1,172 @@
+#ifndef SG_PT_NVME_H
+#define SG_PT_NVME_H
+
+/*
+ * Copyright (c) 2017-2018 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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* structures copied and slightly modified from <linux/nvme_ioctl.h> which
+ * is Copyright (c) 2011-2014, Intel Corporation.  */
+
+
+/* Note that the command input structure is in (packed) "cpu" format. That
+ * means, for example, if the CPU is little endian (most are) then so is the
+ * structure. However what comes out in the data-in buffer (e.g. for the
+ * Admin Identify command response) is almost all little endian following ATA
+ * (but no SCSI and IP which are big endian) and Intel's preference. There
+ * are exceptions, for example the EUI-64 identifiers in the Admin Identify
+ * response are big endian.
+ *
+ * Code online (e.g. nvme-cli at github.com) seems to like packed strcutures,
+ * the author prefers byte offset plus a range of unaligned integer builders
+ * such as those in sg_unaligned.h .
+ */
+
+#ifdef __GNUC__
+#ifndef __clang__
+  struct __attribute__((__packed__)) sg_nvme_user_io
+#else
+  struct sg_nvme_user_io
+#endif
+#else
+struct sg_nvme_user_io
+#endif
+{
+        uint8_t opcode;
+        uint8_t flags;
+        uint16_t control;
+        uint16_t nblocks;
+        uint16_t rsvd;
+        uint64_t metadata;
+        uint64_t addr;
+        uint64_t slba;
+        uint32_t dsmgmt;
+        uint32_t reftag;
+        uint16_t apptag;
+        uint16_t appmask;
+}
+#ifdef SG_LIB_FREEBSD
+__packed;
+#else
+;
+#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
+#else
+  struct sg_nvme_passthru_cmd
+#endif
+#else
+struct sg_nvme_passthru_cmd
+#endif
+{
+        uint8_t opcode;
+        uint8_t flags;
+        uint16_t rsvd1;
+        uint32_t nsid;
+        uint32_t cdw2;
+        uint32_t cdw3;
+        uint64_t metadata;
+        uint64_t addr;
+        uint32_t metadata_len;
+        uint32_t data_len;
+        uint32_t cdw10;
+        uint32_t cdw11;
+        uint32_t cdw12;
+        uint32_t cdw13;
+        uint32_t cdw14;
+        uint32_t cdw15;
+#ifdef SG_LIB_LINUX
+        uint32_t timeout_ms;
+        uint32_t result;        /* out: DWord(0) from completion queue */
+#endif
+}
+#ifdef SG_LIB_FREEBSD
+__packed;
+#else
+;
+#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             /* length: 1 byte */
+#define SG_NVME_PT_FLAGS 1              /* length: 1 byte */
+#define SG_NVME_PT_RSVD1 2              /* length: 2 bytes */
+#define SG_NVME_PT_NSID 4               /* length: 4 bytes */
+#define SG_NVME_PT_CDW2 8               /* length: 4 bytes */
+#define SG_NVME_PT_CDW3 12              /* length: 4 bytes */
+#define SG_NVME_PT_METADATA 16          /* length: 8 bytes */
+#define SG_NVME_PT_ADDR 24              /* length: 8 bytes */
+#define SG_NVME_PT_METADATA_LEN 32      /* length: 4 bytes */
+#define SG_NVME_PT_DATA_LEN 36          /* length: 4 bytes */
+#define SG_NVME_PT_CDW10 40             /* length: 4 bytes */
+#define SG_NVME_PT_CDW11 44             /* length: 4 bytes */
+#define SG_NVME_PT_CDW12 48             /* length: 4 bytes */
+#define SG_NVME_PT_CDW13 52             /* length: 4 bytes */
+#define SG_NVME_PT_CDW14 56             /* length: 4 bytes */
+#define SG_NVME_PT_CDW15 60             /* length: 4 bytes */
+
+#ifdef SG_LIB_LINUX
+/* General references state that "all NVMe commands are 64 bytes long". If
+ * so then the following are add-ons by Linux, go to the OS and not the
+ * the NVMe device. */
+#define SG_NVME_PT_TIMEOUT_MS 64        /* length: 4 bytes */
+#define SG_NVME_PT_RESULT 68            /* length: 4 bytes */
+#endif
+
+/* Byte offset of Result and Status (plus phase bit) in CQ */
+#define SG_NVME_PT_CQ_RESULT 0          /* CDW0, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW0 0             /* CDW0, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW1 4             /* CDW1, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW2 8             /* CDW2, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW3 12            /* CDW3, length: 4 bytes */
+#define SG_NVME_PT_CQ_STATUS_P 14       /* CDW3 31:16, length: 2 bytes */
+
+
+/* Valid namespace IDs (nsid_s) range from 1 to 0xfffffffe, leaving: */
+#define SG_NVME_BROADCAST_NSID 0xffffffff       /* all namespaces */
+#define SG_NVME_CTL_NSID 0x0            /* the "controller's" namespace */
+
+/* Given the NVMe Identify Controller response and optionally the NVMe
+ * Identify Namespace response (NULL otherwise), generate the SCSI VPD
+ * page 0x83 (device identification) descriptor(s) in dop. Return the
+ * number of bytes written which will not exceed max_do_len. Probably use
+ * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport
+ * protocol (tproto) should be -1 if not known, else SCSI value.
+ * N.B. Does not write total VPD page length into dop[2:3] . */
+int sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p,
+                               const uint8_t * nvme_id_ns_p, int pdt,
+                               int tproto, uint8_t * dop, int max_do_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif          /* SG_PT_NVME_H */
diff --git a/tools/sg_write_buffer/include/sg_pt_win32.h b/tools/sg_write_buffer/include/sg_pt_win32.h
new file mode 100644
index 0000000..b49437f
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_pt_win32.h
@@ -0,0 +1,473 @@
+#ifndef SG_PT_WIN32_H
+#define SG_PT_WIN32_H
+/*
+ * The information in this file was obtained from scsi-wnt.h by
+ * Richard Stemmer, rs@epost.de . He in turn gives credit to
+ * Jay A. Key (for scsipt.c).
+ * The plscsi program (by Pat LaVarre <p.lavarre@ieee.org>) has
+ * also been used as a reference.
+ * Much of the information in this header can also be obtained
+ * from msdn.microsoft.com .
+ * Updated for cygwin version 1.7.17 changes 20121026
+ */
+
+/* WIN32_LEAN_AND_MEAN may be required to prevent inclusion of <winioctl.h> */
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SCSI_MAX_SENSE_LEN 64
+#define SCSI_MAX_CDB_LEN 16
+#define SCSI_MAX_INDIRECT_DATA 16384
+
+typedef struct {
+    USHORT          Length;
+    UCHAR           ScsiStatus;
+    UCHAR           PathId;
+    UCHAR           TargetId;
+    UCHAR           Lun;
+    UCHAR           CdbLength;
+    UCHAR           SenseInfoLength;
+    UCHAR           DataIn;
+    ULONG           DataTransferLength;
+    ULONG           TimeOutValue;
+    ULONG_PTR       DataBufferOffset;  /* was ULONG; problem in 64 bit */
+    ULONG           SenseInfoOffset;
+    UCHAR           Cdb[SCSI_MAX_CDB_LEN];
+} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH;
+
+
+typedef struct {
+    USHORT          Length;
+    UCHAR           ScsiStatus;
+    UCHAR           PathId;
+    UCHAR           TargetId;
+    UCHAR           Lun;
+    UCHAR           CdbLength;
+    UCHAR           SenseInfoLength;
+    UCHAR           DataIn;
+    ULONG           DataTransferLength;
+    ULONG           TimeOutValue;
+    PVOID           DataBuffer;
+    ULONG           SenseInfoOffset;
+    UCHAR           Cdb[SCSI_MAX_CDB_LEN];
+} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;
+
+
+typedef struct {
+    SCSI_PASS_THROUGH spt;
+    /* plscsi shows a follow on 16 bytes allowing 32 byte cdb */
+    ULONG           Filler;
+    UCHAR           ucSenseBuf[SCSI_MAX_SENSE_LEN];
+    UCHAR           ucDataBuf[SCSI_MAX_INDIRECT_DATA];
+} SCSI_PASS_THROUGH_WITH_BUFFERS, *PSCSI_PASS_THROUGH_WITH_BUFFERS;
+
+
+typedef struct {
+    SCSI_PASS_THROUGH_DIRECT spt;
+    ULONG           Filler;
+    UCHAR           ucSenseBuf[SCSI_MAX_SENSE_LEN];
+} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;
+
+
+
+typedef struct {
+    UCHAR           NumberOfLogicalUnits;
+    UCHAR           InitiatorBusId;
+    ULONG           InquiryDataOffset;
+} SCSI_BUS_DATA, *PSCSI_BUS_DATA;
+
+
+typedef struct {
+    UCHAR           NumberOfBusses;
+    SCSI_BUS_DATA   BusData[1];
+} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO;
+
+
+typedef struct {
+    UCHAR           PathId;
+    UCHAR           TargetId;
+    UCHAR           Lun;
+    BOOLEAN         DeviceClaimed;
+    ULONG           InquiryDataLength;
+    ULONG           NextInquiryDataOffset;
+    UCHAR           InquiryData[1];
+} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA;
+
+
+typedef struct {
+    ULONG           Length;
+    UCHAR           PortNumber;
+    UCHAR           PathId;
+    UCHAR           TargetId;
+    UCHAR           Lun;
+} SCSI_ADDRESS, *PSCSI_ADDRESS;
+
+/*
+ * Standard IOCTL define
+ */
+#ifndef CTL_CODE
+#define CTL_CODE(DevType, Function, Method, Access)             \
+        (((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
+#endif
+
+/*
+ * file access values
+ */
+#ifndef FILE_ANY_ACCESS
+#define FILE_ANY_ACCESS         0
+#endif
+#ifndef FILE_READ_ACCESS
+#define FILE_READ_ACCESS        0x0001
+#endif
+#ifndef FILE_WRITE_ACCESS
+#define FILE_WRITE_ACCESS       0x0002
+#endif
+
+// IOCTL_STORAGE_QUERY_PROPERTY
+
+#define FILE_DEVICE_MASS_STORAGE    0x0000002d
+#define IOCTL_STORAGE_BASE          FILE_DEVICE_MASS_STORAGE
+#define FILE_ANY_ACCESS             0
+
+// #define METHOD_BUFFERED             0
+
+#define IOCTL_STORAGE_QUERY_PROPERTY \
+    CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+
+#ifndef _DEVIOCTL_
+typedef enum _STORAGE_BUS_TYPE {
+    BusTypeUnknown      = 0x00,
+    BusTypeScsi         = 0x01,
+    BusTypeAtapi        = 0x02,
+    BusTypeAta          = 0x03,
+    BusType1394         = 0x04,
+    BusTypeSsa          = 0x05,
+    BusTypeFibre        = 0x06,
+    BusTypeUsb          = 0x07,
+    BusTypeRAID         = 0x08,
+    BusTypeiScsi        = 0x09,
+    BusTypeSas          = 0x0A,
+    BusTypeSata         = 0x0B,
+    BusTypeSd           = 0x0C,
+    BusTypeMmc          = 0x0D,
+    BusTypeVirtual             = 0xE,
+    BusTypeFileBackedVirtual   = 0xF,
+    BusTypeSpaces       = 0x10,
+    BusTypeNvme         = 0x11,
+    BusTypeSCM          = 0x12,
+    BusTypeUfs          = 0x13,
+    BusTypeMax          = 0x14,
+    BusTypeMaxReserved  = 0x7F
+} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
+
+typedef enum _STORAGE_PROTOCOL_TYPE {
+    ProtocolTypeUnknown = 0,
+    ProtocolTypeScsi,
+    ProtocolTypeAta,
+    ProtocolTypeNvme,
+    ProtocolTypeSd
+} STORAGE_PROTOCOL_TYPE;
+
+typedef enum _STORAGE_PROTOCOL_NVME_DATA_TYPE {
+    NVMeDataTypeUnknown = 0,
+    NVMeDataTypeIdentify,
+    NVMeDataTypeLogPage,
+    NVMeDataTypeFeature
+} STORAGE_PROTOCOL_NVME_DATA_TYPE;
+
+typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA {
+    STORAGE_PROTOCOL_TYPE ProtocolType;
+    ULONG DataType;
+    ULONG ProtocolDataRequestValue;
+    ULONG ProtocolDataRequestSubValue;
+    ULONG ProtocolDataOffset;
+    ULONG ProtocolDataLength;
+    ULONG FixedProtocolReturnData;
+    ULONG Reserved[3];
+} STORAGE_PROTOCOL_SPECIFIC_DATA;
+
+
+typedef struct _STORAGE_DEVICE_DESCRIPTOR {
+    ULONG Version;
+    ULONG Size;
+    UCHAR DeviceType;
+    UCHAR DeviceTypeModifier;
+    BOOLEAN RemovableMedia;
+    BOOLEAN CommandQueueing;
+    ULONG VendorIdOffset;       /* 0 if not available */
+    ULONG ProductIdOffset;      /* 0 if not available */
+    ULONG ProductRevisionOffset;/* 0 if not available */
+    ULONG SerialNumberOffset;   /* -1 if not available ?? */
+    STORAGE_BUS_TYPE BusType;
+    ULONG RawPropertiesLength;
+    UCHAR RawDeviceProperties[1];
+} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
+
+#define STORAGE_PROTOCOL_STRUCTURE_VERSION 0x1
+
+#define IOCTL_STORAGE_PROTOCOL_COMMAND \
+        CTL_CODE(IOCTL_STORAGE_BASE, 0x04F0, METHOD_BUFFERED, \
+                FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+typedef struct _STORAGE_PROTOCOL_COMMAND {
+    DWORD         Version;        /* STORAGE_PROTOCOL_STRUCTURE_VERSION */
+    DWORD         Length;
+    STORAGE_PROTOCOL_TYPE   ProtocolType;
+    DWORD         Flags;
+    DWORD         ReturnStatus;
+    DWORD         ErrorCode;
+    DWORD         CommandLength;
+    DWORD         ErrorInfoLength;
+    DWORD         DataToDeviceTransferLength;
+    DWORD         DataFromDeviceTransferLength;
+    DWORD         TimeOutValue;
+    DWORD         ErrorInfoOffset;
+    DWORD         DataToDeviceBufferOffset;
+    DWORD         DataFromDeviceBufferOffset;
+    DWORD         CommandSpecific;
+    DWORD         Reserved0;
+    DWORD         FixedProtocolReturnData;
+    DWORD         Reserved1[3];
+    BYTE          Command[1];     /* has CommandLength elements */
+} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND;
+
+#endif          /* _DEVIOCTL_ */
+
+typedef struct _STORAGE_DEVICE_UNIQUE_IDENTIFIER {
+    ULONG  Version;
+    ULONG  Size;
+    ULONG  StorageDeviceIdOffset;
+    ULONG  StorageDeviceOffset;
+    ULONG  DriveLayoutSignatureOffset;
+} STORAGE_DEVICE_UNIQUE_IDENTIFIER, *PSTORAGE_DEVICE_UNIQUE_IDENTIFIER;
+
+// Use CompareStorageDuids(PSTORAGE_DEVICE_UNIQUE_IDENTIFIER duid1, duid2)
+// to test for equality
+
+#ifndef _DEVIOCTL_
+typedef enum _STORAGE_QUERY_TYPE {
+    PropertyStandardQuery = 0,
+    PropertyExistsQuery,
+    PropertyMaskQuery,
+    PropertyQueryMaxDefined
+} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
+
+typedef enum _STORAGE_PROPERTY_ID {
+    StorageDeviceProperty = 0,
+    StorageAdapterProperty,
+    StorageDeviceIdProperty,
+    StorageDeviceUniqueIdProperty,
+    StorageDeviceWriteCacheProperty,
+    StorageMiniportProperty,
+    StorageAccessAlignmentProperty,
+    /* Identify controller goes to adapter; Identify namespace to device */
+    StorageAdapterProtocolSpecificProperty = 49,
+    StorageDeviceProtocolSpecificProperty = 50
+} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
+
+typedef struct _STORAGE_PROPERTY_QUERY {
+    STORAGE_PROPERTY_ID PropertyId;
+    STORAGE_QUERY_TYPE QueryType;
+    UCHAR AdditionalParameters[1];
+} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
+
+typedef struct _STORAGE_PROTOCOL_DATA_DESCRIPTOR {
+    DWORD  Version;
+    DWORD  Size;
+    STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecificData;
+} STORAGE_PROTOCOL_DATA_DESCRIPTOR, *PSTORAGE_PROTOCOL_DATA_DESCRIPTOR;
+
+// Command completion status
+// The "Phase Tag" field and "Status Field" are separated in spec. We define
+// them in the same data structure to ease the memory access from software.
+//
+typedef union {
+    struct {
+        USHORT  P           : 1;        // Phase Tag (P)
+
+        USHORT  SC          : 8;        // Status Code (SC)
+        USHORT  SCT         : 3;        // Status Code Type (SCT)
+        USHORT  Reserved    : 2;
+        USHORT  M           : 1;        // More (M)
+        USHORT  DNR         : 1;        // Do Not Retry (DNR)
+    } DUMMYSTRUCTNAME;
+    USHORT AsUshort;
+} NVME_COMMAND_STATUS, *PNVME_COMMAND_STATUS;
+
+// Information of log: NVME_LOG_PAGE_ERROR_INFO. Size: 64 bytes
+//
+typedef struct {
+    ULONGLONG  ErrorCount;
+    USHORT     SQID;           // Submission Queue ID
+    USHORT     CMDID;          // Command ID
+    NVME_COMMAND_STATUS Status; // Status Field: This field indicates the
+                                // Status Field for the command that
+                                // completed. The Status Field is located in
+                                // bits 15:01, bit 00 corresponds to the Phase
+                                // Tag posted for the command.
+    struct {
+        USHORT  Byte        : 8;   // Byte in command that contained error
+        USHORT  Bit         : 3;   // Bit in command that contained error
+        USHORT  Reserved    : 5;
+    } ParameterErrorLocation;
+
+    ULONGLONG  Lba;            // LBA: This field indicates the first LBA
+                               // that experienced the error condition, if
+                               // applicable.
+    ULONG      NameSpace;      // Namespace: This field indicates the nsid
+                               // that the error is associated with, if
+                               // applicable.
+    UCHAR      VendorInfoAvailable;    // Vendor Specific Information Available
+    UCHAR      Reserved0[3];
+    ULONGLONG  CommandSpecificInfo;    // This field contains command specific
+                                       // information. If used, the command
+                                       // definition specifies the information
+                                       // returned.
+    UCHAR      Reserved1[24];
+} NVME_ERROR_INFO_LOG, *PNVME_ERROR_INFO_LOG;
+
+typedef struct {
+
+    ULONG   DW0;
+    ULONG   Reserved;
+
+    union {
+        struct {
+            USHORT  SQHD;               // SQ Head Pointer (SQHD)
+            USHORT  SQID;               // SQ Identifier (SQID)
+        } DUMMYSTRUCTNAME;
+
+        ULONG   AsUlong;
+    } DW2;
+
+    union {
+        struct {
+            USHORT              CID;    // Command Identifier (CID)
+            NVME_COMMAND_STATUS Status;
+        } DUMMYSTRUCTNAME;
+
+        ULONG   AsUlong;
+    } DW3;
+
+} NVME_COMPLETION_ENTRY, *PNVME_COMPLETION_ENTRY;
+
+
+// Bit-mask values for STORAGE_PROTOCOL_COMMAND - "Flags" field.
+//
+// Flag indicates the request targeting to adapter instead of device.
+#define STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST    0x80000000
+
+//
+// Status values for STORAGE_PROTOCOL_COMMAND - "ReturnStatus" field.
+//
+#define STORAGE_PROTOCOL_STATUS_PENDING                 0x0
+#define STORAGE_PROTOCOL_STATUS_SUCCESS                 0x1
+#define STORAGE_PROTOCOL_STATUS_ERROR                   0x2
+#define STORAGE_PROTOCOL_STATUS_INVALID_REQUEST         0x3
+#define STORAGE_PROTOCOL_STATUS_NO_DEVICE               0x4
+#define STORAGE_PROTOCOL_STATUS_BUSY                    0x5
+#define STORAGE_PROTOCOL_STATUS_DATA_OVERRUN            0x6
+#define STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES  0x7
+
+#define STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED           0xFF
+
+// Command Length for Storage Protocols.
+//
+// NVMe commands are always 64 bytes.
+#define STORAGE_PROTOCOL_COMMAND_LENGTH_NVME            0x40
+
+// Command Specific Information for Storage Protocols - CommandSpecific field
+//
+#define STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND    0x01
+#define STORAGE_PROTOCOL_SPECIFIC_NVME_NVM_COMMAND 0x02
+
+#endif          /* _DEVIOCTL_ */
+
+
+// NVME_PASS_THROUGH
+
+#ifndef STB_IO_CONTROL
+typedef struct _SRB_IO_CONTROL {
+    ULONG HeaderLength;
+    UCHAR Signature[8];
+    ULONG Timeout;
+    ULONG ControlCode;
+    ULONG ReturnCode;
+    ULONG Length;
+} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
+#endif
+
+#ifndef NVME_PASS_THROUGH_SRB_IO_CODE
+
+#define NVME_SIG_STR "NvmeMini"
+#define NVME_STORPORT_DRIVER 0xe000
+
+#define NVME_PASS_THROUGH_SRB_IO_CODE \
+  CTL_CODE(NVME_STORPORT_DRIVER, 0x0800, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#pragma pack(1)
+
+/* Following is pre-Win10; used with DeviceIoControl(IOCTL_SCSI_MINIPORT),
+ * in Win10 need DeviceIoControl(IOCTL_STORAGE_PROTOCOL_COMMAND) for pure
+ * pass-through. Win10 also has "Protocol specific queries" for things like
+ * Identify and Get feature. */
+typedef struct _NVME_PASS_THROUGH_IOCTL
+{
+    SRB_IO_CONTROL SrbIoCtrl;
+    ULONG VendorSpecific[6];
+    ULONG NVMeCmd[16];      /* Command DW[0...15] */
+    ULONG CplEntry[4];      /* Completion DW[0...3] */
+    ULONG Direction;        /* 0=None, 1=Out, 2=In, 3=I/O */
+    ULONG QueueId;          /* 0=AdminQ */
+    ULONG DataBufferLen;    /* sizeof(DataBuffer) if Data In */
+    ULONG MetaDataLen;
+    ULONG ReturnBufferLen;  /* offsetof(DataBuffer), plus
+                             * sizeof(DataBuffer) if Data Out */
+    UCHAR DataBuffer[1];
+} NVME_PASS_THROUGH_IOCTL;
+#pragma pack()
+
+#endif // NVME_PASS_THROUGH_SRB_IO_CODE
+
+
+/*
+ * method codes
+ */
+#define METHOD_BUFFERED         0
+#define METHOD_IN_DIRECT        1
+#define METHOD_OUT_DIRECT       2
+#define METHOD_NEITHER          3
+
+
+#define IOCTL_SCSI_BASE    0x00000004
+
+/*
+ * constants for DataIn member of SCSI_PASS_THROUGH* structures
+ */
+#define SCSI_IOCTL_DATA_OUT             0
+#define SCSI_IOCTL_DATA_IN              1
+#define SCSI_IOCTL_DATA_UNSPECIFIED     2
+
+#define IOCTL_SCSI_PASS_THROUGH         CTL_CODE(IOCTL_SCSI_BASE, 0x0401, \
+        METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_MINIPORT             CTL_CODE(IOCTL_SCSI_BASE, 0x0402, \
+        METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_GET_INQUIRY_DATA     CTL_CODE(IOCTL_SCSI_BASE, 0x0403, \
+        METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define IOCTL_SCSI_GET_CAPABILITIES     CTL_CODE(IOCTL_SCSI_BASE, 0x0404, \
+        METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define IOCTL_SCSI_PASS_THROUGH_DIRECT  CTL_CODE(IOCTL_SCSI_BASE, 0x0405, \
+        METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_GET_ADDRESS          CTL_CODE(IOCTL_SCSI_BASE, 0x0406, \
+        METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/sg_write_buffer/include/sg_unaligned.h b/tools/sg_write_buffer/include/sg_unaligned.h
new file mode 100644
index 0000000..3b2c70a
--- /dev/null
+++ b/tools/sg_write_buffer/include/sg_unaligned.h
@@ -0,0 +1,325 @@
+#ifndef SG_UNALIGNED_H
+#define SG_UNALIGNED_H
+
+/*
+ * Copyright (c) 2014-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>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Borrowed from the Linux kernel, via mhvtl */
+
+/* In the first section below, functions that copy unsigned integers in a
+ * computer's native format, to and from an unaligned big endian sequence of
+ * bytes. Big endian byte format "on the wire" is the default used by SCSI
+ * standards (www.t10.org). Big endian is also the network byte order. */
+
+static inline uint16_t __get_unaligned_be16(const uint8_t *p)
+{
+        return p[0] << 8 | p[1];
+}
+
+static inline uint32_t __get_unaligned_be32(const uint8_t *p)
+{
+        return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline uint64_t __get_unaligned_be48(const uint8_t *p)
+{
+        return (uint64_t)__get_unaligned_be16(p) << 32 |
+               __get_unaligned_be32(p + 2);
+}
+
+static inline uint64_t __get_unaligned_be64(const uint8_t *p)
+{
+        return (uint64_t)__get_unaligned_be32(p) << 32 |
+               __get_unaligned_be32(p + 4);
+}
+
+static inline void __put_unaligned_be16(uint16_t val, uint8_t *p)
+{
+        *p++ = val >> 8;
+        *p++ = val;
+}
+
+static inline void __put_unaligned_be32(uint32_t val, uint8_t *p)
+{
+        __put_unaligned_be16(val >> 16, p);
+        __put_unaligned_be16(val, p + 2);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline void __put_unaligned_be48(uint64_t val, uint8_t *p)
+{
+        __put_unaligned_be16(val >> 32, p);
+        __put_unaligned_be32(val, p + 2);
+}
+
+static inline void __put_unaligned_be64(uint64_t val, uint8_t *p)
+{
+        __put_unaligned_be32(val >> 32, p);
+        __put_unaligned_be32(val, p + 4);
+}
+
+static inline uint16_t sg_get_unaligned_be16(const void *p)
+{
+        return __get_unaligned_be16((const uint8_t *)p);
+}
+
+static inline uint32_t sg_get_unaligned_be24(const void *p)
+{
+        return ((const uint8_t *)p)[0] << 16 | ((const uint8_t *)p)[1] << 8 |
+               ((const uint8_t *)p)[2];
+}
+
+static inline uint32_t sg_get_unaligned_be32(const void *p)
+{
+        return __get_unaligned_be32((const uint8_t *)p);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline uint64_t sg_get_unaligned_be48(const void *p)
+{
+        return __get_unaligned_be48((const uint8_t *)p);
+}
+
+static inline uint64_t sg_get_unaligned_be64(const void *p)
+{
+        return __get_unaligned_be64((const uint8_t *)p);
+}
+
+/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than
+ * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is
+ * an 8 byte unsigned integer. */
+static inline uint64_t sg_get_unaligned_be(int num_bytes, const void *p)
+{
+        if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t)))
+                return 0;
+        else {
+                const uint8_t * xp = (const uint8_t *)p;
+                uint64_t res = *xp;
+
+                for (++xp; num_bytes > 1; ++xp, --num_bytes)
+                        res = (res << 8) | *xp;
+                return res;
+        }
+}
+
+static inline void sg_put_unaligned_be16(uint16_t val, void *p)
+{
+        __put_unaligned_be16(val, (uint8_t *)p);
+}
+
+static inline void sg_put_unaligned_be24(uint32_t val, void *p)
+{
+        ((uint8_t *)p)[0] = (val >> 16) & 0xff;
+        ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+        ((uint8_t *)p)[2] = val & 0xff;
+}
+
+static inline void sg_put_unaligned_be32(uint32_t val, void *p)
+{
+        __put_unaligned_be32(val, (uint8_t *)p);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline void sg_put_unaligned_be48(uint64_t val, void *p)
+{
+        __put_unaligned_be48(val, (uint8_t *)p);
+}
+
+static inline void sg_put_unaligned_be64(uint64_t val, void *p)
+{
+        __put_unaligned_be64(val, (uint8_t *)p);
+}
+
+/* Since cdb and parameter blocks are often memset to zero before these
+ * unaligned function partially fill them, then check for a val of zero
+ * and ignore if it is with these variants. */
+static inline void sg_nz_put_unaligned_be16(uint16_t val, void *p)
+{
+        if (val)
+                __put_unaligned_be16(val, (uint8_t *)p);
+}
+
+static inline void sg_nz_put_unaligned_be24(uint32_t val, void *p)
+{
+        if (val) {
+                ((uint8_t *)p)[0] = (val >> 16) & 0xff;
+                ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+                ((uint8_t *)p)[2] = val & 0xff;
+        }
+}
+
+static inline void sg_nz_put_unaligned_be32(uint32_t val, void *p)
+{
+        if (val)
+                __put_unaligned_be32(val, (uint8_t *)p);
+}
+
+static inline void sg_nz_put_unaligned_be64(uint64_t val, void *p)
+{
+        if (val)
+            __put_unaligned_be64(val, (uint8_t *)p);
+}
+
+
+/* Below are the little endian equivalents of the big endian functions
+ * above. Little endian is used by ATA, PCI and NVMe.
+ */
+
+static inline uint16_t __get_unaligned_le16(const uint8_t *p)
+{
+        return p[1] << 8 | p[0];
+}
+
+static inline uint32_t __get_unaligned_le32(const uint8_t *p)
+{
+        return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0];
+}
+
+static inline uint64_t __get_unaligned_le64(const uint8_t *p)
+{
+        return (uint64_t)__get_unaligned_le32(p + 4) << 32 |
+               __get_unaligned_le32(p);
+}
+
+static inline void __put_unaligned_le16(uint16_t val, uint8_t *p)
+{
+        *p++ = val;
+        *p++ = val >> 8;
+}
+
+static inline void __put_unaligned_le32(uint32_t val, uint8_t *p)
+{
+        __put_unaligned_le16(val >> 16, p + 2);
+        __put_unaligned_le16(val, p);
+}
+
+static inline void __put_unaligned_le64(uint64_t val, uint8_t *p)
+{
+        __put_unaligned_le32(val >> 32, p + 4);
+        __put_unaligned_le32(val, p);
+}
+
+static inline uint16_t sg_get_unaligned_le16(const void *p)
+{
+        return __get_unaligned_le16((const uint8_t *)p);
+}
+
+static inline uint32_t sg_get_unaligned_le24(const void *p)
+{
+        return (uint32_t)__get_unaligned_le16((const uint8_t *)p) |
+               ((const uint8_t *)p)[2] << 16;
+}
+
+static inline uint32_t sg_get_unaligned_le32(const void *p)
+{
+        return __get_unaligned_le32((const uint8_t *)p);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline uint64_t sg_get_unaligned_le48(const void *p)
+{
+        return (uint64_t)__get_unaligned_le16((const uint8_t *)p + 4) << 32 |
+               __get_unaligned_le32((const uint8_t *)p);
+}
+
+static inline uint64_t sg_get_unaligned_le64(const void *p)
+{
+        return __get_unaligned_le64((const uint8_t *)p);
+}
+
+/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than
+ * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is
+ * an 8 byte unsigned integer. */
+static inline uint64_t sg_get_unaligned_le(int num_bytes, const void *p)
+{
+        if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t)))
+                return 0;
+        else {
+                const uint8_t * xp = (const uint8_t *)p + (num_bytes - 1);
+                uint64_t res = *xp;
+
+                for (--xp; num_bytes > 1; --xp, --num_bytes)
+                        res = (res << 8) | *xp;
+                return res;
+        }
+}
+
+static inline void sg_put_unaligned_le16(uint16_t val, void *p)
+{
+        __put_unaligned_le16(val, (uint8_t *)p);
+}
+
+static inline void sg_put_unaligned_le24(uint32_t val, void *p)
+{
+        ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+        ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+        ((uint8_t *)p)[0] = val & 0xff;
+}
+
+static inline void sg_put_unaligned_le32(uint32_t val, void *p)
+{
+        __put_unaligned_le32(val, (uint8_t *)p);
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline void sg_put_unaligned_le48(uint64_t val, void *p)
+{
+        ((uint8_t *)p)[5] = (val >> 40) & 0xff;
+        ((uint8_t *)p)[4] = (val >> 32) & 0xff;
+        ((uint8_t *)p)[3] = (val >> 24) & 0xff;
+        ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+        ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+        ((uint8_t *)p)[0] = val & 0xff;
+}
+
+static inline void sg_put_unaligned_le64(uint64_t val, void *p)
+{
+        __put_unaligned_le64(val, (uint8_t *)p);
+}
+
+/* Since cdb and parameter blocks are often memset to zero before these
+ * unaligned function partially fill them, then check for a val of zero
+ * and ignore if it is with these variants. */
+static inline void sg_nz_put_unaligned_le16(uint16_t val, void *p)
+{
+        if (val)
+                __put_unaligned_le16(val, (uint8_t *)p);
+}
+
+static inline void sg_nz_put_unaligned_le24(uint32_t val, void *p)
+{
+        if (val) {
+                ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+                ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+                ((uint8_t *)p)[0] = val & 0xff;
+        }
+}
+
+static inline void sg_nz_put_unaligned_le32(uint32_t val, void *p)
+{
+        if (val)
+                __put_unaligned_le32(val, (uint8_t *)p);
+}
+
+static inline void sg_nz_put_unaligned_le64(uint64_t val, void *p)
+{
+        if (val)
+            __put_unaligned_le64(val, (uint8_t *)p);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SG_UNALIGNED_H */
diff --git a/tools/sg_write_buffer/sg_cmds_basic.c b/tools/sg_write_buffer/sg_cmds_basic.c
new file mode 100644
index 0000000..35a4991
--- /dev/null
+++ b/tools/sg_write_buffer/sg_cmds_basic.c
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) 1999-2018 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.
+ */
+
+/*
+ * CONTENTS
+ *    Some SCSI commands are executed in many contexts and hence began
+ *    to appear in several sg3_utils utilities. This files centralizes
+ *    some of the low level command execution code. In most cases the
+ *    interpretation of the command response is left to the each
+ *    utility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+/* Needs to be after config.h */
+#ifdef SG_LIB_LINUX
+#include <errno.h>
+#endif
+
+
+static const char * const version_str = "1.83 20180204";
+
+
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+#define EBUFF_SZ 256
+
+#define DEF_PT_TIMEOUT 60       /* 60 seconds */
+#define START_PT_TIMEOUT 120    /* 120 seconds == 2 minutes */
+#define LONG_PT_TIMEOUT 7200    /* 7,200 seconds == 120 minutes */
+
+#define INQUIRY_CMD     0x12
+#define INQUIRY_CMDLEN  6
+#define REQUEST_SENSE_CMD 0x3
+#define REQUEST_SENSE_CMDLEN 6
+#define REPORT_LUNS_CMD 0xa0
+#define REPORT_LUNS_CMDLEN 12
+#define TUR_CMD  0x0
+#define TUR_CMDLEN  6
+
+#define SAFE_STD_INQ_RESP_LEN 36 /* other lengths lock up some devices */
+
+
+const char *
+sg_cmds_version()
+{
+    return version_str;
+}
+
+#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;
+}
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+   negated errno. */
+int
+sg_cmds_open_device(const char * device_name, bool read_only, int verbose)
+{
+    /* 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;
+
+    return scsi_pt_open_device(device_name, read_only, verbose);
+}
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+   negated errno. */
+int
+sg_cmds_open_flags(const char * device_name, int flags, int verbose)
+{
+    return scsi_pt_open_flags(device_name, flags, verbose);
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+sg_cmds_close_device(int device_fd)
+{
+    return scsi_pt_close_device(device_fd);
+}
+
+static const char * const pass_through_s = "pass-through";
+
+static int
+sg_cmds_process_helper(const char * leadin, int mx_di_len, int resid,
+                       const unsigned char * sbp, int slen, bool noisy,
+                       int verbose, int * o_sense_cat)
+{
+    int scat, got;
+    bool n = false;
+    bool check_data_in = false;
+    char b[512];
+
+    scat = sg_err_category_sense(sbp, slen);
+    switch (scat) {
+    case SG_LIB_CAT_NOT_READY:
+    case SG_LIB_CAT_INVALID_OP:
+    case SG_LIB_CAT_ILLEGAL_REQ:
+    case SG_LIB_CAT_ABORTED_COMMAND:
+    case SG_LIB_CAT_COPY_ABORTED:
+    case SG_LIB_CAT_DATA_PROTECT:
+    case SG_LIB_CAT_PROTECTION:
+    case SG_LIB_CAT_NO_SENSE:
+    case SG_LIB_CAT_MISCOMPARE:
+        n = false;
+        break;
+    case SG_LIB_CAT_RECOVERED:
+    case SG_LIB_CAT_MEDIUM_HARD:
+        check_data_in = true;
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+        __attribute__((fallthrough));
+        /* FALL THROUGH */
+#endif
+#endif
+    case SG_LIB_CAT_UNIT_ATTENTION:
+    case SG_LIB_CAT_SENSE:
+    default:
+        n = noisy;
+        break;
+    }
+    if (verbose || n) {
+        if (leadin && (strlen(leadin) > 0))
+            pr2ws("%s:\n", leadin);
+        sg_get_sense_str(NULL, sbp, slen, (verbose > 1),
+                         sizeof(b), b);
+        pr2ws("%s", b);
+        if ((mx_di_len > 0) && (resid > 0)) {
+            got = mx_di_len - resid;
+            if ((verbose > 2) || check_data_in || (got > 0))
+                pr2ws("    %s requested %d bytes (data-in) but got %d "
+                      "bytes\n", pass_through_s, mx_di_len, got);
+        }
+    }
+    if (o_sense_cat)
+        *o_sense_cat = scat;
+    return -2;
+}
+
+/* This is a helper function used by sg_cmds_* implementations after the
+ * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid
+ * sense data is found it is decoded and output to sg_warnings_strm (def:
+ * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for
+ * "sense" category (may not be fatal), -1 for failed, 0, or a positive
+ * number. If 'mx_di_len > 0' then asks pass-through for resid and returns
+ * (mx_di_len - resid); otherwise returns 0. So for data-in it should return
+ * the actual number of bytes received. For data-out (to device) or no data
+ * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category
+ * output via 'o_sense_cat' pointer (if not NULL). Note that several sense
+ * categories also have data in bytes received; -2 is still returned. */
+int
+sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin,
+                     int pt_res, int mx_di_len, const unsigned char * sbp,
+                     bool noisy, int verbose, int * o_sense_cat)
+{
+    int got, cat, duration, slen, resid, resp_code, sstat;
+    bool transport_sense;
+    char b[1024];
+
+    if (NULL == leadin)
+        leadin = "";
+    if (pt_res < 0) {
+#ifdef SG_LIB_LINUX
+        if (verbose)
+            pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+                  safe_strerror(-pt_res));
+        if ((-ENXIO == pt_res) && o_sense_cat) {
+            if (verbose > 2)
+                pr2ws("map ENXIO to SG_LIB_CAT_NOT_READY\n");
+            *o_sense_cat = SG_LIB_CAT_NOT_READY;
+            return -2;
+        } else if (noisy && (0 == verbose))
+            pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+                  safe_strerror(-pt_res));
+#else
+        if (noisy || verbose)
+            pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+                  safe_strerror(-pt_res));
+#endif
+        return -1;
+    } else if (SCSI_PT_DO_BAD_PARAMS == pt_res) {
+        pr2ws("%s: bad %s setup\n", leadin, pass_through_s);
+        return -1;
+    } else if (SCSI_PT_DO_TIMEOUT == pt_res) {
+        pr2ws("%s: %s timeout\n", leadin, pass_through_s);
+        return -1;
+    }
+    if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0))
+        pr2ws("      duration=%d ms\n", duration);
+    resid = (mx_di_len > 0) ? get_scsi_pt_resid(ptvp) : 0;
+    slen = get_scsi_pt_sense_len(ptvp);
+    switch ((cat = get_scsi_pt_result_category(ptvp))) {
+    case SCSI_PT_RESULT_GOOD:
+        if (sbp && (slen > 7)) {
+            resp_code = sbp[0] & 0x7f;
+            /* SBC referrals can have status=GOOD and sense_key=COMPLETED */
+            if (resp_code >= 0x70) {
+                if (resp_code < 0x72) {
+                    if (SPC_SK_NO_SENSE != (0xf & sbp[2]))
+                        sg_err_category_sense(sbp, slen);
+                } else if (resp_code < 0x74) {
+                    if (SPC_SK_NO_SENSE != (0xf & sbp[1]))
+                        sg_err_category_sense(sbp, slen);
+                }
+            }
+        }
+        if (mx_di_len > 0) {
+            got = mx_di_len - resid;
+            if ((verbose > 1) && (resid != 0))
+                pr2ws("    %s: %s requested %d bytes (data-in) but got %d "
+                      "bytes\n", leadin, pass_through_s, mx_di_len, got);
+            if (got >= 0)
+                return got;
+            else {
+                if (verbose)
+                    pr2ws("    %s: %s can't get negative bytes, say it got "
+                          "none\n", leadin, pass_through_s);
+                return 0;
+            }
+        } else
+            return 0;
+    case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+        sstat = get_scsi_pt_status_response(ptvp);
+        if (o_sense_cat) {
+            switch (sstat) {
+            case SAM_STAT_RESERVATION_CONFLICT:
+                *o_sense_cat = SG_LIB_CAT_RES_CONFLICT;
+                return -2;
+            case SAM_STAT_CONDITION_MET:
+                *o_sense_cat = SG_LIB_CAT_CONDITION_MET;
+                return -2;
+            case SAM_STAT_BUSY:
+                *o_sense_cat = SG_LIB_CAT_BUSY;
+                return -2;
+            case SAM_STAT_TASK_SET_FULL:
+                *o_sense_cat = SG_LIB_CAT_TS_FULL;
+                return -2;
+            case SAM_STAT_ACA_ACTIVE:
+                *o_sense_cat = SG_LIB_CAT_ACA_ACTIVE;
+                return -2;
+            case SAM_STAT_TASK_ABORTED:
+                *o_sense_cat = SG_LIB_CAT_TASK_ABORTED;
+                return -2;
+            default:
+                break;
+            }
+        }
+        if (verbose || noisy) {
+            sg_get_scsi_status_str(sstat, sizeof(b), b);
+            pr2ws("%s: scsi status: %s\n", leadin, b);
+        }
+        return -1;
+    case SCSI_PT_RESULT_SENSE:
+        return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp, slen,
+                                      noisy, verbose, o_sense_cat);
+    case SCSI_PT_RESULT_TRANSPORT_ERR:
+        if (verbose || noisy) {
+            get_scsi_pt_transport_err_str(ptvp, sizeof(b), b);
+            pr2ws("%s: transport: %s\n", leadin, b);
+        }
+#ifdef SG_LIB_LINUX
+        transport_sense = (slen > 0);
+#else
+        transport_sense = ((SAM_STAT_CHECK_CONDITION ==
+                            get_scsi_pt_status_response(ptvp)) && (slen > 0));
+#endif
+        if (transport_sense)
+            return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp,
+                                          slen, noisy, verbose, o_sense_cat);
+        else
+            return -1;
+    case SCSI_PT_RESULT_OS_ERR:
+        if (verbose || noisy) {
+            get_scsi_pt_os_err_str(ptvp, sizeof(b), b);
+            pr2ws("%s: os: %s\n", leadin, b);
+        }
+        return -1;
+    default:
+        pr2ws("%s: unknown %s result category (%d)\n", leadin, pass_through_s,
+               cat);
+        return -1;
+    }
+}
+
+bool
+sg_cmds_is_nvme(const struct sg_pt_base * ptvp)
+{
+    return pt_device_is_nvme(ptvp);
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+    struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp)
+        pr2ws("%s: out of memory\n", cname);
+    return ptvp;
+}
+
+static const char * const inquiry_s = "inquiry";
+
+static int
+sg_ll_inquiry_com(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+                  int mx_resp_len, int timeout_secs, int * residp,
+                  bool noisy, int verbose)
+{
+    int res, ret, k, sense_cat, resid;
+    unsigned char inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    unsigned char * up;
+    struct sg_pt_base * ptvp;
+
+    if (cmddt)
+        inq_cdb[1] |= 0x2;
+    if (evpd)
+        inq_cdb[1] |= 0x1;
+    inq_cdb[2] = (unsigned char)pg_op;
+    /* 16 bit allocation length (was 8, increased in spc3r09, 200209) */
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3);
+    if (verbose) {
+        pr2ws("    %s cdb: ", inquiry_s);
+        for (k = 0; k < INQUIRY_CMDLEN; ++k)
+            pr2ws("%02x ", inq_cdb[k]);
+        pr2ws("\n");
+    }
+    if (resp && (mx_resp_len > 0)) {
+        up = (unsigned char *)resp;
+        up[0] = 0x7f;   /* defensive prefill */
+        if (mx_resp_len > 4)
+            up[4] = 0;
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+    ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp) {
+        pr2ws("%s: out of memory\n", __func__);
+        if (residp)
+            *residp = 0;
+        return -1;
+    }
+    set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, inquiry_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (residp)
+        *residp = resid;
+    if (-1 == ret)
+        ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+    else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else if (ret < 4) {
+        if (verbose)
+            pr2ws("%s: got too few bytes (%d)\n", __func__, ret);
+        ret = SG_LIB_CAT_MALFORMED;
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+
+    if (resid > 0) {
+        if (resid > mx_resp_len) {
+            pr2ws("%s resid (%d) should never exceed requested "
+                    "len=%d\n", inquiry_s, resid, mx_resp_len);
+            return ret ? ret : SG_LIB_CAT_MALFORMED;
+        }
+        /* zero unfilled section of response buffer, based on resid */
+        memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
+    }
+    return ret;
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb. */
+int
+sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+              int mx_resp_len, bool noisy, int verbose)
+{
+    return sg_ll_inquiry_com(sg_fd, cmddt, evpd, pg_op, resp, mx_resp_len,
+                             0 /* timeout_sec */, NULL, noisy, verbose);
+}
+
+/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data,
+                  bool noisy, int verbose)
+{
+    int ret;
+    unsigned char inq_resp[SAFE_STD_INQ_RESP_LEN];
+
+    if (inq_data) {
+        memset(inq_data, 0, sizeof(* inq_data));
+        inq_data->peripheral_qualifier = 0x3;
+        inq_data->peripheral_type = 0x1f;
+    }
+    ret = sg_ll_inquiry_com(sg_fd, false, false, 0, inq_resp,
+                            sizeof(inq_resp), 0, NULL, noisy, verbose);
+
+    if (inq_data && (0 == ret)) {
+        inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7;
+        inq_data->peripheral_type = inq_resp[0] & 0x1f;
+        inq_data->byte_1 = inq_resp[1];
+        inq_data->version = inq_resp[2];
+        inq_data->byte_3 = inq_resp[3];
+        inq_data->byte_5 = inq_resp[5];
+        inq_data->byte_6 = inq_resp[6];
+        inq_data->byte_7 = inq_resp[7];
+        memcpy(inq_data->vendor, inq_resp + 8, 8);
+        memcpy(inq_data->product, inq_resp + 16, 16);
+        memcpy(inq_data->revision, inq_resp + 32, 4);
+    }
+    return ret;
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so
+ * 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 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
+ * bytes have been written. */
+int
+sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp,
+                 int mx_resp_len, int timeout_secs, int * residp,
+                 bool noisy, int verbose)
+{
+    return sg_ll_inquiry_com(sg_fd, false, evpd, pg_op, resp, mx_resp_len,
+                             timeout_secs, residp, noisy, verbose);
+}
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Looks for progress indicator if 'progress' non-NULL;
+ * if found writes value [0..65535] else write -1.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress,
+                               bool noisy, int verbose)
+{
+    static const char * const tur_s = "test unit ready";
+    int res, ret, k, sense_cat;
+    unsigned char tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", tur_s);
+        for (k = 0; k < TUR_CMDLEN; ++k)
+            pr2ws("%02x ", tur_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(tur_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_packet_id(ptvp, pack_id);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, tur_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        if (progress) {
+            int slen = get_scsi_pt_sense_len(ptvp);
+
+            if (! sg_get_sense_progress_fld(sense_b, slen, progress))
+                *progress = -1;
+        }
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose)
+{
+    return sg_ll_test_unit_ready_progress(sg_fd, pack_id, NULL, noisy,
+                                          verbose);
+}
+
+/* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various
+ * SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len,
+                    bool noisy, int verbose)
+{
+    static const char * const rq_s = "request sense";
+    int k, ret, res, sense_cat;
+    unsigned char rs_cdb[REQUEST_SENSE_CMDLEN] =
+        {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (desc)
+        rs_cdb[1] |= 0x1;
+    if (mx_resp_len > 0xff) {
+        pr2ws("mx_resp_len cannot exceed 255\n");
+        return -1;
+    }
+    rs_cdb[4] = mx_resp_len & 0xff;
+    if (verbose) {
+        pr2ws("    %s cmd: ", rq_s);
+        for (k = 0; k < REQUEST_SENSE_CMDLEN; ++k)
+            pr2ws("%02x ", rs_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(rq_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, rq_s, res, mx_resp_len, sense_b, noisy,
+                               verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((mx_resp_len >= 8) && (ret < 8)) {
+            if (verbose)
+                pr2ws("    %s: got %d bytes in response, too short\n", rq_s,
+                      ret);
+            ret = -1;
+        } else
+            ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len,
+                  bool noisy, int verbose)
+{
+    static const char * const report_luns_s = "report luns";
+    int k, ret, res, sense_cat;
+    unsigned char rl_cdb[REPORT_LUNS_CMDLEN] =
+                         {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    rl_cdb[2] = select_report & 0xff;
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", report_luns_s);
+        for (k = 0; k < REPORT_LUNS_CMDLEN; ++k)
+            pr2ws("%02x ", rl_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(report_luns_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, report_luns_s, res, mx_resp_len,
+                               sense_b, noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
diff --git a/tools/sg_write_buffer/sg_cmds_basic2.c b/tools/sg_write_buffer/sg_cmds_basic2.c
new file mode 100644
index 0000000..18b6cd7
--- /dev/null
+++ b/tools/sg_write_buffer/sg_cmds_basic2.c
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (c) 1999-2018 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.
+ */
+
+/*
+ * CONTENTS
+ *    Some SCSI commands are executed in many contexts and hence began
+ *    to appear in several sg3_utils utilities. This files centralizes
+ *    some of the low level command execution code. In most cases the
+ *    interpretation of the command response is left to the each
+ *    utility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+
+
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+#define EBUFF_SZ 256
+
+#define DEF_PT_TIMEOUT 60       /* 60 seconds */
+#define START_PT_TIMEOUT 120    /* 120 seconds == 2 minutes */
+#define LONG_PT_TIMEOUT 7200    /* 7,200 seconds == 120 minutes */
+
+#define SYNCHRONIZE_CACHE_CMD     0x35
+#define SYNCHRONIZE_CACHE_CMDLEN  10
+#define SERVICE_ACTION_IN_16_CMD 0x9e
+#define SERVICE_ACTION_IN_16_CMDLEN 16
+#define READ_CAPACITY_16_SA 0x10
+#define READ_CAPACITY_10_CMD 0x25
+#define READ_CAPACITY_10_CMDLEN 10
+#define MODE_SENSE6_CMD      0x1a
+#define MODE_SENSE6_CMDLEN   6
+#define MODE_SENSE10_CMD     0x5a
+#define MODE_SENSE10_CMDLEN  10
+#define MODE_SELECT6_CMD   0x15
+#define MODE_SELECT6_CMDLEN   6
+#define MODE_SELECT10_CMD   0x55
+#define MODE_SELECT10_CMDLEN  10
+#define LOG_SENSE_CMD     0x4d
+#define LOG_SENSE_CMDLEN  10
+#define LOG_SELECT_CMD     0x4c
+#define LOG_SELECT_CMDLEN  10
+#define START_STOP_CMD          0x1b
+#define START_STOP_CMDLEN       6
+#define PREVENT_ALLOW_CMD    0x1e
+#define PREVENT_ALLOW_CMDLEN   6
+
+#define MODE6_RESP_HDR_LEN 4
+#define MODE10_RESP_HDR_LEN 8
+#define MODE_RESP_ARB_LEN 1024
+
+#define INQUIRY_RESP_INITIAL_LEN 36
+
+
+#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;
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+    struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp)
+        pr2ws("%s: out of memory\n", cname);
+    return ptvp;
+}
+
+/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group,
+                    unsigned int lba, unsigned int count, bool noisy,
+                    int verbose)
+{
+    static const char * const cdb_name_s = "synchronize cache(10)";
+    int res, ret, k, sense_cat;
+    unsigned char sc_cdb[SYNCHRONIZE_CACHE_CMDLEN] =
+                {SYNCHRONIZE_CACHE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (sync_nv)
+        sc_cdb[1] |= 4;
+    if (immed)
+        sc_cdb[1] |= 2;
+    sg_put_unaligned_be32((uint32_t)lba, sc_cdb + 2);
+    sc_cdb[6] = group & 0x1f;
+    if (count > 0xffff) {
+        pr2ws("count too big\n");
+        return -1;
+    }
+    sg_put_unaligned_be16((int16_t)count, sc_cdb + 7);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SYNCHRONIZE_CACHE_CMDLEN; ++k)
+            pr2ws("%02x ", sc_cdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, sc_cdb, sizeof(sc_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp,
+                 int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "read capacity(16)";
+    int k, ret, res, sense_cat;
+    unsigned char rc_cdb[SERVICE_ACTION_IN_16_CMDLEN] =
+                        {SERVICE_ACTION_IN_16_CMD, READ_CAPACITY_16_SA,
+                         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (pmi) { /* lbs only valid when pmi set */
+        rc_cdb[14] |= 1;
+        sg_put_unaligned_be64(llba, rc_cdb + 2);
+    }
+    /* Allocation length, no guidance in SBC-2 rev 15b */
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rc_cdb + 10);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
+            pr2ws("%02x ", rc_cdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ CAPACITY (10) command. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp,
+                 int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "read capacity(10)";
+    int k, ret, res, sense_cat;
+    unsigned char rc_cdb[READ_CAPACITY_10_CMDLEN] =
+                         {READ_CAPACITY_10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (pmi) { /* lbs only valid when pmi set */
+        rc_cdb[8] |= 1;
+        sg_put_unaligned_be32((uint32_t)lba, rc_cdb + 2);
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_CAPACITY_10_CMDLEN; ++k)
+            pr2ws("%02x ", rc_cdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, int sub_pg_code,
+                  void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "mode sense(6)";
+    int res, ret, k, sense_cat, resid;
+    unsigned char modes_cdb[MODE_SENSE6_CMDLEN] =
+        {MODE_SENSE6_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    modes_cdb[1] = (unsigned char)(dbd ? 0x8 : 0);
+    modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+    modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff);
+    modes_cdb[4] = (unsigned char)(mx_resp_len & 0xff);
+    if (mx_resp_len > 0xff) {
+        pr2ws("mx_resp_len too big\n");
+        return -1;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MODE_SENSE6_CMDLEN; ++k)
+            pr2ws("%02x ", modes_cdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+
+    if (resid > 0) {
+        if (resid > mx_resp_len) {
+            pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+                  cdb_name_s, resid, mx_resp_len);
+            return ret ? ret : SG_LIB_CAT_MALFORMED;
+        }
+        /* zero unfilled section of response buffer */
+        memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
+    }
+    return ret;
+}
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+                   int sub_pg_code, void * resp, int mx_resp_len,
+                   bool noisy, int verbose)
+{
+    return sg_ll_mode_sense10_v2(sg_fd, llbaa, dbd, pc, pg_code, sub_pg_code,
+                                 resp, mx_resp_len, 0, NULL, noisy, verbose);
+}
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * 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 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
+ * bytes have been written. */
+int
+sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+                      int sub_pg_code, void * resp, int mx_resp_len,
+                      int timeout_secs, int * residp, bool noisy, int verbose)
+{
+    int res, ret, k, sense_cat, resid;
+    static const char * const cdb_name_s = "mode sense(10)";
+    struct sg_pt_base * ptvp;
+    unsigned char modes_cdb[MODE_SENSE10_CMDLEN] =
+        {MODE_SENSE10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+
+    modes_cdb[1] = (unsigned char)((dbd ? 0x8 : 0) | (llbaa ? 0x10 : 0));
+    modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+    modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff);
+    sg_put_unaligned_be16((int16_t)mx_resp_len, modes_cdb + 7);
+    if (mx_resp_len > 0xffff) {
+        pr2ws("mx_resp_len too big\n");
+        goto gen_err;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MODE_SENSE10_CMDLEN; ++k)
+            pr2ws("%02x ", modes_cdb[k]);
+        pr2ws("\n");
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        goto gen_err;
+    set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (residp)
+        *residp = resid;
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+
+    if (resid > 0) {
+        if (resid > mx_resp_len) {
+            pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+                  cdb_name_s, resid, mx_resp_len);
+            return ret ? ret : SG_LIB_CAT_MALFORMED;
+        }
+        /* zero unfilled section of response buffer */
+        memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
+    }
+    return ret;
+gen_err:
+    if (residp)
+        *residp = 0;
+    return -1;
+}
+
+/* Invokes a SCSI MODE SELECT (6) command.  Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, int param_len,
+                   bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "mode select(6)";
+    int res, ret, k, sense_cat;
+    unsigned char modes_cdb[MODE_SELECT6_CMDLEN] =
+        {MODE_SELECT6_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0));
+    modes_cdb[4] = (unsigned char)(param_len & 0xff);
+    if (param_len > 0xff) {
+        pr2ws("%s: param_len too big\n", cdb_name_s);
+        return -1;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MODE_SELECT6_CMDLEN; ++k)
+            pr2ws("%02x ", modes_cdb[k]);
+        pr2ws("\n");
+    }
+    if (verbose > 1) {
+        pr2ws("    %s parameter list\n", cdb_name_s);
+        hex2stderr((const uint8_t *)paramp, param_len, -1);
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI MODE SELECT (10) command.  Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, int param_len,
+                    bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "mode select(10)";
+    int res, ret, k, sense_cat;
+    unsigned char modes_cdb[MODE_SELECT10_CMDLEN] =
+        {MODE_SELECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0));
+    sg_put_unaligned_be16((int16_t)param_len, modes_cdb + 7);
+    if (param_len > 0xffff) {
+        pr2ws("%s: param_len too big\n", cdb_name_s);
+        return -1;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MODE_SELECT10_CMDLEN; ++k)
+            pr2ws("%02x ", modes_cdb[k]);
+        pr2ws("\n");
+    }
+    if (verbose > 1) {
+        pr2ws("    %s parameter list\n", cdb_name_s);
+        hex2stderr((const uint8_t *)paramp, param_len, -1);
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. In most cases users are
+ * interested in the first mode page. This function returns the (byte)
+ * offset of the start of the first mode page. Set mode_sense_6 to true for
+ * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful
+ * or -1 if failure. If there is a failure a message is written to err_buff
+ * if it is non-NULL and err_buff_len > 0. */
+int
+sg_mode_page_offset(const unsigned char * resp, int resp_len,
+                    bool mode_sense_6, char * err_buff, int err_buff_len)
+{
+    int bd_len, calc_len, offset;
+    bool err_buff_ok = ((err_buff_len > 0) && err_buff);
+
+    if ((NULL == resp) || (resp_len < 4))
+            goto too_short;
+    if (mode_sense_6) {
+        calc_len = resp[0] + 1;
+        bd_len = resp[3];
+        offset = bd_len + MODE6_RESP_HDR_LEN;
+    } else {    /* Mode sense(10) */
+        if (resp_len < 8)
+            goto too_short;
+        calc_len = sg_get_unaligned_be16(resp) + 2;
+        bd_len = sg_get_unaligned_be16(resp + 6);
+        /* LongLBA doesn't change this calculation */
+        offset = bd_len + MODE10_RESP_HDR_LEN;
+    }
+    if ((offset + 2) > calc_len) {
+        if (err_buff_ok)
+            snprintf(err_buff, err_buff_len, "calculated response "
+                     "length too small, offset=%d calc_len=%d bd_len=%d\n",
+                     offset, calc_len, bd_len);
+        offset = -1;
+    }
+    return offset;
+too_short:
+    if (err_buff_ok)
+        snprintf(err_buff, err_buff_len, "given MS(%d) response length (%d) "
+                 "too short\n", (mode_sense_6 ? 6 : 10), resp_len);
+    return -1;
+}
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. This functions returns the
+ * length (in bytes) of those three components. Note that the return value
+ * can exceed resp_len in which case the MODE SENSE command should be
+ * re-issued with a larger response buffer. If bd_lenp is non-NULL and if
+ * successful the block descriptor length (in bytes) is written to *bd_lenp.
+ * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10)
+ * responses. Returns -1 if there is an error (e.g. response too short). */
+int
+sg_msense_calc_length(const unsigned char * resp, int resp_len,
+                      bool mode_sense_6, int * bd_lenp)
+{
+    int calc_len;
+
+    if (NULL == resp)
+        goto an_err;
+    if (mode_sense_6) {
+        if (resp_len < 4)
+            goto an_err;
+        calc_len = resp[0] + 1;
+    } else {
+        if (resp_len < 8)
+            goto an_err;
+        calc_len = sg_get_unaligned_be16(resp + 0) + 2;
+    }
+    if (bd_lenp)
+        *bd_lenp = mode_sense_6 ? resp[3] : sg_get_unaligned_be16(resp + 6);
+    return calc_len;
+an_err:
+    if (bd_lenp)
+        *bd_lenp = 0;
+    return -1;
+}
+
+/* Fetches current, changeable, default and/or saveable modes pages as
+ * indicated by pcontrol_arr for given pg_code and sub_pg_code. If
+ * mode6==false then use MODE SENSE (10) else use MODE SENSE (6). If
+ * flexible set and mode data length seems wrong then try and
+ * fix (compensating hack for bad device or driver). pcontrol_arr
+ * should have 4 elements for output of current, changeable, default
+ * and saved values respectively. Each element should be NULL or
+ * at least mx_mpage_len bytes long.
+ * Return of 0 -> overall success, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors.
+ * If success_mask pointer is not NULL then first zeros it. Then set bits
+ * 0, 1, 2 and/or 3 if the current, changeable, default and saved values
+ * respectively have been fetched. If error on current page
+ * then stops and returns that error; otherwise continues if an error is
+ * detected but returns the first error encountered.  */
+int
+sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, int sub_pg_code,
+                          bool dbd, bool flexible, int mx_mpage_len,
+                          int * success_mask, void * pcontrol_arr[],
+                          int * reported_lenp, int verbose)
+{
+    bool resp_mode6;
+    int k, n, res, offset, calc_len, xfer_len;
+    int resid = 0;
+    const int msense10_hlen = MODE10_RESP_HDR_LEN;
+    unsigned char buff[MODE_RESP_ARB_LEN];
+    char ebuff[EBUFF_SZ];
+    int first_err = 0;
+
+    if (success_mask)
+        *success_mask = 0;
+    if (reported_lenp)
+        *reported_lenp = 0;
+    if (mx_mpage_len < 4)
+        return 0;
+    memset(ebuff, 0, sizeof(ebuff));
+    /* first try to find length of current page response */
+    memset(buff, 0, msense10_hlen);
+    if (mode6)  /* want first 8 bytes just in case */
+        res = sg_ll_mode_sense6(sg_fd, dbd, 0 /* pc */, pg_code,
+                                sub_pg_code, buff, msense10_hlen, true,
+                                verbose);
+    else        /* MODE SENSE(10) obviously */
+        res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd,
+                                    0 /* pc */, pg_code, sub_pg_code, buff,
+                                    msense10_hlen, 0, &resid, true, verbose);
+    if (0 != res)
+        return res;
+    n = buff[0];
+    if (reported_lenp) {
+        int m;
+
+        m = sg_msense_calc_length(buff, msense10_hlen, mode6, NULL) - resid;
+        if (m < 0)      /* Grrr, this should not happen */
+            m = 0;
+        *reported_lenp = m;
+    }
+    resp_mode6 = mode6;
+    if (flexible) {
+        if (mode6 && (n < 3)) {
+            resp_mode6 = false;
+            if (verbose)
+                pr2ws(">>> msense(6) but resp[0]=%d so try msense(10) "
+                      "response processing\n", n);
+        }
+        if ((! mode6) && (n > 5)) {
+            if ((n > 11) && (0 == (n % 2)) && (0 == buff[4]) &&
+                (0 == buff[5]) && (0 == buff[6])) {
+                buff[1] = n;
+                buff[0] = 0;
+                if (verbose)
+                    pr2ws(">>> msense(10) but resp[0]=%d and not msense(6) "
+                          "response so fix length\n", n);
+            } else
+                resp_mode6 = true;
+        }
+    }
+    if (verbose && (resp_mode6 != mode6))
+        pr2ws(">>> msense(%d) but resp[0]=%d so switch response "
+              "processing\n", (mode6 ? 6 : 10), buff[0]);
+    calc_len = sg_msense_calc_length(buff, msense10_hlen, resp_mode6, NULL);
+    if (calc_len > MODE_RESP_ARB_LEN)
+        calc_len = MODE_RESP_ARB_LEN;
+    offset = sg_mode_page_offset(buff, calc_len, resp_mode6, ebuff, EBUFF_SZ);
+    if (offset < 0) {
+        if (('\0' != ebuff[0]) && (verbose > 0))
+            pr2ws("%s: %s\n", __func__, ebuff);
+        return SG_LIB_CAT_MALFORMED;
+    }
+    xfer_len = calc_len - offset;
+    if (xfer_len > mx_mpage_len)
+        xfer_len = mx_mpage_len;
+
+    for (k = 0; k < 4; ++k) {
+        if (NULL == pcontrol_arr[k])
+            continue;
+        memset(pcontrol_arr[k], 0, mx_mpage_len);
+        resid = 0;
+        if (mode6)
+            res = sg_ll_mode_sense6(sg_fd, dbd, k /* pc */,
+                                    pg_code, sub_pg_code, buff,
+                                    calc_len, true, verbose);
+        else
+            res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd,
+                                        k /* pc */, pg_code, sub_pg_code,
+                                        buff, calc_len, 0, &resid, true,
+                                        verbose);
+        if (res || resid) {
+            if (0 == first_err) {
+                if (res)
+                    first_err = res;
+                else {
+                    first_err = -49;    /* unexpected resid != 0 */
+                    if (verbose)
+                        pr2ws("%s: unexpected resid=%d, page=0x%x, "
+                              "pcontrol=%d\n", __func__, resid, pg_code, k);
+                }
+            }
+            if (0 == k)
+                break;  /* if problem on current page, it won't improve */
+            else
+                continue;
+        }
+        if (xfer_len > 0)
+            memcpy(pcontrol_arr[k], buff + offset, xfer_len);
+        if (success_mask)
+            *success_mask |= (1 << k);
+    }
+    return first_err;
+}
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors. */
+int
+sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+                int subpg_code, int paramp, unsigned char * resp,
+                int mx_resp_len, bool noisy, int verbose)
+{
+    return sg_ll_log_sense_v2(sg_fd, ppc, sp, pc, pg_code, subpg_code,
+                              paramp, resp, mx_resp_len, 0, NULL, noisy,
+                              verbose);
+}
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * 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 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
+ * bytes have been written. */
+int
+sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+                   int subpg_code, int paramp, unsigned char * resp,
+                   int mx_resp_len, int timeout_secs, int * residp,
+                   bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "log sense";
+    int res, ret, k, sense_cat, resid;
+    unsigned char logs_cdb[LOG_SENSE_CMDLEN] =
+        {LOG_SENSE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (mx_resp_len > 0xffff) {
+        pr2ws("mx_resp_len too big\n");
+        goto gen_err;
+    }
+    logs_cdb[1] = (unsigned char)((ppc ? 2 : 0) | (sp ? 1 : 0));
+    logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+    logs_cdb[3] = (unsigned char)(subpg_code & 0xff);
+    sg_put_unaligned_be16((int16_t)paramp, logs_cdb + 5);
+    sg_put_unaligned_be16((int16_t)mx_resp_len, logs_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < LOG_SENSE_CMDLEN; ++k)
+            pr2ws("%02x ", logs_cdb[k]);
+        pr2ws("\n");
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        goto gen_err;
+    set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len,
+                               sense_b, noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (residp)
+        *residp = resid;
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((mx_resp_len > 3) && (ret < 4)) {
+            /* resid indicates LOG SENSE response length bad, so zero it */
+            resp[2] = 0;
+            resp[3] = 0;
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+
+    if (resid > 0) {
+        if (resid > mx_resp_len) {
+            pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+                  cdb_name_s, resid, mx_resp_len);
+            return ret ? ret : SG_LIB_CAT_MALFORMED;
+        }
+        /* zero unfilled section of response buffer */
+        memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
+    }
+    return ret;
+gen_err:
+    if (residp)
+        *residp = 0;
+    return -1;
+}
+
+/* Invokes a SCSI LOG SELECT command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code,
+                 int subpg_code, unsigned char * paramp, int param_len,
+                 bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "log select";
+    int res, ret, k, sense_cat;
+    unsigned char logs_cdb[LOG_SELECT_CMDLEN] =
+        {LOG_SELECT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (param_len > 0xffff) {
+        pr2ws("%s: param_len too big\n", cdb_name_s);
+        return -1;
+    }
+    logs_cdb[1] = (unsigned char)((pcr ? 2 : 0) | (sp ? 1 : 0));
+    logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+    logs_cdb[3] = (unsigned char)(subpg_code & 0xff);
+    sg_put_unaligned_be16((int16_t)param_len, logs_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < LOG_SELECT_CMDLEN; ++k)
+            pr2ws("%02x ", logs_cdb[k]);
+        pr2ws("\n");
+    }
+    if ((verbose > 1) && (param_len > 0)) {
+        pr2ws("    %s parameter list\n", cdb_name_s);
+        hex2stderr(paramp, param_len, -1);
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI START STOP UNIT command (SBC + MMC).
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and
+ * format_layer_number(mmc) fields. They also overlap on the noflush(sbc)
+ * and fl(mmc) one bit field. This is the cause of the awkardly named
+ * pc_mod__fl_num and noflush__fl arguments to this function.
+ *  */
+int
+sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
+                      int power_cond, bool noflush__fl, bool loej, bool start,
+                      bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "start stop unit";
+    int k, res, ret, sense_cat;
+    struct sg_pt_base * ptvp;
+    unsigned char ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+
+    if (immed)
+        ssuBlk[1] = 0x1;
+    ssuBlk[3] = pc_mod__fl_num & 0xf;  /* bits 2 and 3 are reserved in MMC */
+    ssuBlk[4] = ((power_cond & 0xf) << 4);
+    if (noflush__fl)
+        ssuBlk[4] |= 0x4;
+    if (loej)
+        ssuBlk[4] |= 0x2;
+    if (start)
+        ssuBlk[4] |= 0x1;
+    if (verbose) {
+        pr2ws("    %s command:", cdb_name_s);
+        for (k = 0; k < (int)sizeof(ssuBlk); ++k)
+                pr2ws(" %02x", ssuBlk[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, START_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+            ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command
+ * [was in SPC-3 but displaced from SPC-4 into SBC-3, MMC-5, SSC-3]
+ * prevent==0 allows removal, prevent==1 prevents removal ...
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "prevent allow medium removal";
+    int k, res, ret, sense_cat;
+    unsigned char p_cdb[PREVENT_ALLOW_CMDLEN] =
+                {PREVENT_ALLOW_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if ((prevent < 0) || (prevent > 3)) {
+        pr2ws("prevent argument should be 0, 1, 2 or 3\n");
+        return -1;
+    }
+    p_cdb[4] |= (prevent & 0x3);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < PREVENT_ALLOW_CMDLEN; ++k)
+            pr2ws("%02x ", p_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, p_cdb, sizeof(p_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+            ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
diff --git a/tools/sg_write_buffer/sg_cmds_extra.c b/tools/sg_write_buffer/sg_cmds_extra.c
new file mode 100644
index 0000000..bebc859
--- /dev/null
+++ b/tools/sg_write_buffer/sg_cmds_extra.c
@@ -0,0 +1,2524 @@
+/*
+ * Copyright (c) 1999-2018 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+
+#define DEF_PT_TIMEOUT 60       /* 60 seconds */
+#define LONG_PT_TIMEOUT 7200    /* 7,200 seconds == 120 minutes */
+
+#define SERVICE_ACTION_IN_16_CMD 0x9e
+#define SERVICE_ACTION_IN_16_CMDLEN 16
+#define SERVICE_ACTION_OUT_16_CMD 0x9f
+#define SERVICE_ACTION_OUT_16_CMDLEN 16
+#define MAINTENANCE_IN_CMD 0xa3
+#define MAINTENANCE_IN_CMDLEN 12
+#define MAINTENANCE_OUT_CMD 0xa4
+#define MAINTENANCE_OUT_CMDLEN 12
+
+#define ATA_PT_12_CMD 0xa1
+#define ATA_PT_12_CMDLEN 12
+#define ATA_PT_16_CMD 0x85
+#define ATA_PT_16_CMDLEN 16
+#define ATA_PT_32_SA 0x1ff0
+#define ATA_PT_32_CMDLEN 32
+#define FORMAT_UNIT_CMD 0x4
+#define FORMAT_UNIT_CMDLEN 6
+#define PERSISTENT_RESERVE_IN_CMD 0x5e
+#define PERSISTENT_RESERVE_IN_CMDLEN 10
+#define PERSISTENT_RESERVE_OUT_CMD 0x5f
+#define PERSISTENT_RESERVE_OUT_CMDLEN 10
+#define READ_BLOCK_LIMITS_CMD 0x5
+#define READ_BLOCK_LIMITS_CMDLEN 6
+#define READ_BUFFER_CMD 0x3c
+#define READ_BUFFER_CMDLEN 10
+#define READ_DEFECT10_CMD     0x37
+#define READ_DEFECT10_CMDLEN    10
+#define REASSIGN_BLKS_CMD     0x7
+#define REASSIGN_BLKS_CMDLEN  6
+#define RECEIVE_DIAGNOSTICS_CMD   0x1c
+#define RECEIVE_DIAGNOSTICS_CMDLEN  6
+#define THIRD_PARTY_COPY_OUT_CMD 0x83   /* was EXTENDED_COPY_CMD */
+#define THIRD_PARTY_COPY_OUT_CMDLEN 16
+#define THIRD_PARTY_COPY_IN_CMD 0x84     /* was RECEIVE_COPY_RESULTS_CMD */
+#define THIRD_PARTY_COPY_IN_CMDLEN 16
+#define SEND_DIAGNOSTIC_CMD   0x1d
+#define SEND_DIAGNOSTIC_CMDLEN  6
+#define SERVICE_ACTION_IN_12_CMD 0xab
+#define SERVICE_ACTION_IN_12_CMDLEN 12
+#define READ_LONG10_CMD 0x3e
+#define READ_LONG10_CMDLEN 10
+#define UNMAP_CMD 0x42
+#define UNMAP_CMDLEN 10
+#define VERIFY10_CMD 0x2f
+#define VERIFY10_CMDLEN 10
+#define VERIFY16_CMD 0x8f
+#define VERIFY16_CMDLEN 16
+#define WRITE_LONG10_CMD 0x3f
+#define WRITE_LONG10_CMDLEN 10
+#define WRITE_BUFFER_CMD 0x3b
+#define WRITE_BUFFER_CMDLEN 10
+#define PRE_FETCH10_CMD 0x34
+#define PRE_FETCH10_CMDLEN 10
+#define PRE_FETCH16_CMD 0x90
+#define PRE_FETCH16_CMDLEN 16
+#define SEEK10_CMD 0x2b
+#define SEEK10_CMDLEN 10
+
+#define GET_LBA_STATUS16_SA 0x12
+#define GET_LBA_STATUS32_SA 0x12
+#define READ_LONG_16_SA 0x11
+#define READ_MEDIA_SERIAL_NUM_SA 0x1
+#define REPORT_IDENTIFYING_INFORMATION_SA 0x5
+#define REPORT_TGT_PRT_GRP_SA 0xa
+#define SET_IDENTIFYING_INFORMATION_SA 0x6
+#define SET_TGT_PRT_GRP_SA 0xa
+#define WRITE_LONG_16_SA 0x11
+#define REPORT_REFERRALS_SA 0x13
+#define EXTENDED_COPY_LID1_SA 0x0
+
+#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;
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+    struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp)
+        pr2ws("%s: out of memory\n", cname);
+    return ptvp;
+}
+
+
+/* Invokes a SCSI GET LBA STATUS(16) command (SBC). Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt,
+                      void * resp, int alloc_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Get LBA status(16)";
+    int k, res, sense_cat, ret;
+    unsigned char getLbaStatCmd[SERVICE_ACTION_IN_16_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(getLbaStatCmd, 0, sizeof(getLbaStatCmd));
+    getLbaStatCmd[0] = SERVICE_ACTION_IN_16_CMD;
+    getLbaStatCmd[1] = GET_LBA_STATUS16_SA;
+
+    sg_put_unaligned_be64(start_llba, getLbaStatCmd + 2);
+    sg_put_unaligned_be32((uint32_t)alloc_len, getLbaStatCmd + 10);
+    getLbaStatCmd[14] = rt;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
+            pr2ws("%02x ", getLbaStatCmd[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, getLbaStatCmd, sizeof(getLbaStatCmd));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, alloc_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response\n", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+int
+sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp,
+                     int alloc_len, bool noisy, int verbose)
+{
+    return sg_ll_get_lba_status16(sg_fd, start_llba, /* rt = */ 0x0, resp,
+                                  alloc_len, noisy, verbose);
+}
+
+#define GLS32_CMD_LEN 32
+
+int
+sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len,
+                       uint32_t element_id, uint8_t rt,
+                       void * resp, int alloc_len, bool noisy,
+                       int verbose)
+{
+    static const char * const cdb_name_s = "Get LBA status(32)";
+    int k, res, sense_cat, ret;
+    unsigned char gls32_cmd[GLS32_CMD_LEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(gls32_cmd, 0, sizeof(gls32_cmd));
+    gls32_cmd[0] = SG_VARIABLE_LENGTH_CMD;
+    gls32_cmd[7] = GLS32_CMD_LEN - 8;
+    sg_put_unaligned_be16((uint16_t)GET_LBA_STATUS32_SA, gls32_cmd + 8);
+    gls32_cmd[10] = rt;
+    sg_put_unaligned_be64(start_llba, gls32_cmd + 12);
+    sg_put_unaligned_be32(scan_len, gls32_cmd + 20);
+    sg_put_unaligned_be32(element_id, gls32_cmd + 24);
+    sg_put_unaligned_be32((uint32_t)alloc_len, gls32_cmd + 28);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < GLS32_CMD_LEN; ++k)
+            pr2ws("%02x ", gls32_cmd[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, gls32_cmd, sizeof(gls32_cmd));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, alloc_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response\n", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+int
+sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len,
+                         bool noisy, int verbose)
+{
+    return sg_ll_report_tgt_prt_grp2(sg_fd, resp, mx_resp_len, false, noisy,
+                                     verbose);
+}
+
+/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len,
+                          bool extended, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Report target port groups";
+    int k, res, ret, sense_cat;
+    unsigned char rtpg_cdb[MAINTENANCE_IN_CMDLEN] =
+                         {MAINTENANCE_IN_CMD, REPORT_TGT_PRT_GRP_SA,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (extended)
+        rtpg_cdb[1] |= 0x20;
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rtpg_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k)
+            pr2ws("%02x ", rtpg_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rtpg_cdb, sizeof(rtpg_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy,
+                      int verbose)
+{
+    static const char * const cdb_name_s = "Set target port groups";
+    int k, res, ret, sense_cat;
+    unsigned char stpg_cdb[MAINTENANCE_OUT_CMDLEN] =
+                         {MAINTENANCE_OUT_CMD, SET_TGT_PRT_GRP_SA,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be32((uint32_t)param_len, stpg_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", stpg_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, stpg_cdb, sizeof(stpg_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg,
+                       void * resp, int mx_resp_len, bool noisy,
+                       int verbose)
+{
+    static const char * const cdb_name_s = "Report referrals";
+    int k, res, ret, sense_cat;
+    unsigned char repRef_cdb[SERVICE_ACTION_IN_16_CMDLEN] =
+                         {SERVICE_ACTION_IN_16_CMD, REPORT_REFERRALS_SA,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be64(start_llba, repRef_cdb + 2);
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, repRef_cdb + 10);
+    if (one_seg)
+        repRef_cdb[14] = 0x1;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
+            pr2ws("%02x ", repRef_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, repRef_cdb, sizeof(repRef_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* 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 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,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit,
+                bool devofl_bit, bool unitofl_bit, int long_duration,
+                void * paramp, int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Send diagnostic";
+    int k, res, ret, sense_cat, tmout;
+    unsigned char senddiag_cdb[SEND_DIAGNOSTIC_CMDLEN] =
+        {SEND_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    senddiag_cdb[1] = (unsigned char)(st_code << 5);
+    if (pf_bit)
+        senddiag_cdb[1] |= 0x10;
+    if (st_bit)
+        senddiag_cdb[1] |= 0x4;
+    if (devofl_bit)
+        senddiag_cdb[1] |= 0x2;
+    if (unitofl_bit)
+        senddiag_cdb[1] |= 0x1;
+    sg_put_unaligned_be16((uint16_t)param_len, senddiag_cdb + 3);
+    if (long_duration > LONG_PT_TIMEOUT)
+        tmout = long_duration;
+    else
+        tmout = long_duration ? LONG_PT_TIMEOUT : DEF_PT_TIMEOUT;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SEND_DIAGNOSTIC_CMDLEN; ++k)
+            pr2ws("%02x ", senddiag_cdb[k]);
+        pr2ws("\n");
+        if (verbose > 1) {
+            if (paramp && param_len) {
+                pr2ws("    %s parameter list:\n", cdb_name_s);
+                hex2stderr((const uint8_t *)paramp, param_len, -1);
+            }
+            pr2ws("    %s timeout: %d seconds\n", cdb_name_s, tmout);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp,
+                   int mx_resp_len, bool noisy, int verbose)
+{
+    return sg_ll_receive_diag_v2(sg_fd, pcv, pg_code, resp, mx_resp_len, 0,
+                                 NULL, noisy, verbose);
+}
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp,
+                      int mx_resp_len, int timeout_secs, int * residp,
+                      bool noisy, int verbose)
+{
+    int resid = 0;
+    int k, res, ret, sense_cat;
+    static const char * const cdb_name_s = "Receive diagnostic results";
+    struct sg_pt_base * ptvp;
+    unsigned char rcvdiag_cdb[RECEIVE_DIAGNOSTICS_CMDLEN] =
+        {RECEIVE_DIAGNOSTICS_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+
+    if (pcv)
+        rcvdiag_cdb[1] = 0x1;
+    rcvdiag_cdb[2] = (unsigned char)(pg_code);
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, rcvdiag_cdb + 3);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < RECEIVE_DIAGNOSTICS_CMDLEN; ++k)
+            pr2ws("%02x ", rcvdiag_cdb[k]);
+        pr2ws("\n");
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) {
+        if (residp)
+            *residp = 0;
+        return -1;
+    }
+    set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    resid = get_scsi_pt_resid(ptvp);
+    if (residp)
+        *residp = resid;
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                            -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, int dl_format,
+                    void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Read defect(10)";
+    int res, k, ret, sense_cat;
+    unsigned char rdef_cdb[READ_DEFECT10_CMDLEN] =
+        {READ_DEFECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    rdef_cdb[2] = (dl_format & 0x7);
+    if (req_plist)
+        rdef_cdb[2] |= 0x10;
+    if (req_glist)
+        rdef_cdb[2] |= 0x8;
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, rdef_cdb + 7);
+    if (mx_resp_len > 0xffff) {
+        pr2ws("mx_resp_len too big\n");
+        return -1;
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_DEFECT10_CMDLEN; ++k)
+            pr2ws("%02x ", rdef_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rdef_cdb, sizeof(rdef_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response\n", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len,
+                            bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Read media serial number";
+    int k, res, ret, sense_cat;
+    unsigned char rmsn_cdb[SERVICE_ACTION_IN_12_CMDLEN] =
+                         {SERVICE_ACTION_IN_12_CMD, READ_MEDIA_SERIAL_NUM_SA,
+                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rmsn_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_12_CMDLEN; ++k)
+            pr2ws("%02x ", rmsn_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rmsn_cdb, sizeof(rmsn_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was
+ * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len,
+                     bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Report identifying information";
+    int k, res, ret, sense_cat;
+    unsigned char rii_cdb[MAINTENANCE_IN_CMDLEN] = {MAINTENANCE_IN_CMD,
+                        REPORT_IDENTIFYING_INFORMATION_SA,
+                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be32((uint32_t)max_resp_len, rii_cdb + 6);
+    rii_cdb[10] |= (itype << 1) & 0xfe;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k)
+            pr2ws("%02x ", rii_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rii_cdb, sizeof(rii_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, max_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, max_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was
+ * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len,
+                  bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Set identifying information";
+    int k, res, ret, sense_cat;
+    unsigned char sii_cdb[MAINTENANCE_OUT_CMDLEN] = {MAINTENANCE_OUT_CMD,
+                         SET_IDENTIFYING_INFORMATION_SA,
+                         0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    sg_put_unaligned_be32((uint32_t)param_len, sii_cdb + 6);
+    sii_cdb[10] |= (itype << 1) & 0xfe;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", sii_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, sii_cdb, sizeof(sii_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                  bool cmplst, int dlist_format, int timeout_secs,
+                  void * paramp, int param_len, bool noisy, int verbose)
+{
+    return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst,
+                                dlist_format, 0, timeout_secs, paramp,
+                                param_len, noisy, verbose);
+}
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                   bool cmplst, int dlist_format, int ffmt, int timeout_secs,
+                   void * paramp, int param_len, bool noisy, int verbose)
+{
+    return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst,
+                                dlist_format, ffmt, timeout_secs, paramp,
+                                param_len, noisy, verbose);
+}
+
+/* Invokes a FORMAT UNIT (SBC-4) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * FFMT field added in sbc4r10 [20160121] */
+int
+sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+                     bool cmplst, int dlist_format, int ffmt,
+                     int timeout_secs, void * paramp, int param_len,
+                     bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Format unit";
+    int k, res, ret, sense_cat, tmout;
+    unsigned char fu_cdb[FORMAT_UNIT_CMDLEN] =
+                {FORMAT_UNIT_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (fmtpinfo)
+        fu_cdb[1] |= (fmtpinfo << 6);
+    if (longlist)
+        fu_cdb[1] |= 0x20;
+    if (fmtdata)
+        fu_cdb[1] |= 0x10;
+    if (cmplst)
+        fu_cdb[1] |= 0x8;
+    if (dlist_format)
+        fu_cdb[1] |= (dlist_format & 0x7);
+    if (ffmt)
+        fu_cdb[4] |= (ffmt & 0x3);
+    tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < 6; ++k)
+            pr2ws("%02x ", fu_cdb[k]);
+        pr2ws("\n");
+        if (verbose > 1) {
+            if (param_len > 0) {
+                pr2ws("    %s parameter list:\n", cdb_name_s);
+                hex2stderr((const uint8_t *)paramp, param_len, -1);
+            }
+            pr2ws("    %s timeout: %d seconds\n", cdb_name_s, tmout);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, fu_cdb, sizeof(fu_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI REASSIGN BLOCKS command.  Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, void * paramp,
+                      int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Reassign blocks";
+    int res, k, ret, sense_cat;
+    unsigned char reass_cdb[REASSIGN_BLKS_CMDLEN] =
+        {REASSIGN_BLKS_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (longlba)
+        reass_cdb[1] = 0x2;
+    if (longlist)
+        reass_cdb[1] |= 0x1;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < REASSIGN_BLKS_CMDLEN; ++k)
+            pr2ws("%02x ", reass_cdb[k]);
+        pr2ws("\n");
+    }
+    if (verbose > 1) {
+        pr2ws("    %s parameter list\n", cdb_name_s);
+        hex2stderr((const uint8_t *)paramp, param_len, -1);
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, reass_cdb, sizeof(reass_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0
+ * when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp,
+                            int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Persistent reservation in";
+    int res, k, ret, sense_cat;
+    unsigned char prin_cdb[PERSISTENT_RESERVE_IN_CMDLEN] =
+                 {PERSISTENT_RESERVE_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (rq_servact > 0)
+        prin_cdb[1] = (unsigned char)(rq_servact & 0x1f);
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, prin_cdb + 7);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < PERSISTENT_RESERVE_IN_CMDLEN; ++k)
+            pr2ws("%02x ", prin_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, prin_cdb, sizeof(prin_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0
+ * when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope,
+                             unsigned int rq_type, void * paramp,
+                             int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "Persistent reservation out";
+    int res, k, ret, sense_cat;
+    unsigned char prout_cdb[PERSISTENT_RESERVE_OUT_CMDLEN] =
+                 {PERSISTENT_RESERVE_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (rq_servact > 0)
+        prout_cdb[1] = (unsigned char)(rq_servact & 0x1f);
+    prout_cdb[2] = (((rq_scope & 0xf) << 4) | (rq_type & 0xf));
+    sg_put_unaligned_be16((uint16_t)param_len, prout_cdb + 7);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < PERSISTENT_RESERVE_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", prout_cdb[k]);
+        pr2ws("\n");
+        if (verbose > 1) {
+            pr2ws("    %s parameters:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, 0);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, prout_cdb, sizeof(prout_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+static bool
+has_blk_ili(unsigned char * sensep, int sb_len)
+{
+    int resp_code;
+    const unsigned char * cup;
+
+    if (sb_len < 8)
+        return false;
+    resp_code = (0x7f & sensep[0]);
+    if (resp_code >= 0x72) { /* descriptor format */
+        /* find block command descriptor */
+        if ((cup = sg_scsi_sense_desc_find(sensep, sb_len, 0x5)))
+            return (cup[3] & 0x20);
+    } else /* fixed */
+        return (sensep[2] & 0x20);
+    return false;
+}
+
+/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba,
+                  void * resp, int xfer_len, int * offsetp, bool noisy,
+                  int verbose)
+{
+    static const char * const cdb_name_s = "read long(10)";
+    int k, res, sense_cat, ret;
+    unsigned char readLong_cdb[READ_LONG10_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(readLong_cdb, 0, READ_LONG10_CMDLEN);
+    readLong_cdb[0] = READ_LONG10_CMD;
+    if (pblock)
+        readLong_cdb[1] |= 0x4;
+    if (correct)
+        readLong_cdb[1] |= 0x2;
+
+    sg_put_unaligned_be32((uint32_t)lba, readLong_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_LONG10_CMDLEN; ++k)
+            pr2ws("%02x ", readLong_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, xfer_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_ILLEGAL_REQ:
+            {
+                bool valid, ili;
+                int slen;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                ili = has_blk_ili(sense_b, slen);
+                if (valid && ili) {
+                    if (offsetp)
+                        *offsetp = (int)(int64_t)ull;
+                    ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+                } else {
+                    if (verbose > 1)
+                        pr2ws("  info field: 0x%" PRIx64 ",  valid: %d, "
+                              "ili: %d\n", ull, valid, ili);
+                    ret = SG_LIB_CAT_ILLEGAL_REQ;
+                }
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba,
+                  void * resp, int xfer_len, int * offsetp, bool noisy,
+                  int verbose)
+{
+    static const char * const cdb_name_s = "read long(16)";
+    int k, res, sense_cat, ret;
+    unsigned char readLong_cdb[SERVICE_ACTION_IN_16_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(readLong_cdb, 0, sizeof(readLong_cdb));
+    readLong_cdb[0] = SERVICE_ACTION_IN_16_CMD;
+    readLong_cdb[1] = READ_LONG_16_SA;
+    if (pblock)
+        readLong_cdb[14] |= 0x2;
+    if (correct)
+        readLong_cdb[14] |= 0x1;
+
+    sg_put_unaligned_be64(llba, readLong_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 12);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
+            pr2ws("%02x ", readLong_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, xfer_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_ILLEGAL_REQ:
+            {
+                bool valid, ili;
+                int slen;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                ili = has_blk_ili(sense_b, slen);
+                if (valid && ili) {
+                    if (offsetp)
+                        *offsetp = (int)(int64_t)ull;
+                    ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+                } else {
+                    if (verbose > 1)
+                        pr2ws("  info field: 0x%" PRIx64 ",  valid: %d, "
+                              "ili: %d\n", ull, (int)valid, (int)ili);
+                    ret = SG_LIB_CAT_ILLEGAL_REQ;
+                }
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+                   unsigned int lba, void * data_out, int xfer_len,
+                   int * offsetp, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "write long(10)";
+    int k, res, sense_cat, ret;
+    unsigned char writeLong_cdb[WRITE_LONG10_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(writeLong_cdb, 0, WRITE_LONG10_CMDLEN);
+    writeLong_cdb[0] = WRITE_LONG10_CMD;
+    if (cor_dis)
+        writeLong_cdb[1] |= 0x80;
+    if (wr_uncor)
+        writeLong_cdb[1] |= 0x40;
+    if (pblock)
+        writeLong_cdb[1] |= 0x20;
+
+    sg_put_unaligned_be32((uint32_t)lba, writeLong_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < (int)sizeof(writeLong_cdb); ++k)
+            pr2ws("%02x ", writeLong_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, xfer_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret)
+        ;
+    else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_ILLEGAL_REQ:
+            {
+                int valid, slen, ili;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                ili = has_blk_ili(sense_b, slen);
+                if (valid && ili) {
+                    if (offsetp)
+                        *offsetp = (int)(int64_t)ull;
+                    ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+                } else {
+                    if (verbose > 1)
+                        pr2ws("  info field: 0x%" PRIx64 ",  valid: %d, "
+                              "ili: %d\n", ull, (int)valid, (int)ili);
+                    ret = SG_LIB_CAT_ILLEGAL_REQ;
+                }
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+                   uint64_t llba, void * data_out, int xfer_len,
+                   int * offsetp, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "write long(16)";
+    int k, res, sense_cat, ret;
+    unsigned char writeLong_cdb[SERVICE_ACTION_OUT_16_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(writeLong_cdb, 0, sizeof(writeLong_cdb));
+    writeLong_cdb[0] = SERVICE_ACTION_OUT_16_CMD;
+    writeLong_cdb[1] = WRITE_LONG_16_SA;
+    if (cor_dis)
+        writeLong_cdb[1] |= 0x80;
+    if (wr_uncor)
+        writeLong_cdb[1] |= 0x40;
+    if (pblock)
+        writeLong_cdb[1] |= 0x20;
+
+    sg_put_unaligned_be64(llba, writeLong_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 12);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SERVICE_ACTION_OUT_16_CMDLEN; ++k)
+            pr2ws("%02x ", writeLong_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, xfer_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_ILLEGAL_REQ:
+            {
+                bool valid, ili;
+                int slen;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                ili = has_blk_ili(sense_b, slen);
+                if (valid && ili) {
+                    if (offsetp)
+                        *offsetp = (int)(int64_t)ull;
+                    ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+                } else {
+                    if (verbose > 1)
+                        pr2ws("  info field: 0x%" PRIx64 ",  valid: %d, "
+                              "ili: %d\n", ull, (int)valid, (int)ili);
+                    ret = SG_LIB_CAT_ILLEGAL_REQ;
+                }
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI VERIFY (10) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success, * various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytchk,
+               unsigned int lba, int veri_len, void * data_out,
+               int data_out_len, unsigned int * infop, bool noisy,
+               int verbose)
+{
+    static const char * const cdb_name_s = "verify(10)";
+    int k, res, ret, sense_cat, slen;
+    unsigned char v_cdb[VERIFY10_CMDLEN] =
+                {VERIFY10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */
+    v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ;
+    if (dpo)
+        v_cdb[1] |= 0x10;
+    sg_put_unaligned_be32((uint32_t)lba, v_cdb + 2);
+    sg_put_unaligned_be16((uint16_t)veri_len, v_cdb + 7);
+    if (verbose > 1) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < VERIFY10_CMDLEN; ++k)
+            pr2ws("%02x ", v_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) {
+            k = data_out_len > 4104 ? 4104 : data_out_len;
+            pr2ws("    data_out buffer%s\n",
+                  (data_out_len > 4104 ? ", first 4104 bytes" : ""));
+            hex2stderr((const uint8_t *)data_out, k, verbose < 5);
+        }
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (data_out_len > 0)
+        set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, data_out_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_MEDIUM_HARD:
+            {
+                bool valid;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                if (valid) {
+                    if (infop)
+                        *infop = (unsigned int)ull;
+                    ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO;
+                } else
+                    ret = SG_LIB_CAT_MEDIUM_HARD;
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI VERIFY (16) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytchk, uint64_t llba,
+               int veri_len, int group_num, void * data_out,
+               int data_out_len, uint64_t * infop, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "verify(16)";
+    int k, res, ret, sense_cat, slen;
+    unsigned char v_cdb[VERIFY16_CMDLEN] =
+                {VERIFY16_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */
+    v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ;
+    if (dpo)
+        v_cdb[1] |= 0x10;
+    sg_put_unaligned_be64(llba, v_cdb + 2);
+    sg_put_unaligned_be32((uint32_t)veri_len, v_cdb + 10);
+    v_cdb[14] = group_num & 0x1f;
+    if (verbose > 1) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < VERIFY16_CMDLEN; ++k)
+            pr2ws("%02x ", v_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) {
+            k = data_out_len > 4104 ? 4104 : data_out_len;
+            pr2ws("    data_out buffer%s\n",
+                  (data_out_len > 4104 ? ", first 4104 bytes" : ""));
+            hex2stderr((const uint8_t *)data_out, k, verbose < 5);
+        }
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (data_out_len > 0)
+        set_scsi_pt_data_out(ptvp, (unsigned char *)data_out, data_out_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        case SG_LIB_CAT_MEDIUM_HARD:
+            {
+                bool valid;
+                uint64_t ull = 0;
+
+                slen = get_scsi_pt_sense_len(ptvp);
+                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+                if (valid) {
+                    if (infop)
+                        *infop = ull;
+                    ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO;
+                } else
+                    ret = SG_LIB_CAT_MEDIUM_HARD;
+            }
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is
+ * selected by the cdb_len argument that can take values of 12, 16 or 32
+ * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9
+ * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from
+ * the control byte, the rest is copied into an internal cdb which is then
+ * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15
+ * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the
+ * timeout is set to 60 seconds. For data in or out transfers set dinp or
+ * doutp, and dlen to the number of bytes to transfer. If dlen is zero then
+ * no data transfer is assumed. If sense buffer obtained then it is written
+ * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is
+ * obtained then written to ata_return_dp, else ata_return_dp[0] is set to
+ * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers.
+ * Returns SCSI status value (>= 0) or -1 if other error. Users are
+ * expected to check the sense buffer themselves. If available the data in
+ * resid is written to residp. Note in SAT-2 and later, fixed format sense
+ * data may be placed in *sensep in which case sensep[0]==0x70, prior to
+ * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72).
+ */
+int
+sg_ll_ata_pt(int sg_fd, const unsigned char * cdbp, int cdb_len,
+             int timeout_secs, void * dinp, void * doutp, int dlen,
+             unsigned char * sensep, int max_sense_len,
+             unsigned char * ata_return_dp, int max_ata_return_len,
+             int * residp, int verbose)
+{
+    int k, res, slen, duration;
+    int ret = -1;
+    unsigned char apt_cdb[ATA_PT_32_CMDLEN];
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    unsigned char * sp;
+    const unsigned char * bp;
+    struct sg_pt_base * ptvp;
+    const char * cnamep;
+    char b[256];
+
+    memset(apt_cdb, 0, sizeof(apt_cdb));
+    b[0] = '\0';
+    switch (cdb_len) {
+    case 12:
+        cnamep = "ATA pass-through(12)";
+        apt_cdb[0] = ATA_PT_12_CMD;
+        memcpy(apt_cdb + 1, cdbp + 1,  10);
+        /* control byte at cdb[11] left at zero */
+        break;
+    case 16:
+        cnamep = "ATA pass-through(16)";
+        apt_cdb[0] = ATA_PT_16_CMD;
+        memcpy(apt_cdb + 1, cdbp + 1,  14);
+        /* control byte at cdb[15] left at zero */
+        break;
+    case 32:
+        cnamep = "ATA pass-through(32)";
+        apt_cdb[0] = SG_VARIABLE_LENGTH_CMD;
+        /* control byte at cdb[1] left at zero */
+        apt_cdb[7] = 0x18;    /* length starting at next byte */
+        sg_put_unaligned_be16(ATA_PT_32_SA, apt_cdb + 8);
+        memcpy(apt_cdb + 10, cdbp + 10,  32 - 10);
+        break;
+    default:
+        pr2ws("cdb_len must be 12, 16 or 32\n");
+        return -1;
+    }
+    if (NULL == cdbp) {
+        if (verbose)
+            pr2ws("%s NULL cdb pointer\n", cnamep);
+        return -1;
+    }
+    if (sensep && (max_sense_len >= (int)sizeof(sense_b))) {
+        sp = sensep;
+        slen = max_sense_len;
+    } else {
+        sp = sense_b;
+        slen = sizeof(sense_b);
+    }
+    if (verbose) {
+        pr2ws("    %s cdb: ", cnamep);
+        if (cdb_len < 32) {
+            for (k = 0; k < cdb_len; ++k)
+                pr2ws("%02x ", apt_cdb[k]);
+            pr2ws("\n");
+        } else {
+            pr2ws("\n");
+            hex2stderr(apt_cdb, cdb_len, -1);
+        }
+    }
+    if (NULL == ((ptvp = create_pt_obj(cnamep))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, apt_cdb, cdb_len);
+    set_scsi_pt_sense(ptvp, sp, slen);
+    if (dlen > 0) {
+        if (dinp)
+            set_scsi_pt_data_in(ptvp, (unsigned char *)dinp, dlen);
+        else if (doutp)
+            set_scsi_pt_data_out(ptvp, (unsigned char *)doutp, dlen);
+    }
+    res = do_scsi_pt(ptvp, sg_fd,
+                     ((timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT),
+                     verbose);
+    if (SCSI_PT_DO_BAD_PARAMS == res) {
+        if (verbose)
+            pr2ws("%s: bad parameters\n", cnamep);
+        goto out;
+    } else if (SCSI_PT_DO_TIMEOUT == res) {
+        if (verbose)
+            pr2ws("%s: timeout\n", cnamep);
+        goto out;
+    } else if (res > 2) {
+        if (verbose)
+            pr2ws("%s: do_scsi_pt: errno=%d\n", cnamep, -res);
+    }
+
+    if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0))
+        pr2ws("      duration=%d ms\n", duration);
+
+    switch (get_scsi_pt_result_category(ptvp)) {
+    case SCSI_PT_RESULT_GOOD:
+        if ((sensep) && (max_sense_len > 0))
+            *sensep = 0;
+        if ((ata_return_dp) && (max_ata_return_len > 0))
+            *ata_return_dp = 0;
+        if (residp && (dlen > 0))
+            *residp = get_scsi_pt_resid(ptvp);
+        ret = 0;
+        break;
+    case SCSI_PT_RESULT_STATUS: /* other than GOOD + CHECK CONDITION */
+        if ((sensep) && (max_sense_len > 0))
+            *sensep = 0;
+        if ((ata_return_dp) && (max_ata_return_len > 0))
+            *ata_return_dp = 0;
+        ret = get_scsi_pt_status_response(ptvp);
+        break;
+    case SCSI_PT_RESULT_SENSE:
+        if (sensep && (sp != sensep)) {
+            k = get_scsi_pt_sense_len(ptvp);
+            k = (k > max_sense_len) ? max_sense_len : k;
+            memcpy(sensep, sp, k);
+        }
+        if (ata_return_dp && (max_ata_return_len > 0))  {
+            /* search for ATA return descriptor */
+            bp = sg_scsi_sense_desc_find(sp, slen, 0x9);
+            if (bp) {
+                k = bp[1] + 2;
+                k = (k > max_ata_return_len) ? max_ata_return_len : k;
+                memcpy(ata_return_dp, bp, k);
+            } else
+                ata_return_dp[0] = 0x0;
+        }
+        if (residp && (dlen > 0))
+            *residp = get_scsi_pt_resid(ptvp);
+        ret = get_scsi_pt_status_response(ptvp);
+        break;
+    case SCSI_PT_RESULT_TRANSPORT_ERR:
+        if (verbose)
+            pr2ws("%s: transport error: %s\n", cnamep,
+                  get_scsi_pt_transport_err_str(ptvp, sizeof(b), b));
+        break;
+    case SCSI_PT_RESULT_OS_ERR:
+        if (verbose)
+            pr2ws("%s: os error: %s\n", cnamep,
+                  get_scsi_pt_os_err_str(ptvp, sizeof(b) , b));
+        break;
+    default:
+        if (verbose)
+            pr2ws("%s: unknown pt_result_category=%d\n", cnamep,
+                  get_scsi_pt_result_category(ptvp));
+        break;
+    }
+
+out:
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ BUFFER(10) command (SPC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+                  void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "read buffer(10)";
+    int res, k, ret, sense_cat;
+    unsigned char rbuf_cdb[READ_BUFFER_CMDLEN] =
+        {READ_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    rbuf_cdb[1] = (unsigned char)(mode & 0x1f);
+    rbuf_cdb[2] = (unsigned char)(buffer_id & 0xff);
+    sg_put_unaligned_be24((uint32_t)buffer_offset, rbuf_cdb + 3);
+    sg_put_unaligned_be24((uint32_t)mx_resp_len, rbuf_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_BUFFER_CMDLEN; ++k)
+            pr2ws("%02x ", rbuf_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rbuf_cdb, sizeof(rbuf_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+                   void * paramp, int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "write buffer";
+    int k, res, ret, sense_cat;
+    unsigned char wbuf_cdb[WRITE_BUFFER_CMDLEN] =
+        {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    wbuf_cdb[1] = (unsigned char)(mode & 0x1f);
+    wbuf_cdb[2] = (unsigned char)(buffer_id & 0xff);
+    sg_put_unaligned_be24((uint32_t)buffer_offset, wbuf_cdb + 3);
+    sg_put_unaligned_be24((uint32_t)param_len, wbuf_cdb + 6);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k)
+            pr2ws("%02x ", wbuf_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list", cdb_name_s);
+            if (2 == verbose) {
+                pr2ws("%s:\n", (param_len > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)paramp,
+                           (param_len > 256 ? 256 : param_len), -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)paramp, param_len, 0);
+            }
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure. Adds mode specific field (spc4r32) and timeout
+ *  to command abort to override default of 60 seconds. If timeout_secs is
+ *  0 or less then the default timeout is used instead. */
+int
+sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id,
+                      uint32_t buffer_offset, void * paramp,
+                      uint32_t param_len, int timeout_secs, bool noisy,
+                      int verbose)
+{
+    int k, res, ret, sense_cat;
+    uint8_t wbuf_cdb[WRITE_BUFFER_CMDLEN] =
+        {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    uint8_t sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (buffer_offset > 0xffffff) {
+        pr2ws("%s: buffer_offset value too large for 24 bits\n", __func__);
+        return -1;
+    }
+    if (param_len > 0xffffff) {
+        pr2ws("%s: param_len value too large for 24 bits\n", __func__);
+        return -1;
+    }
+    wbuf_cdb[1] = (uint8_t)(mode & 0x1f);
+    wbuf_cdb[1] |= (uint8_t)((m_specific & 0x7) << 5);
+    wbuf_cdb[2] = (uint8_t)(buffer_id & 0xff);
+    sg_put_unaligned_be24(buffer_offset, wbuf_cdb + 3);
+    sg_put_unaligned_be24(param_len, wbuf_cdb + 6);
+    if (verbose) {
+        pr2ws("    Write buffer cdb: ");
+        for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k)
+            pr2ws("%02x ", wbuf_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    Write buffer parameter list%s:\n",
+                  ((param_len > 256) ? " (first 256 bytes)" : ""));
+            hex2stderr((const uint8_t *)paramp,
+                       ((param_len > 256) ? 256 : param_len), -1);
+        }
+    }
+    if (timeout_secs <= 0)
+        timeout_secs = DEF_PT_TIMEOUT;
+
+    ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp) {
+        pr2ws("%s: out of memory\n", __func__);
+        return -1;
+    }
+    set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+    ret = sg_cmds_process_resp(ptvp, "Write buffer", res, SG_NO_DATA_IN,
+                               sense_b, noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI UNMAP command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp,
+            int param_len, bool noisy, int verbose)
+{
+    return sg_ll_unmap_v2(sg_fd, false, group_num, timeout_secs, paramp,
+                          param_len, noisy, verbose);
+}
+
+/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field
+ * (sbc3r22). Otherwise same as sg_ll_unmap() . */
+int
+sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs,
+               void * paramp, int param_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "unmap";
+    int k, res, ret, sense_cat, tmout;
+    unsigned char u_cdb[UNMAP_CMDLEN] =
+                         {UNMAP_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (anchor)
+        u_cdb[1] |= 0x1;
+    tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+    u_cdb[6] = group_num & 0x1f;
+    sg_put_unaligned_be16((uint16_t)param_len, u_cdb + 7);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < UNMAP_CMDLEN; ++k)
+            pr2ws("%02x ", u_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, u_cdb, sizeof(u_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len,
+                        bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "read block limits";
+    int k, ret, res, sense_cat;
+    unsigned char rl_cdb[READ_BLOCK_LIMITS_CMDLEN] =
+      {READ_BLOCK_LIMITS_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < READ_BLOCK_LIMITS_CMDLEN; ++k)
+            pr2ws("%02x ", rl_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 0)) {
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, ret, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI RECEIVE COPY RESULTS command. Actually cover all current
+ * uses of opcode 0x84 (Third-party copy IN). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp,
+                           int mx_resp_len, bool noisy, int verbose)
+{
+    int k, res, ret, sense_cat;
+    unsigned char rcvcopyres_cdb[THIRD_PARTY_COPY_IN_CMDLEN] =
+      {THIRD_PARTY_COPY_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+    char b[64];
+
+    sg_get_opcode_sa_name(THIRD_PARTY_COPY_IN_CMD, sa, 0, (int)sizeof(b), b);
+    rcvcopyres_cdb[1] = (unsigned char)(sa & 0x1f);
+    if (sa <= 4)        /* LID1 variants */
+        rcvcopyres_cdb[2] = (unsigned char)(list_id);
+    else if ((sa >= 5) && (sa <= 7))    /* LID4 variants */
+        sg_put_unaligned_be32((uint32_t)list_id, rcvcopyres_cdb + 2);
+    sg_put_unaligned_be32((uint32_t)mx_resp_len, rcvcopyres_cdb + 10);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", b);
+        for (k = 0; k < THIRD_PARTY_COPY_IN_CMDLEN; ++k)
+            pr2ws("%02x ", rcvcopyres_cdb[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(b))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, rcvcopyres_cdb, sizeof(rcvcopyres_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, b, res, mx_resp_len, sense_b, noisy,
+                               verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+
+/* SPC-4 rev 35 and later calls this opcode (0x83) "Third-party copy OUT"
+ * The original EXTENDED COPY command (now called EXTENDED COPY (LID1))
+ * is the only one supported by sg_ll_extended_copy(). See function
+ * sg_ll_3party_copy_out() for the other service actions ( > 0 ). */
+
+/* Invokes a SCSI EXTENDED COPY (LID1) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy,
+                    int verbose)
+{
+    int k, res, ret, sense_cat;
+    unsigned char xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] =
+      {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+    const char * opcode_name = "Extended copy (LID1)";
+
+    xcopy_cdb[1] = (unsigned char)(EXTENDED_COPY_LID1_SA & 0x1f);
+    sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", opcode_name);
+        for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", xcopy_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", opcode_name);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(opcode_name))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, opcode_name, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Handles various service actions associated with opcode 0x83 which is
+ * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID1 and
+ * LID4), POPULATE TOKEN and WRITE USING TOKEN commands.
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, int group_num,
+                      int timeout_secs, void * paramp, int param_len,
+                      bool noisy, int verbose)
+{
+    int k, res, ret, sense_cat, tmout;
+    unsigned char xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] =
+      {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+    char cname[80];
+
+    sg_get_opcode_sa_name(THIRD_PARTY_COPY_OUT_CMD, sa, 0, sizeof(cname),
+                          cname);
+    xcopy_cdb[1] = (unsigned char)(sa & 0x1f);
+    switch (sa) {
+    case 0x0:   /* XCOPY(LID1) */
+    case 0x1:   /* XCOPY(LID4) */
+        sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+        break;
+    case 0x10:  /* POPULATE TOKEN (SBC-3) */
+    case 0x11:  /* WRITE USING TOKEN (SBC-3) */
+        sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 6);
+        sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+        xcopy_cdb[14] = (unsigned char)(group_num & 0x1f);
+        break;
+    case 0x1c:  /* COPY OPERATION ABORT */
+        sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 2);
+        break;
+    default:
+        pr2ws("%s: unknown service action 0x%x\n", __func__, sa);
+        return -1;
+    }
+    tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cname);
+        for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k)
+            pr2ws("%02x ", xcopy_cdb[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cname);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cname))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    ret = sg_cmds_process_resp(ptvp, cname, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC).
+ * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_*
+ * positive values or -1 -> other errors. Note that CONDITION MET status
+ * is returned when immed=true and num_blocks can fit in device's cache,
+ * somewaht strangely, GOOD status (return 0) is returned if num_blocks
+ * cannot fit in device's cache. If do_seek10==true then does a SEEK(10)
+ * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10)
+ * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then
+ * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */
+int
+sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed,
+                  uint64_t lba, uint32_t num_blocks, int group_num,
+                  int timeout_secs, bool noisy, int verbose)
+{
+    static const char * const cdb10_name_s = "Pre-fetch(10)";
+    static const char * const cdb16_name_s = "Pre-fetch(16)";
+    static const char * const cdb_seek_name_s = "Seek(10)";
+    int k, res, sense_cat, ret, cdb_len, tmout;
+    const char *cdb_name_s;
+    unsigned char preFetchCdb[PRE_FETCH16_CMDLEN]; /* all use longest cdb */
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    memset(preFetchCdb, 0, sizeof(preFetchCdb));
+    if (do_seek10) {
+        if (lba > UINT32_MAX) {
+            if (verbose)
+                pr2ws("%s: LBA exceeds 2**32 in %s\n", __func__,
+                      cdb_seek_name_s);
+            return -1;
+        }
+        preFetchCdb[0] = SEEK10_CMD;
+        cdb_len = SEEK10_CMDLEN;
+        cdb_name_s = cdb_seek_name_s;
+        sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2);
+    } else {
+        if ((! cdb16) &&
+            ((lba > UINT32_MAX) || (num_blocks > UINT16_MAX))) {
+            cdb16 = true;
+            if (noisy || verbose)
+                pr2ws("%s: do %s due to %s size\n", __func__, cdb16_name_s,
+                      (lba > UINT32_MAX) ? "LBA" : "NUM_BLOCKS");
+        }
+        if (cdb16) {
+            preFetchCdb[0] = PRE_FETCH16_CMD;
+            cdb_len = PRE_FETCH16_CMDLEN;
+            cdb_name_s = cdb16_name_s;
+            if (immed)
+                preFetchCdb[1] = 0x2;
+            sg_put_unaligned_be64(lba, preFetchCdb + 2);
+            sg_put_unaligned_be32(num_blocks, preFetchCdb + 10);
+            preFetchCdb[14] = 0x3f & group_num;
+        } else {
+            preFetchCdb[0] = PRE_FETCH10_CMD;
+            cdb_len = PRE_FETCH10_CMDLEN;
+            cdb_name_s = cdb10_name_s;
+            if (immed)
+                preFetchCdb[1] = 0x2;
+            sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2);
+            preFetchCdb[6] = 0x3f & group_num;
+            sg_put_unaligned_be16((uint16_t)num_blocks, preFetchCdb + 7);
+        }
+    }
+    tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < cdb_len; ++k)
+            pr2ws("%02x ", preFetchCdb[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, preFetchCdb, cdb_len);
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, tmout, verbose);
+    if (0 == res) {
+        int sstat = get_scsi_pt_status_response(ptvp);
+
+        if (SG_LIB_CAT_CONDITION_MET == sstat) {
+            ret = SG_LIB_CAT_CONDITION_MET;
+            if (verbose > 2)
+                pr2ws("%s: returns SG_LIB_CAT_CONDITION_MET\n", __func__);
+            goto fini;
+        }
+    }
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = sense_cat;
+            break;
+        }
+    } else
+        ret = 0;
+fini:
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
diff --git a/tools/sg_write_buffer/sg_cmds_mmc.c b/tools/sg_write_buffer/sg_cmds_mmc.c
new file mode 100644
index 0000000..18f6ae1
--- /dev/null
+++ b/tools/sg_write_buffer/sg_cmds_mmc.c
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2008-2018 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_mmc.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+
+#define DEF_PT_TIMEOUT 60       /* 60 seconds */
+
+#define GET_CONFIG_CMD 0x46
+#define GET_CONFIG_CMD_LEN 10
+#define GET_PERFORMANCE_CMD 0xac
+#define GET_PERFORMANCE_CMD_LEN 12
+#define SET_CD_SPEED_CMD 0xbb
+#define SET_CD_SPEED_CMDLEN 12
+#define SET_STREAMING_CMD 0xb6
+#define SET_STREAMING_CMDLEN 12
+
+#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;
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+    struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp)
+        pr2ws("%s: out of memory\n", cname);
+    return ptvp;
+}
+
+/* Invokes a SCSI SET CD SPEED command (MMC).
+ * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int
+sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed,
+                   int drv_write_speed, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "set cd speed";
+    int res, ret, k, sense_cat;
+    unsigned char scsCmdBlk[SET_CD_SPEED_CMDLEN] = {SET_CD_SPEED_CMD, 0,
+                                         0, 0, 0, 0, 0, 0, 0, 0, 0 ,0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    scsCmdBlk[1] |= (rot_control & 0x3);
+    sg_put_unaligned_be16((uint16_t)drv_read_speed, scsCmdBlk + 2);
+    sg_put_unaligned_be16((uint16_t)drv_write_speed, scsCmdBlk + 4);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SET_CD_SPEED_CMDLEN; ++k)
+            pr2ws("%02x ", scsCmdBlk[k]);
+        pr2ws("\n");
+    }
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, scsCmdBlk, sizeof(scsCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_NOT_READY:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else
+        ret = 0;
+
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI GET CONFIGURATION command (MMC-3,4,5).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int
+sg_ll_get_config(int sg_fd, int rt, int starting, void * resp,
+                 int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "get configuration";
+    int res, k, ret, sense_cat;
+    unsigned char gcCmdBlk[GET_CONFIG_CMD_LEN] = {GET_CONFIG_CMD, 0, 0, 0,
+                                                  0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if ((rt < 0) || (rt > 3)) {
+        pr2ws("Bad rt value: %d\n", rt);
+        return -1;
+    }
+    gcCmdBlk[1] = (rt & 0x3);
+    if ((starting < 0) || (starting > 0xffff)) {
+        pr2ws("Bad starting field number: 0x%x\n", starting);
+        return -1;
+    }
+    sg_put_unaligned_be16((uint16_t)starting, gcCmdBlk + 2);
+    if ((mx_resp_len < 0) || (mx_resp_len > 0xffff)) {
+        pr2ws("Bad mx_resp_len: 0x%x\n", starting);
+        return -1;
+    }
+    sg_put_unaligned_be16((uint16_t)mx_resp_len, gcCmdBlk + 7);
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < GET_CONFIG_CMD_LEN; ++k)
+            pr2ws("%02x ", gcCmdBlk[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, gcCmdBlk, sizeof(gcCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len,
+                               sense_b, noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 3)) {
+            unsigned char * bp;
+            int len;
+
+            bp = (unsigned char *)resp;
+            len = sg_get_unaligned_be32(bp + 0);
+            if (len < 0)
+                len = 0;
+            len = (ret < len) ? ret : len;
+            pr2ws("    %s: response:\n", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, len, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int
+sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba,
+                      int max_num_desc, int ttype, void * resp,
+                      int mx_resp_len, bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "get performance";
+    int res, k, ret, sense_cat;
+    unsigned char gpCmdBlk[GET_PERFORMANCE_CMD_LEN] = {GET_PERFORMANCE_CMD, 0,
+                                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    if ((data_type < 0) || (data_type > 0x1f)) {
+        pr2ws("Bad data_type value: %d\n", data_type);
+        return -1;
+    }
+    gpCmdBlk[1] = (data_type & 0x1f);
+    sg_put_unaligned_be32((uint32_t)starting_lba, gpCmdBlk + 2);
+    if ((max_num_desc < 0) || (max_num_desc > 0xffff)) {
+        pr2ws("Bad max_num_desc: 0x%x\n", max_num_desc);
+        return -1;
+    }
+    sg_put_unaligned_be16((uint16_t)max_num_desc, gpCmdBlk + 8);
+    if ((ttype < 0) || (ttype > 0xff)) {
+        pr2ws("Bad type: 0x%x\n", ttype);
+        return -1;
+    }
+    gpCmdBlk[10] = (unsigned char)ttype;
+
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < GET_PERFORMANCE_CMD_LEN; ++k)
+            pr2ws("%02x ", gpCmdBlk[k]);
+        pr2ws("\n");
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, gpCmdBlk, sizeof(gpCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else {
+        if ((verbose > 2) && (ret > 3)) {
+            unsigned char * bp;
+            int len;
+
+            bp = (unsigned char *)resp;
+            len = sg_get_unaligned_be32(bp + 0);
+            if (len < 0)
+                len = 0;
+            len = (ret < len) ? ret : len;
+            pr2ws("    %s: response", cdb_name_s);
+            if (3 == verbose) {
+                pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : ""));
+                hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len),
+                           -1);
+            } else {
+                pr2ws(":\n");
+                hex2stderr((const uint8_t *)resp, len, 0);
+            }
+        }
+        ret = 0;
+    }
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int
+sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len,
+                    bool noisy, int verbose)
+{
+    static const char * const cdb_name_s = "set streaming";
+    int k, res, ret, sense_cat;
+    unsigned char ssCmdBlk[SET_STREAMING_CMDLEN] =
+                 {SET_STREAMING_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    ssCmdBlk[8] = type;
+    sg_put_unaligned_be16((uint16_t)param_len, ssCmdBlk + 9);
+    if (verbose) {
+        pr2ws("    %s cdb: ", cdb_name_s);
+        for (k = 0; k < SET_STREAMING_CMDLEN; ++k)
+            pr2ws("%02x ", ssCmdBlk[k]);
+        pr2ws("\n");
+        if ((verbose > 1) && paramp && param_len) {
+            pr2ws("    %s parameter list:\n", cdb_name_s);
+            hex2stderr((const uint8_t *)paramp, param_len, -1);
+        }
+    }
+
+    if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
+        return -1;
+    set_scsi_pt_cdb(ptvp, ssCmdBlk, sizeof(ssCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
+                               noisy, verbose, &sense_cat);
+    if (-1 == ret) {
+        int os_err = get_scsi_pt_os_err(ptvp);
+
+        if ((os_err > 0) && (os_err < 47))
+            ret = SG_LIB_OS_BASE_ERR + os_err;
+    } else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_NOT_READY:
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
diff --git a/tools/sg_write_buffer/sg_io_linux.c b/tools/sg_write_buffer/sg_io_linux.c
new file mode 100644
index 0000000..f9f08e7
--- /dev/null
+++ b/tools/sg_write_buffer/sg_io_linux.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 1999-2018 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef SG_LIB_LINUX
+
+#include "sg_io_linux.h"
+
+
+/* Version 1.07 20160405 */
+
+#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;
+}
+
+
+void
+sg_print_masked_status(int masked_status)
+{
+    int scsi_status = (masked_status << 1) & 0x7e;
+
+    sg_print_scsi_status(scsi_status);
+}
+
+static const char * linux_host_bytes[] = {
+    "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT",
+    "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR",
+    "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR",
+    "DID_IMM_RETRY", "DID_REQUEUE", "DID_TRANSPORT_DISRUPTED",
+    "DID_TRANSPORT_FAILFAST", "DID_TARGET_FAILURE", "DID_NEXUS_FAILURE",
+    "DID_ALLOC_FAILURE", "DID_MEDIUM_ERROR",
+};
+
+#define LINUX_HOST_BYTES_SZ \
+        (int)(sizeof(linux_host_bytes) / sizeof(linux_host_bytes[0]))
+
+void
+sg_print_host_status(int host_status)
+{
+    pr2ws("Host_status=0x%02x ", host_status);
+    if ((host_status < 0) || (host_status >= LINUX_HOST_BYTES_SZ))
+        pr2ws("is invalid ");
+    else
+        pr2ws("[%s] ", linux_host_bytes[host_status]);
+}
+
+static const char * linux_driver_bytes[] = {
+    "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA",
+    "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD",
+    "DRIVER_SENSE"
+};
+
+#define LINUX_DRIVER_BYTES_SZ \
+    (int)(sizeof(linux_driver_bytes) / sizeof(linux_driver_bytes[0]))
+
+#if 0
+static const char * linux_driver_suggests[] = {
+    "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP",
+    "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN",
+    "SUGGEST_SENSE"
+};
+
+#define LINUX_DRIVER_SUGGESTS_SZ \
+    (int)(sizeof(linux_driver_suggests) / sizeof(linux_driver_suggests[0]))
+#endif
+
+
+void
+sg_print_driver_status(int driver_status)
+{
+    int driv;
+    const char * driv_cp = "invalid";
+
+    driv = driver_status & SG_LIB_DRIVER_MASK;
+    if (driv < LINUX_DRIVER_BYTES_SZ)
+        driv_cp = linux_driver_bytes[driv];
+#if 0
+    sugg = (driver_status & SG_LIB_SUGGEST_MASK) >> 4;
+    if (sugg < LINUX_DRIVER_SUGGESTS_SZ)
+        sugg_cp = linux_driver_suggests[sugg];
+#endif
+    pr2ws("Driver_status=0x%02x", driver_status);
+    pr2ws(" [%s] ", driv_cp);
+}
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+   prints error/warning (prefix by 'leadin') and returns 0. */
+static int
+sg_linux_sense_print(const char * leadin, int scsi_status, int host_status,
+                     int driver_status, const unsigned char * sense_buffer,
+                     int sb_len, bool raw_sinfo)
+{
+    bool done_leadin = false;
+    bool done_sense = false;
+
+    scsi_status &= 0x7e; /*sanity */
+    if ((0 == scsi_status) && (0 == host_status) && (0 == driver_status))
+        return 1;       /* No problems */
+    if (0 != scsi_status) {
+        if (leadin)
+            pr2ws("%s: ", leadin);
+        done_leadin = true;
+        pr2ws("SCSI status: ");
+        sg_print_scsi_status(scsi_status);
+        pr2ws("\n");
+        if (sense_buffer && ((scsi_status == SAM_STAT_CHECK_CONDITION) ||
+                             (scsi_status == SAM_STAT_COMMAND_TERMINATED))) {
+            /* SAM_STAT_COMMAND_TERMINATED is obsolete */
+            sg_print_sense(0, sense_buffer, sb_len, raw_sinfo);
+            done_sense = true;
+        }
+    }
+    if (0 != host_status) {
+        if (leadin && (! done_leadin))
+            pr2ws("%s: ", leadin);
+        if (done_leadin)
+            pr2ws("plus...: ");
+        else
+            done_leadin = true;
+        sg_print_host_status(host_status);
+        pr2ws("\n");
+    }
+    if (0 != driver_status) {
+        if (done_sense &&
+            (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status)))
+            return 0;
+        if (leadin && (! done_leadin))
+            pr2ws("%s: ", leadin);
+        if (done_leadin)
+            pr2ws("plus...: ");
+        sg_print_driver_status(driver_status);
+        pr2ws("\n");
+        if (sense_buffer && (! done_sense) &&
+            (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status)))
+            sg_print_sense(0, sense_buffer, sb_len, raw_sinfo);
+    }
+    return 0;
+}
+
+#ifdef SG_IO
+
+bool
+sg_normalize_sense(const struct sg_io_hdr * hp,
+                   struct sg_scsi_sense_hdr * sshp)
+{
+    if ((NULL == hp) || (0 == hp->sb_len_wr)) {
+        if (sshp)
+            memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
+        return 0;
+    }
+    return sg_scsi_normalize_sense(hp->sbp, hp->sb_len_wr, sshp);
+}
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+   returns 0. */
+int
+sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp,
+                bool raw_sinfo)
+{
+    return sg_linux_sense_print(leadin, hp->status, hp->host_status,
+                                hp->driver_status, hp->sbp, hp->sb_len_wr,
+                                raw_sinfo);
+}
+#endif
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+   returns 0. */
+int
+sg_chk_n_print(const char * leadin, int masked_status, int host_status,
+               int driver_status, const unsigned char * sense_buffer,
+               int sb_len, bool raw_sinfo)
+{
+    int scsi_status = (masked_status << 1) & 0x7e;
+
+    return sg_linux_sense_print(leadin, scsi_status, host_status,
+                                driver_status, sense_buffer, sb_len,
+                                raw_sinfo);
+}
+
+#ifdef SG_IO
+int
+sg_err_category3(struct sg_io_hdr * hp)
+{
+    return sg_err_category_new(hp->status, hp->host_status,
+                               hp->driver_status, hp->sbp, hp->sb_len_wr);
+}
+#endif
+
+int
+sg_err_category(int masked_status, int host_status, int driver_status,
+                const unsigned char * sense_buffer, int sb_len)
+{
+    int scsi_status = (masked_status << 1) & 0x7e;
+
+    return sg_err_category_new(scsi_status, host_status, driver_status,
+                               sense_buffer, sb_len);
+}
+
+int
+sg_err_category_new(int scsi_status, int host_status, int driver_status,
+                    const unsigned char * sense_buffer, int sb_len)
+{
+    int masked_driver_status = (SG_LIB_DRIVER_MASK & driver_status);
+
+    scsi_status &= 0x7e;
+    if ((0 == scsi_status) && (0 == host_status) &&
+        (0 == masked_driver_status))
+        return SG_LIB_CAT_CLEAN;
+    if ((SAM_STAT_CHECK_CONDITION == scsi_status) ||
+        (SAM_STAT_COMMAND_TERMINATED == scsi_status) ||
+        (SG_LIB_DRIVER_SENSE == masked_driver_status))
+        return sg_err_category_sense(sense_buffer, sb_len);
+    if (0 != host_status) {
+        if ((SG_LIB_DID_NO_CONNECT == host_status) ||
+            (SG_LIB_DID_BUS_BUSY == host_status) ||
+            (SG_LIB_DID_TIME_OUT == host_status))
+            return SG_LIB_CAT_TIMEOUT;
+        if (SG_LIB_DID_NEXUS_FAILURE == host_status)
+            return SG_LIB_CAT_RES_CONFLICT;
+    }
+    if (SG_LIB_DRIVER_TIMEOUT == masked_driver_status)
+        return SG_LIB_CAT_TIMEOUT;
+    return SG_LIB_CAT_OTHER;
+}
+
+#endif  /* if SG_LIB_LINUX defined */
diff --git a/tools/sg_write_buffer/sg_lib.c b/tools/sg_write_buffer/sg_lib.c
new file mode 100644
index 0000000..e31c816
--- /dev/null
+++ b/tools/sg_write_buffer/sg_lib.c
@@ -0,0 +1,3494 @@
+/*
+ * Copyright (c) 1999-2018 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.
+ */
+
+/* NOTICE:
+ *    On 5th October 2004 (v1.00) this file name was changed from sg_err.c
+ *    to sg_lib.c and the previous GPL was changed to a FreeBSD license.
+ *    The intention is to maintain this file and the related sg_lib.h file
+ *    as open source and encourage their unencumbered use.
+ *
+ * CONTRIBUTIONS:
+ *    This file started out as a copy of SCSI opcodes, sense keys and
+ *    additional sense codes (ASC/ASCQ) kept in the Linux SCSI subsystem
+ *    in the kernel source file: drivers/scsi/constant.c . That file
+ *    bore this notice: "Copyright (C) 1993, 1994, 1995 Eric Youngdale"
+ *    and a GPL notice.
+ *
+ *    Much of the data in this file is derived from SCSI draft standards
+ *    found at http://www.t10.org with the "SCSI Primary Commands-4" (SPC-4)
+ *    being the central point of reference.
+ *
+ *    Contributions:
+ *      sense key specific field decoding [Trent Piepho 20031116]
+ *
+ */
+
+#define _POSIX_C_SOURCE 200809L         /* for posix_memalign() */
+#define __STDC_FORMAT_MACROS 1
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */
+
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d  /* corresponding ASC is 0 */
+
+FILE * sg_warnings_strm = NULL;        /* would like to default to stderr */
+
+#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;
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+static int scnpr(char * cp, int cp_max_len, const char * fmt, ...)
+                 __attribute__ ((format (printf, 3, 4)));
+#else
+static int scnpr(char * cp, int cp_max_len, const char * fmt, ...);
+#endif
+
+/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of
+ * functions. Returns number of chars placed in cp excluding the
+ * trailing null char. So for cp_max_len > 0 the return value is always
+ * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are
+ * written to cp. Note this means that when cp_max_len = 1, this function
+ * assumes that cp[0] is the null character and does nothing (and returns
+ * 0). Linux kernel has a similar function called  scnprintf().  */
+static int
+scnpr(char * cp, int cp_max_len, const char * fmt, ...)
+{
+    va_list args;
+    int n;
+
+    if (cp_max_len < 2)
+        return 0;
+    va_start(args, fmt);
+    n = vsnprintf(cp, cp_max_len, fmt, args);
+    va_end(args);
+    return (n < cp_max_len) ? n : (cp_max_len - 1);
+}
+
+/* Simple ASCII printable (does not use locale), includes space and excludes
+ * DEL (0x7f). */
+static inline int my_isprint(int ch)
+{
+    return ((ch >= ' ') && (ch < 0x7f));
+}
+
+/* Searches 'arr' for match on 'value' then 'peri_type'. If matches
+   'value' but not 'peri_type' then yields first 'value' match entry.
+   Last element of 'arr' has NULL 'name'. If no match returns NULL. */
+static const struct sg_lib_value_name_t *
+get_value_name(const struct sg_lib_value_name_t * arr, int value,
+               int peri_type)
+{
+    const struct sg_lib_value_name_t * vp = arr;
+    const struct sg_lib_value_name_t * holdp;
+
+    if (peri_type < 0)
+        peri_type = 0;
+    for (; vp->name; ++vp) {
+        if (value == vp->value) {
+            if (peri_type == vp->peri_dev_type)
+                return vp;
+            holdp = vp;
+            while ((vp + 1)->name && (value == (vp + 1)->value)) {
+                ++vp;
+                if (peri_type == vp->peri_dev_type)
+                    return vp;
+            }
+            return holdp;
+        }
+    }
+    return NULL;
+}
+
+/* If this function is not called, sg_warnings_strm will be NULL and all users
+ * (mainly fprintf() ) need to check and substitute stderr as required */
+void
+sg_set_warnings_strm(FILE * warnings_strm)
+{
+    sg_warnings_strm = warnings_strm;
+}
+
+#define CMD_NAME_LEN 128
+
+void
+sg_print_command(const unsigned char * command)
+{
+    int k, sz;
+    char buff[CMD_NAME_LEN];
+
+    sg_get_command_name(command, 0, CMD_NAME_LEN, buff);
+    buff[CMD_NAME_LEN - 1] = '\0';
+
+    pr2ws("%s [", buff);
+    if (SG_VARIABLE_LENGTH_CMD == command[0])
+        sz = command[7] + 8;
+    else
+        sz = sg_get_command_size(command[0]);
+    for (k = 0; k < sz; ++k)
+        pr2ws("%02x ", command[k]);
+    pr2ws("]\n");
+}
+
+void
+sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff)
+{
+    const char * ccp = NULL;
+    bool unknown = false;
+
+    if ((NULL == buff) || (buff_len < 1))
+        return;
+    else if (1 ==  buff_len) {
+        buff[0] = '\0';
+        return;
+    }
+    scsi_status &= 0x7e; /* sanitize as much as possible */
+    switch (scsi_status) {
+        case 0: ccp = "Good"; break;
+        case 0x2: ccp = "Check Condition"; break;
+        case 0x4: ccp = "Condition Met"; break;
+        case 0x8: ccp = "Busy"; break;
+        case 0x10: ccp = "Intermediate (obsolete)"; break;
+        case 0x14: ccp = "Intermediate-Condition Met (obsolete)"; break;
+        case 0x18: ccp = "Reservation Conflict"; break;
+        case 0x22: ccp = "Command Terminated (obsolete)"; break;
+        case 0x28: ccp = "Task set Full"; break;
+        case 0x30: ccp = "ACA Active"; break;
+        case 0x40: ccp = "Task Aborted"; break;
+        default:
+            unknown = true;
+            break;
+    }
+    if (unknown)
+        scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status);
+    else
+        scnpr(buff, buff_len, "%s", ccp);
+}
+
+void
+sg_print_scsi_status(int scsi_status)
+{
+    char buff[128];
+
+    sg_get_scsi_status_str(scsi_status, sizeof(buff) - 1, buff);
+    buff[sizeof(buff) - 1] = '\0';
+    pr2ws("%s ", buff);
+}
+
+/* Get sense key from sense buffer. If successful returns a sense key value
+ * between 0 and 15. If sense buffer cannot be decode, returns -1 . */
+int
+sg_get_sense_key(const unsigned char * sbp, int sb_len)
+{
+    if ((NULL == sbp) || (sb_len < 2))
+        return -1;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        return (sb_len < 3) ? -1 : (sbp[2] & 0xf);
+    case 0x72:
+    case 0x73:
+        return sbp[1] & 0xf;
+    default:
+        return -1;
+    }
+}
+
+/* Yield string associated with sense_key value. Returns 'buff'. */
+char *
+sg_get_sense_key_str(int sense_key, int buff_len, char * buff)
+{
+    if (1 == buff_len) {
+        buff[0] = '\0';
+        return buff;
+    }
+    if ((sense_key >= 0) && (sense_key < 16))
+         scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]);
+    else
+         scnpr(buff, buff_len, "invalid value: 0x%x", sense_key);
+    return buff;
+}
+
+/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
+char *
+sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff)
+{
+    int k, num, rlen;
+    bool found = false;
+    struct sg_lib_asc_ascq_t * eip;
+    struct sg_lib_asc_ascq_range_t * ei2p;
+
+    if (1 == buff_len) {
+        buff[0] = '\0';
+        return buff;
+    }
+    for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) {
+        ei2p = &sg_lib_asc_ascq_range[k];
+        if ((ei2p->asc == asc) &&
+            (ascq >= ei2p->ascq_min)  &&
+            (ascq <= ei2p->ascq_max)) {
+            found = true;
+            num = scnpr(buff, buff_len, "Additional sense: ");
+            rlen = buff_len - num;
+            scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq);
+        }
+    }
+    if (found)
+        return buff;
+
+    for (k = 0; sg_lib_asc_ascq[k].text; ++k) {
+        eip = &sg_lib_asc_ascq[k];
+        if (eip->asc == asc &&
+            eip->ascq == ascq) {
+            found = true;
+            scnpr(buff, buff_len, "Additional sense: %s", eip->text);
+        }
+    }
+    if (! found) {
+        if (asc >= 0x80)
+            scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x "
+                  "(hex)", asc, ascq);
+        else if (ascq >= 0x80)
+            scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification "
+                  "ASCQ=%02x (hex)", asc, ascq);
+        else
+            scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq);
+    }
+    return buff;
+}
+
+/* Attempt to find the first SCSI sense data descriptor that matches the
+ * given 'desc_type'. If found return pointer to start of sense data
+ * descriptor; otherwise (including fixed format sense data) returns NULL. */
+const unsigned char *
+sg_scsi_sense_desc_find(const unsigned char * sbp, int sb_len,
+                        int desc_type)
+{
+    int add_sb_len, add_d_len, desc_len, k;
+    const unsigned char * descp;
+
+    if ((sb_len < 8) || (0 == (add_sb_len = sbp[7])))
+        return NULL;
+    if ((sbp[0] < 0x72) || (sbp[0] > 0x73))
+        return NULL;
+    add_sb_len = (add_sb_len < (sb_len - 8)) ?  add_sb_len : (sb_len - 8);
+    descp = &sbp[8];
+    for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) {
+        descp += desc_len;
+        add_d_len = (k < (add_sb_len - 1)) ? descp[1]: -1;
+        desc_len = add_d_len + 2;
+        if (descp[0] == desc_type)
+            return descp;
+        if (add_d_len < 0) /* short descriptor ?? */
+            break;
+    }
+    return NULL;
+}
+
+/* Returns true if valid bit set, false if valid bit clear. Irrespective the
+ * information field is written out via 'info_outp' (except when it is
+ * NULL). Handles both fixed and descriptor sense formats. */
+bool
+sg_get_sense_info_fld(const unsigned char * sbp, int sb_len,
+                      uint64_t * info_outp)
+{
+    const unsigned char * bp;
+    uint64_t ull;
+
+    if (info_outp)
+        *info_outp = 0;
+    if (sb_len < 7)
+        return false;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        if (info_outp)
+            *info_outp = sg_get_unaligned_be32(sbp + 3);
+        return !!(sbp[0] & 0x80);
+    case 0x72:
+    case 0x73:
+        bp = sg_scsi_sense_desc_find(sbp, sb_len, 0 /* info desc */);
+        if (bp && (0xa == bp[1])) {
+            ull = sg_get_unaligned_be64(bp + 4);
+            if (info_outp)
+                *info_outp = ull;
+            return !!(bp[2] & 0x80);   /* since spc3r23 should be set */
+        } else
+            return false;
+    default:
+        return false;
+    }
+}
+
+/* Returns true if fixed format or command specific information descriptor
+ * is found in the descriptor sense; else false. If available the command
+ * specific information field (4 byte integer in fixed format, 8 byte
+ * integer in descriptor format) is written out via 'cmd_spec_outp'.
+ * Handles both fixed and descriptor sense formats. */
+bool
+sg_get_sense_cmd_spec_fld(const unsigned char * sbp, int sb_len,
+                          uint64_t * cmd_spec_outp)
+{
+    const unsigned char * bp;
+
+    if (cmd_spec_outp)
+        *cmd_spec_outp = 0;
+    if (sb_len < 7)
+        return false;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        if (cmd_spec_outp)
+            *cmd_spec_outp = sg_get_unaligned_be32(sbp + 8);
+        return true;
+    case 0x72:
+    case 0x73:
+        bp = sg_scsi_sense_desc_find(sbp, sb_len,
+                                     1 /* command specific info desc */);
+        if (bp && (0xa == bp[1])) {
+            if (cmd_spec_outp)
+                *cmd_spec_outp = sg_get_unaligned_be64(bp + 4);
+            return true;
+        } else
+            return false;
+    default:
+        return false;
+    }
+}
+
+/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set.
+ * In descriptor format if the stream commands descriptor not found
+ * then returns false. Writes true or false corresponding to these bits to
+ * the last three arguments if they are non-NULL. */
+bool
+sg_get_sense_filemark_eom_ili(const unsigned char * sbp, int sb_len,
+                              bool * filemark_p, bool * eom_p, bool * ili_p)
+{
+    const unsigned char * bp;
+
+    if (sb_len < 7)
+        return false;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        if (sbp[2] & 0xe0) {
+            if (filemark_p)
+                *filemark_p = !!(sbp[2] & 0x80);
+            if (eom_p)
+                *eom_p = !!(sbp[2] & 0x40);
+            if (ili_p)
+                *ili_p = !!(sbp[2] & 0x20);
+            return true;
+        } else
+            return false;
+    case 0x72:
+    case 0x73:
+       /* Look for stream commands sense data descriptor */
+        bp = sg_scsi_sense_desc_find(sbp, sb_len, 4);
+        if (bp && (bp[1] >= 2)) {
+            if (bp[3] & 0xe0) {
+                if (filemark_p)
+                    *filemark_p = !!(bp[3] & 0x80);
+                if (eom_p)
+                    *eom_p = !!(bp[3] & 0x40);
+                if (ili_p)
+                    *ili_p = !!(bp[3] & 0x20);
+                return true;
+            }
+        }
+        return false;
+    default:
+        return false;
+    }
+}
+
+/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also
+ * returns true if progress indication sense data descriptor found. Places
+ * progress field from sense data where progress_outp points. If progress
+ * field is not available returns false and *progress_outp is unaltered.
+ * Handles both fixed and descriptor sense formats.
+ * Hint: if true is returned *progress_outp may be multiplied by 100 then
+ * divided by 65536 to get the percentage completion. */
+bool
+sg_get_sense_progress_fld(const unsigned char * sbp, int sb_len,
+                          int * progress_outp)
+{
+    const unsigned char * bp;
+    int sk, sk_pr;
+
+    if (sb_len < 7)
+        return false;
+    switch (sbp[0] & 0x7f) {
+    case 0x70:
+    case 0x71:
+        sk = (sbp[2] & 0xf);
+        if ((sb_len < 18) ||
+            ((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk)))
+            return false;
+        if (sbp[15] & 0x80) {        /* SKSV bit set */
+            if (progress_outp)
+                *progress_outp = sg_get_unaligned_be16(sbp + 16);
+            return true;
+        } else
+            return false;
+    case 0x72:
+    case 0x73:
+        /* sense key specific progress (0x2) or progress descriptor (0xa) */
+        sk = (sbp[1] & 0xf);
+        sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk);
+        if (sk_pr && ((bp = sg_scsi_sense_desc_find(sbp, sb_len, 2))) &&
+            (0x6 == bp[1]) && (0x80 & bp[4])) {
+            if (progress_outp)
+                *progress_outp = sg_get_unaligned_be16(bp + 5);
+            return true;
+        } else if (((bp = sg_scsi_sense_desc_find(sbp, sb_len, 0xa))) &&
+                   ((0x6 == bp[1]))) {
+            if (progress_outp)
+                *progress_outp = sg_get_unaligned_be16(bp + 6);
+            return true;
+        } else
+            return false;
+    default:
+        return false;
+    }
+}
+
+char *
+sg_get_pdt_str(int pdt, int buff_len, char * buff)
+{
+    if ((pdt < 0) || (pdt > 31))
+        scnpr(buff, buff_len, "bad pdt");
+    else
+        scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]);
+    return buff;
+}
+
+int
+sg_lib_pdt_decay(int pdt)
+{
+    if ((pdt < 0) || (pdt > 31))
+        return 0;
+    return sg_lib_pdt_decay_arr[pdt];
+}
+
+char *
+sg_get_trans_proto_str(int tpi, int buff_len, char * buff)
+{
+    if ((tpi < 0) || (tpi > 15))
+        scnpr(buff, buff_len, "bad tpi");
+    else
+        scnpr(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]);
+    return buff;
+}
+
+#define TRANSPORT_ID_MIN_LEN 24
+
+char *
+sg_decode_transportid_str(const char * lip, unsigned char * bp, int bplen,
+                          bool only_one, int blen, char * b)
+{
+    int proto_id, num, k, n, normal_len, tpid_format;
+    uint64_t ull;
+    int bump;
+
+    if ((NULL == b) || (blen < 1))
+        return b;
+    else if (1 == blen) {
+        b[0] = '\0';
+        return b;
+    }
+    if (NULL == lip)
+        lip = "";
+    bump = TRANSPORT_ID_MIN_LEN; /* should be overwritten in all loop paths */
+    for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) {
+        if ((k > 0) && only_one)
+            break;
+        if ((bplen < 24) || (0 != (bplen % 4)))
+            n += scnpr(b + n, blen - n, "%sTransport Id short or not "
+                       "multiple of 4 [length=%d]:\n", lip, blen);
+        else
+            n += scnpr(b + n, blen - n, "%sTransport Id of initiator:\n",
+                       lip);
+        tpid_format = ((bp[0] >> 6) & 0x3);
+        proto_id = (bp[0] & 0xf);
+        normal_len = (bplen > TRANSPORT_ID_MIN_LEN) ?
+                                TRANSPORT_ID_MIN_LEN : bplen;
+        switch (proto_id) {
+        case TPROTO_FCP: /* Fibre channel */
+            n += scnpr(b + n, blen - n, "%s  FCP-2 World Wide Name:\n", lip);
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            n += hex2str(bp + 8, 8, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_SPI:        /* Scsi Parallel Interface, obsolete */
+            n += scnpr(b + n, blen - n, "%s  Parallel SCSI initiator SCSI "
+                       "address: 0x%x\n", lip, sg_get_unaligned_be16(bp + 2));
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            n += scnpr(b + n, blen - n, "%s  relative port number (of "
+                       "corresponding target): 0x%x\n", lip,
+                       sg_get_unaligned_be16(bp + 6));
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_SSA:
+            n += scnpr(b + n, blen - n, "%s  SSA (transport id not "
+                       "defined):\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_1394: /* IEEE 1394 */
+            n += scnpr(b + n, blen - n, "%s  IEEE 1394 EUI-64 name:\n", lip);
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            n += hex2str(&bp[8], 8, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_SRP:        /* SCSI over RDMA */
+            n += scnpr(b + n, blen - n, "%s  RDMA initiator port "
+                       "identifier:\n", lip);
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            n += hex2str(bp + 8, 16, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_ISCSI:
+            n += scnpr(b + n, blen - n, "%s  iSCSI ", lip);
+            num = sg_get_unaligned_be16(bp + 2);
+            if (0 == tpid_format)
+                n += scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]);
+            else if (1 == tpid_format)
+                n += scnpr(b + n, blen - n, "world wide unique port id: "
+                           "%.*s\n", num, &bp[4]);
+            else {
+                n += scnpr(b + n, blen - n, "  [Unexpected TPID format: "
+                           "%d]\n", tpid_format);
+                n += hex2str(bp, num + 4, lip, 0, blen - n, b + n);
+            }
+            bump = (((num + 4) < TRANSPORT_ID_MIN_LEN) ?
+                         TRANSPORT_ID_MIN_LEN : num + 4);
+            break;
+        case TPROTO_SAS:
+            ull = sg_get_unaligned_be64(bp + 4);
+            n += scnpr(b + n, blen - n, "%s  SAS address: 0x%" PRIx64 "\n",
+                       lip, ull);
+            if (0 != tpid_format)
+                n += scnpr(b + n, blen - n, "%s  [Unexpected TPID format: "
+                           "%d]\n", lip, tpid_format);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_ADT:        /* no TransportID defined by T10 yet */
+            n += scnpr(b + n, blen - n, "%s  ADT:\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_ATA:        /* no TransportID defined by T10 yet */
+            n += scnpr(b + n, blen - n, "%s  ATAPI:\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_UAS:        /* no TransportID defined by T10 yet */
+            n += scnpr(b + n, blen - n, "%s  UAS:\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_SOP:
+            n += scnpr(b + n, blen - n, "%s  SOP ", lip);
+            num = sg_get_unaligned_be16(bp + 2);
+            if (0 == tpid_format)
+                n += scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num);
+            else {
+                n += scnpr(b + n, blen - n, "  [Unexpected TPID format: "
+                           "%d]\n", tpid_format);
+                n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            }
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_PCIE:       /* no TransportID defined by T10 yet */
+            n += scnpr(b + n, blen - n, "%s  PCIE:\n", lip);
+            n += scnpr(b + n, blen - n, "%s  TPID format: %d\n", lip,
+                       tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        case TPROTO_NONE:       /* no TransportID defined by T10 */
+            n += scnpr(b + n, blen - n, "%s  No specified protocol\n", lip);
+            /* n += hex2str(bp, ((bplen > 24) ? 24 : bplen),
+             *                 lip, 0, blen - n, b + n); */
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        default:
+            n += scnpr(b + n, blen - n, "%s  unknown protocol id=0x%x  "
+                       "TPID format=%d\n", lip, proto_id, tpid_format);
+            n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+            bump = TRANSPORT_ID_MIN_LEN;
+            break;
+        }
+    }
+    return b;
+}
+
+
+static const char * desig_code_set_str_arr[] =
+{
+    "Reserved [0x0]",
+    "Binary",
+    "ASCII",
+    "UTF-8",
+    "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]",
+    "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]",
+    "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
+};
+
+const char *
+sg_get_desig_code_set_str(int val)
+{
+    if ((val >= 0) && (val < 16))
+        return desig_code_set_str_arr[val];
+    else
+        return NULL;
+}
+
+static const char * desig_assoc_str_arr[] =
+{
+    "Addressed logical unit",
+    "Target port",      /* that received request; unless SCSI ports VPD */
+    "Target device that contains addressed lu",
+    "Reserved [0x3]",
+};
+
+const char *
+sg_get_desig_assoc_str(int val)
+{
+    if ((val >= 0) && (val < 4))
+        return desig_assoc_str_arr[val];
+    else
+        return NULL;
+}
+
+static const char * desig_type_str_arr[] =
+{
+    "vendor specific [0x0]",
+    "T10 vendor identification",
+    "EUI-64 based",
+    "NAA",
+    "Relative target port",
+    "Target port group",        /* spc4r09: _primary_ target port group */
+    "Logical unit group",
+    "MD5 logical unit identifier",
+    "SCSI name string",
+    "Protocol specific port identifier",        /* spc4r36 */
+    "UUID identifier",          /* spc5r08 */
+    "Reserved [0xb]",
+    "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
+};
+
+const char *
+sg_get_desig_type_str(int val)
+{
+    if ((val >= 0) && (val < 16))
+        return desig_type_str_arr[val];
+    else
+        return NULL;
+}
+
+int
+sg_get_designation_descriptor_str(const char * lip, const unsigned char * ddp,
+                                  int dd_len, bool print_assoc, bool do_long,
+                                  int blen, char * b)
+{
+    int m, p_id, piv, c_set, assoc, desig_type, ci_off, c_id, d_id, naa;
+    int vsi, k, n, dlen;
+    const unsigned char * ip;
+    uint64_t vsei;
+    uint64_t id_ext;
+    char e[64];
+    const char * cp;
+
+    n = 0;
+    if (NULL == lip)
+        lip = "";
+    if (dd_len < 4) {
+        n += scnpr(b + n, blen - n, "%sdesignator desc too short: got "
+                   "length of %d want 4 or more\n", lip, dd_len);
+        return n;
+    }
+    dlen = ddp[3];
+    if (dlen > (dd_len - 4)) {
+        n += scnpr(b + n, blen - n, "%sdesignator too long: says it is %d "
+                   "bytes, but given %d bytes\n", lip, dlen, dd_len - 4);
+        return n;
+    }
+    ip = ddp + 4;
+    p_id = ((ddp[0] >> 4) & 0xf);
+    c_set = (ddp[0] & 0xf);
+    piv = ((ddp[1] & 0x80) ? 1 : 0);
+    assoc = ((ddp[1] >> 4) & 0x3);
+    desig_type = (ddp[1] & 0xf);
+    if (print_assoc && ((cp = sg_get_desig_assoc_str(assoc))))
+        n += scnpr(b + n, blen - n, "%s  %s:\n", lip, cp);
+    n += scnpr(b + n, blen - n, "%s    designator type: ", lip);
+    cp = sg_get_desig_type_str(desig_type);
+    if (cp)
+        n += scnpr(b + n, blen - n, "%s", cp);
+    n += scnpr(b + n, blen - n, ",  code set: ");
+    cp = sg_get_desig_code_set_str(c_set);
+    if (cp)
+        n += scnpr(b + n, blen - n, "%s", cp);
+    n += scnpr(b + n, blen - n, "\n");
+    if (piv && ((1 == assoc) || (2 == assoc)))
+        n += scnpr(b + n, blen - n, "%s     transport: %s\n", lip,
+                   sg_get_trans_proto_str(p_id, sizeof(e), e));
+    /* printf("    associated with the %s\n", sdparm_assoc_arr[assoc]); */
+    switch (desig_type) {
+    case 0: /* vendor specific */
+        k = 0;
+        if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */
+            for (k = 0; (k < dlen) && my_isprint(ip[k]); ++k)
+                ;
+            if (k >= dlen)
+                k = 1;
+        }
+        if (k)
+            n += scnpr(b + n, blen - n, "%s      vendor specific: %.*s\n",
+                       lip, dlen, ip);
+        else {
+            n += scnpr(b + n, blen - n, "%s      vendor specific:\n", lip);
+            n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+        }
+        break;
+    case 1: /* T10 vendor identification */
+        n += scnpr(b + n, blen - n, "%s      vendor id: %.8s\n", lip, ip);
+        if (dlen > 8) {
+            if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+                n += scnpr(b + n, blen - n, "%s      vendor specific: "
+                           "%.*s\n", lip, dlen - 8, ip + 8);
+            } else {
+                n += scnpr(b + n, blen - n, "%s      vendor specific: 0x",
+                           lip);
+                for (m = 8; m < dlen; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "\n");
+            }
+        }
+        break;
+    case 2: /* EUI-64 based */
+        if (! do_long) {
+            if ((8 != dlen) && (12 != dlen) && (16 != dlen)) {
+                n += scnpr(b + n, blen - n, "%s      << expect 8, 12 and 16 "
+                           "byte EUI, got %d >>\n", lip, dlen);
+                 n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            n += scnpr(b + n, blen - n, "%s      0x", lip);
+            for (m = 0; m < dlen; ++m)
+                n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+            n += scnpr(b + n, blen - n, "\n");
+            break;
+        }
+        n += scnpr(b + n, blen - n, "%s      EUI-64 based %d byte "
+                   "identifier\n", lip, dlen);
+        if (1 != c_set) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary code_set "
+                       "(1) >>\n", lip);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        ci_off = 0;
+        if (16 == dlen) {
+            ci_off = 8;
+            id_ext = sg_get_unaligned_be64(ip);
+            n += scnpr(b + n, blen - n, "%s      Identifier extension: 0x%"
+                       PRIx64 "\n", lip, id_ext);
+        } else if ((8 != dlen) && (12 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << can only decode 8, 12 "
+                       "and 16 byte ids >>\n", lip);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        c_id = sg_get_unaligned_be24(ip + ci_off);
+        n += scnpr(b + n, blen - n, "%s      IEEE Company_id: 0x%x\n", lip,
+                   c_id);
+        vsei = 0;
+        for (m = 0; m < 5; ++m) {
+            if (m > 0)
+                vsei <<= 8;
+            vsei |= ip[ci_off + 3 + m];
+        }
+        n += scnpr(b + n, blen - n, "%s      Vendor Specific Extension "
+                   "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+        if (12 == dlen) {
+            d_id = sg_get_unaligned_be32(ip + 8);
+            n += scnpr(b + n, blen - n, "%s      Directory ID: 0x%x\n", lip,
+                       d_id);
+        }
+        break;
+    case 3: /* NAA <n> */
+        if (1 != c_set) {
+            n += scnpr(b + n, blen - n, "%s      << unexpected code set %d "
+                       "for NAA >>\n", lip, c_set);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        naa = (ip[0] >> 4) & 0xff;
+        switch (naa) {
+        case 2:         /* NAA 2: IEEE Extended */
+            if (8 != dlen) {
+                n += scnpr(b + n, blen - n, "%s      << unexpected NAA 2 "
+                           "identifier length: 0x%x >>\n", lip, dlen);
+                n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            d_id = (((ip[0] & 0xf) << 8) | ip[1]);
+            c_id = sg_get_unaligned_be24(ip + 2);
+            vsi = sg_get_unaligned_be24(ip + 5);
+            if (do_long) {
+                n += scnpr(b + n, blen - n, "%s      NAA 2, vendor specific "
+                           "identifier A: 0x%x\n", lip, d_id);
+                n += scnpr(b + n, blen - n, "%s      IEEE Company_id: 0x%x\n",
+                           lip, c_id);
+                n += scnpr(b + n, blen - n, "%s      vendor specific "
+                           "identifier B: 0x%x\n", lip, vsi);
+                n += scnpr(b + n, blen - n, "%s      [0x", lip);
+                for (m = 0; m < 8; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "]\n");
+            }
+            n += scnpr(b + n, blen - n, "%s      0x", lip);
+            for (m = 0; m < 8; ++m)
+                n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+            n += scnpr(b + n, blen - n, "\n");
+            break;
+        case 3:         /* NAA 3: Locally assigned */
+            if (8 != dlen) {
+                n += scnpr(b + n, blen - n, "%s      << unexpected NAA 3 "
+                           "identifier length: 0x%x >>\n", lip, dlen);
+                n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            if (do_long)
+                n += scnpr(b + n, blen - n, "%s      NAA 3, Locally "
+                           "assigned:\n", lip);
+            n += scnpr(b + n, blen - n, "%s      0x", lip);
+            for (m = 0; m < 8; ++m)
+                n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+            n += scnpr(b + n, blen - n, "\n");
+            break;
+        case 5:         /* NAA 5: IEEE Registered */
+            if (8 != dlen) {
+                n += scnpr(b + n, blen - n, "%s      << unexpected NAA 5 "
+                           "identifier length: 0x%x >>\n", lip, dlen);
+                n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) |
+                    (ip[2] << 4) | ((ip[3] & 0xf0) >> 4));
+            vsei = ip[3] & 0xf;
+            for (m = 1; m < 5; ++m) {
+                vsei <<= 8;
+                vsei |= ip[3 + m];
+            }
+            if (do_long) {
+                n += scnpr(b + n, blen - n, "%s      NAA 5, IEEE "
+                           "Company_id: 0x%x\n", lip, c_id);
+                n += scnpr(b + n, blen - n, "%s      Vendor Specific "
+                           "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+                n += scnpr(b + n, blen - n, "%s      [0x", lip);
+                for (m = 0; m < 8; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "]\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s      0x", lip);
+                for (m = 0; m < 8; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "\n");
+            }
+            break;
+        case 6:         /* NAA 6: IEEE Registered extended */
+            if (16 != dlen) {
+                n += scnpr(b + n, blen - n, "%s      << unexpected NAA 6 "
+                           "identifier length: 0x%x >>\n", lip, dlen);
+                n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+                break;
+            }
+            c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) |
+                    (ip[2] << 4) | ((ip[3] & 0xf0) >> 4));
+            vsei = ip[3] & 0xf;
+            for (m = 1; m < 5; ++m) {
+                vsei <<= 8;
+                vsei |= ip[3 + m];
+            }
+            if (do_long) {
+                n += scnpr(b + n, blen - n, "%s      NAA 6, IEEE "
+                           "Company_id: 0x%x\n", lip, c_id);
+                n += scnpr(b + n, blen - n, "%s      Vendor Specific "
+                           "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+                vsei = sg_get_unaligned_be64(ip + 8);
+                n += scnpr(b + n, blen - n, "%s      Vendor Specific "
+                           "Identifier Extension: 0x%" PRIx64 "\n", lip,
+                                 vsei);
+                n += scnpr(b + n, blen - n, "%s      [0x", lip);
+                for (m = 0; m < 16; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "]\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s      0x", lip);
+                for (m = 0; m < 16; ++m)
+                    n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[m]);
+                n += scnpr(b + n, blen - n, "\n");
+            }
+            break;
+        default:
+            n += scnpr(b + n, blen - n, "%s      << unexpected NAA [0x%x] "
+                       ">>\n", lip, naa);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        break;
+    case 4: /* Relative target port */
+        if ((1 != c_set) || (1 != assoc) || (4 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set, target port association, length 4 >>\n",
+                       lip);
+            n += hex2str(ip, dlen, "", 1, blen - n, b + n);
+            break;
+        }
+        d_id = sg_get_unaligned_be16(ip + 2);
+        n += scnpr(b + n, blen - n, "%s      Relative target port: 0x%x\n",
+                   lip, d_id);
+        break;
+    case 5: /* (primary) Target port group */
+        if ((1 != c_set) || (1 != assoc) || (4 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set, target port association, length 4 >>\n",
+                       lip);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        d_id = sg_get_unaligned_be16(ip + 2);
+        n += scnpr(b + n, blen - n, "%s      Target port group: 0x%x\n", lip,
+                   d_id);
+        break;
+    case 6: /* Logical unit group */
+        if ((1 != c_set) || (0 != assoc) || (4 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set, logical unit association, length 4 >>\n",
+                       lip);
+            n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+            break;
+        }
+        d_id = sg_get_unaligned_be16(ip + 2);
+        n += scnpr(b + n, blen - n, "%s      Logical unit group: 0x%x\n", lip,
+                   d_id);
+        break;
+    case 7: /* MD5 logical unit identifier */
+        if ((1 != c_set) || (0 != assoc)) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set, logical unit association >>\n", lip);
+            n += hex2str(ip, dlen, "", 1, blen - n, b + n);
+            break;
+        }
+        n += scnpr(b + n, blen - n, "%s      MD5 logical unit identifier:\n",
+                   lip);
+        n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+        break;
+    case 8: /* SCSI name string */
+        if (3 != c_set) {       /* accept ASCII as subset of UTF-8 */
+            if (2 == c_set) {
+                if (do_long)
+                    n += scnpr(b + n, blen - n, "%s      << expected UTF-8, "
+                               "use ASCII >>\n", lip);
+            } else {
+                n += scnpr(b + n, blen - n, "%s      << expected UTF-8 "
+                           "code_set >>\n", lip);
+                n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+                break;
+            }
+        }
+        n += scnpr(b + n, blen - n, "%s      SCSI name string:\n", lip);
+        /* does %s print out UTF-8 ok??
+         * Seems to depend on the locale. Looks ok here with my
+         * locale setting: en_AU.UTF-8
+         */
+        n += scnpr(b + n, blen - n, "%s      %.*s\n", lip, dlen,
+                   (const char *)ip);
+        break;
+    case 9: /* Protocol specific port identifier */
+        /* added in spc4r36, PIV must be set, proto_id indicates */
+        /* whether UAS (USB) or SOP (PCIe) or ... */
+        if (! piv)
+            n += scnpr(b + n, blen - n, " %s      >>>> Protocol specific "
+                       "port identifier expects protocol\n"
+                       "%s           identifier to be valid and it is not\n",
+                       lip, lip);
+        if (TPROTO_UAS == p_id) {
+            n += scnpr(b + n, blen - n, "%s      USB device address: 0x%x\n",
+                       lip, 0x7f & ip[0]);
+            n += scnpr(b + n, blen - n, "%s      USB interface number: "
+                       "0x%x\n", lip, ip[2]);
+        } else if (TPROTO_SOP == p_id) {
+            n += scnpr(b + n, blen - n, "%s      PCIe routing ID, bus "
+                       "number: 0x%x\n", lip, ip[0]);
+            n += scnpr(b + n, blen - n, "%s          function number: 0x%x\n",
+                       lip, ip[1]);
+            n += scnpr(b + n, blen - n, "%s          [or device number: "
+                       "0x%x, function number: 0x%x]\n", lip,
+                       (0x1f & (ip[1] >> 3)), 0x7 & ip[1]);
+        } else
+            n += scnpr(b + n, blen - n, "%s      >>>> unexpected protocol "
+                       "indentifier: %s\n%s           with Protocol specific "
+                       "port identifier\n", lip,
+                       sg_get_trans_proto_str(p_id, sizeof(e), e), lip);
+        break;
+    case 0xa: /* UUID identifier */
+        if (1 != c_set) {
+            n += scnpr(b + n, blen - n, "%s      << expected binary "
+                       "code_set >>\n", lip);
+            n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+            break;
+        }
+        if ((1 != ((ip[0] >> 4) & 0xf)) || (18 != dlen)) {
+            n += scnpr(b + n, blen - n, "%s      << expected locally "
+                       "assigned UUID, 16 bytes long >>\n", lip);
+            n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+            break;
+        }
+        n += scnpr(b + n, blen - n, "%s      Locally assigned UUID: ", lip);
+        for (m = 0; m < 16; ++m) {
+            if ((4 == m) || (6 == m) || (8 == m) || (10 == m))
+                n += scnpr(b + n, blen - n, "-");
+            n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]);
+        }
+        n += scnpr(b + n, blen - n, "\n");
+        if (do_long) {
+            n += scnpr(b + n, blen - n, "%s      [0x", lip);
+            for (m = 0; m < 16; ++m)
+                n += scnpr(b + n, blen - n, "%02x", (unsigned int)ip[2 + m]);
+            n += scnpr(b + n, blen - n, "]\n");
+        }
+        break;
+    default: /* reserved */
+        n += scnpr(b + n, blen - n, "%s      reserved designator=0x%x\n", lip,
+                   desig_type);
+        n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+        break;
+    }
+    return n;
+}
+
+static int
+decode_sks(const char * lip, const unsigned char * descp, int add_d_len,
+           int sense_key, bool * processedp, int blen, char * b)
+{
+    int progress, pr, rem, n;
+
+    n = 0;
+    if (NULL == lip)
+        lip = "";
+    switch (sense_key) {
+    case SPC_SK_ILLEGAL_REQUEST:
+        if (add_d_len < 6) {
+            n += scnpr(b + n, blen - n, "Field pointer: ");
+            goto too_short;
+        }
+        /* abbreviate to fit on one line */
+        n += scnpr(b + n, blen - n, "Field pointer:\n");
+        n += scnpr(b + n, blen - n, "%s        Error in %s: byte %d", lip,
+                   (descp[4] & 0x40) ? "Command" :
+                                                  "Data parameters",
+                         sg_get_unaligned_be16(descp + 5));
+        if (descp[4] & 0x08) {
+            n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
+        } else
+            n += scnpr(b + n, blen - n, "\n");
+        break;
+    case SPC_SK_HARDWARE_ERROR:
+    case SPC_SK_MEDIUM_ERROR:
+    case SPC_SK_RECOVERED_ERROR:
+        n += scnpr(b + n, blen - n, "Actual retry count: ");
+        if (add_d_len < 6)
+            goto too_short;
+        n += scnpr(b + n, blen - n,"%u\n", sg_get_unaligned_be16(descp + 5));
+        break;
+    case SPC_SK_NO_SENSE:
+    case SPC_SK_NOT_READY:
+        n += scnpr(b + n, blen - n, "Progress indication: ");
+        if (add_d_len < 6)
+            goto too_short;
+        progress = sg_get_unaligned_be16(descp + 5);
+        pr = (progress * 100) / 65536;
+        rem = ((progress * 100) % 65536) / 656;
+        n += scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem);
+        break;
+    case SPC_SK_COPY_ABORTED:
+        n += scnpr(b + n, blen - n, "Segment pointer:\n");
+        if (add_d_len < 6)
+            goto too_short;
+        n += scnpr(b + n, blen - n, "%s        Relative to start of %s, byte "
+                   "%d", lip, (descp[4] & 0x20) ? "segment descriptor" :
+                                                  "parameter list",
+                   sg_get_unaligned_be16(descp + 5));
+        if (descp[4] & 0x08)
+            n += scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
+        else
+            n += scnpr(b + n, blen - n, "\n");
+        break;
+    case SPC_SK_UNIT_ATTENTION:
+        n += scnpr(b + n, blen - n, "Unit attention condition queue:\n");
+        n += scnpr(b + n, blen - n, "%s        overflow flag is %d\n", lip,
+                   !!(descp[4] & 0x1));
+        break;
+    default:
+        n += scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n",
+                   sense_key);
+        *processedp = false;
+        break;
+    }
+    return n;
+
+too_short:
+    n += scnpr(b + n, blen - n, "%s\n", "   >> descriptor too short");
+    *processedp = false;
+    return n;
+}
+
+#define TPGS_STATE_OPTIMIZED 0x0
+#define TPGS_STATE_NONOPTIMIZED 0x1
+#define TPGS_STATE_STANDBY 0x2
+#define TPGS_STATE_UNAVAILABLE 0x3
+#define TPGS_STATE_OFFLINE 0xe
+#define TPGS_STATE_TRANSITIONING 0xf
+
+static int
+decode_tpgs_state(int st, char * b, int blen)
+{
+    switch (st) {
+    case TPGS_STATE_OPTIMIZED:
+        return scnpr(b, blen, "active/optimized");
+    case TPGS_STATE_NONOPTIMIZED:
+        return scnpr(b, blen, "active/non optimized");
+    case TPGS_STATE_STANDBY:
+        return scnpr(b, blen, "standby");
+    case TPGS_STATE_UNAVAILABLE:
+        return scnpr(b, blen, "unavailable");
+    case TPGS_STATE_OFFLINE:
+        return scnpr(b, blen, "offline");
+    case TPGS_STATE_TRANSITIONING:
+        return scnpr(b, blen, "transitioning between states");
+    default:
+        return scnpr(b, blen, "unknown: 0x%x", st);
+    }
+}
+
+static int
+uds_referral_descriptor_str(char * b, int blen, const unsigned char * dp,
+                            int alen, const char * lip)
+{
+    int n = 0;
+    int dlen = alen - 2;
+    int k, j, g, f, tpgd;
+    const unsigned char * tp;
+    uint64_t ull;
+    char c[40];
+
+    if (NULL == lip)
+        lip = "";
+    n += scnpr(b + n, blen - n, "%s   Not all referrals: %d\n", lip,
+               !!(dp[2] & 0x1));
+    dp += 4;
+    for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) {
+        tpgd = dp[3];
+        g = (tpgd * 4) + 20;
+        n += scnpr(b + n, blen - n, "%s    Descriptor %d\n", lip, f);
+        if ((k + g) > dlen) {
+            n += scnpr(b + n, blen - n, "%s      truncated descriptor, "
+                       "stop\n", lip);
+            return n;
+        }
+        ull = sg_get_unaligned_be64(dp + 4);
+        n += scnpr(b + n, blen - n, "%s      first uds LBA: 0x%" PRIx64 "\n",
+                   lip, ull);
+        ull = sg_get_unaligned_be64(dp + 12);
+        n += scnpr(b + n, blen - n, "%s      last uds LBA:  0x%" PRIx64 "\n",
+                   lip, ull);
+        for (j = 0; j < tpgd; ++j) {
+            tp = dp + 20 + (j * 4);
+            decode_tpgs_state(tp[0] & 0xf, c, sizeof(c));
+            n += scnpr(b + n, blen - n, "%s        tpg: %d  state: %s\n",
+                       lip, sg_get_unaligned_be16(tp + 2), c);
+        }
+    }
+    return n;
+}
+
+static const char * dd_usage_reason_str_arr[] = {
+    "Unknown",
+    "resend this and further commands to:",
+    "resend this command to:",
+    "new subsiduary lu added to this administrative lu:",
+    "administrative lu associated with a preferred binding:",
+   };
+
+
+/* Decode descriptor format sense descriptors (assumes sense buffer is
+ * in descriptor format) */
+int
+sg_get_sense_descriptors_str(const char * lip, const unsigned char * sbp,
+                             int sb_len, int blen, char * b)
+{
+    int add_sb_len, add_d_len, desc_len, k, j, sense_key;
+    int n, progress, pr, rem;
+    bool processed;
+    const unsigned char * descp;
+    const char * dtsp = "   >> descriptor too short";
+    const char * eccp = "Extended copy command";
+    const char * ddp = "destination device";
+    char z[64];
+
+    if ((NULL == b) || (blen <= 0))
+        return 0;
+    b[0] = '\0';
+    if (lip)
+        scnpr(z, sizeof(z), "%.60s  ", lip);
+    else
+        scnpr(z, sizeof(z), "  ");
+    if ((sb_len < 8) || (0 == (add_sb_len = sbp[7])))
+        return 0;
+    add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8);
+    sense_key = (sbp[1] & 0xf);
+
+    for (descp = (sbp + 8), k = 0, n = 0;
+         (k < add_sb_len) && (n < blen);
+         k += desc_len, descp += desc_len) {
+        add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1;
+        if ((k + add_d_len + 2) > add_sb_len)
+            add_d_len = add_sb_len - k - 2;
+        desc_len = add_d_len + 2;
+        n += scnpr(b + n, blen - n, "%s  Descriptor type: ", lip);
+        processed = true;
+        switch (descp[0]) {
+        case 0:
+            n += scnpr(b + n, blen - n, "Information: ");
+            if ((add_d_len >= 10) && (0x80 & descp[2])) {
+                n += scnpr(b + n, blen - n, "0x");
+                for (j = 0; j < 8; ++j)
+                    n += scnpr(b + n, blen - n, "%02x", descp[4 + j]);
+                n += scnpr(b + n, blen - n, "\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 1:
+            n += scnpr(b + n, blen - n, "Command specific: ");
+            if (add_d_len >= 10) {
+                n += scnpr(b + n, blen - n, "0x");
+                for (j = 0; j < 8; ++j)
+                    n += scnpr(b + n, blen - n, "%02x", descp[4 + j]);
+                n += scnpr(b + n, blen - n, "\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 2:         /* Sense Key Specific */
+            n += scnpr(b + n, blen - n, "Sense key specific: ");
+            n += decode_sks(lip, descp, add_d_len, sense_key, &processed,
+                            blen - n, b + n);
+            break;
+        case 3:
+            n += scnpr(b + n, blen - n, "Field replaceable unit code: ");
+            if (add_d_len >= 2)
+                n += scnpr(b + n, blen - n, "0x%x\n", descp[3]);
+            else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 4:
+            n += scnpr(b + n, blen - n, "Stream commands: ");
+            if (add_d_len >= 2) {
+                if (descp[3] & 0x80)
+                    n += scnpr(b + n, blen - n, "FILEMARK");
+                if (descp[3] & 0x40)
+                    n += scnpr(b + n, blen - n, "End Of Medium (EOM)");
+                if (descp[3] & 0x20)
+                    n += scnpr(b + n, blen - n, "Incorrect Length Indicator "
+                               "(ILI)");
+                n += scnpr(b + n, blen - n, "\n");
+            } else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 5:
+            n += scnpr(b + n, blen - n, "Block commands: ");
+            if (add_d_len >= 2)
+                n += scnpr(b + n, blen - n, "Incorrect Length Indicator "
+                           "(ILI) %s\n", (descp[3] & 0x20) ? "set" : "clear");
+            else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 6:
+            n += scnpr(b + n, blen - n, "OSD object identification\n");
+            processed = false;
+            break;
+        case 7:
+            n += scnpr(b + n, blen - n, "OSD response integrity check "
+                             "value\n");
+            processed = false;
+            break;
+        case 8:
+            n += scnpr(b + n, blen - n, "OSD attribute identification\n");
+            processed = false;
+            break;
+        case 9:         /* this is defined in SAT (SAT-2) */
+            n += scnpr(b + n, blen - n, "ATA Status Return: ");
+            if (add_d_len >= 12) {
+                int extend, count;
+
+                extend = descp[2] & 1;
+                count = descp[5] + (extend ? (descp[4] << 8) : 0);
+                n += scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s"
+                           "        count=0x%x ", extend, descp[3], lip,
+                           count);
+                if (extend)
+                    n += scnpr(b + n, blen - n,
+                               "lba=0x%02x%02x%02x%02x%02x%02x ",
+                                descp[10], descp[8], descp[6], descp[11],
+                                descp[9], descp[7]);
+                else
+                    n += scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ",
+                               descp[11], descp[9], descp[7]);
+                n += scnpr(b + n, blen - n, "device=0x%x status=0x%x\n",
+                           descp[12], descp[13]);
+            } else {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+            }
+            break;
+        case 0xa:
+           /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */
+            n += scnpr(b + n, blen - n, "Another progress indication: ");
+            if (add_d_len < 6) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            progress = sg_get_unaligned_be16(descp + 6);
+            pr = (progress * 100) / 65536;
+            rem = ((progress * 100) % 65536) / 656;
+            n += scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem);
+            n += scnpr(b + n, blen - n, "%s        [sense_key=0x%x "
+                       "asc,ascq=0x%x,0x%x]\n", lip, descp[2], descp[3],
+                       descp[4]);
+            break;
+        case 0xb:       /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */
+            n += scnpr(b + n, blen - n, "User data segment referral: ");
+            if (add_d_len < 2) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            n += scnpr(b + n, blen - n, "\n");
+            n += uds_referral_descriptor_str(b + n, blen - n, descp,
+                                             add_d_len, lip);
+            break;
+        case 0xc:       /* Added in SPC-4 rev 28 */
+            n += scnpr(b + n, blen - n, "Forwarded sense data\n");
+            if (add_d_len < 2) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            n += scnpr(b + n, blen - n, "%s    FSDT: %s\n", lip,
+                       (descp[2] & 0x80) ? "set" : "clear");
+            j = descp[2] & 0xf;
+            n += scnpr(b + n, blen - n, "%s    Sense data source: ", lip);
+            switch (j) {
+            case 0:
+                n += scnpr(b + n, blen - n, "%s source device\n", eccp);
+                break;
+            case 1:
+            case 2:
+            case 3:
+            case 4:
+            case 5:
+            case 6:
+            case 7:
+                n += scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1);
+                break;
+            default:
+                n += scnpr(b + n, blen - n, "unknown [%d]\n", j);
+            }
+            {
+                char c[480];
+
+                sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c);
+                c[sizeof(c) - 1] = '\0';
+                n += scnpr(b + n, blen - n, "%s    Forwarded status: %s\n",
+                           lip, c);
+                if (add_d_len > 2) {
+                    /* recursing; hope not to get carried away */
+                    n += scnpr(b + n, blen - n, "%s vvvvvvvvvvvvvvvv\n", lip);
+                    sg_get_sense_str(lip, descp + 4, add_d_len - 2, false,
+                                     sizeof(c), c);
+                    n += scnpr(b + n, blen - n, "%s", c);
+                    n += scnpr(b + n, blen - n, "%s ^^^^^^^^^^^^^^^^\n", lip);
+                }
+            }
+            break;
+        case 0xd:       /* Added in SBC-3 rev 36d */
+            /* this descriptor combines descriptors 0, 1, 2 and 3 */
+            n += scnpr(b + n, blen - n, "Direct-access block device\n");
+            if (add_d_len < 28) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            if (0x20 & descp[2])
+                n += scnpr(b + n, blen - n, "%s    ILI (incorrect length "
+                           "indication) set\n", lip);
+            if (0x80 & descp[4]) {
+                n += scnpr(b + n, blen - n, "%s    Sense key specific: ",
+                           lip);
+                n += decode_sks(lip, descp, add_d_len, sense_key, &processed,
+                                blen - n, b + n);
+            }
+            n += scnpr(b + n, blen - n, "%s    Field replaceable unit code: "
+                       "0x%x\n", lip, descp[7]);
+            if (0x80 & descp[2]) {
+                n += scnpr(b + n, blen - n, "%s    Information: 0x", lip);
+                for (j = 0; j < 8; ++j)
+                    n += scnpr(b + n, blen - n, "%02x", descp[8 + j]);
+                n += scnpr(b + n, blen - n, "\n");
+            }
+            n += scnpr(b + n, blen - n, "%s    Command specific: 0x", lip);
+            for (j = 0; j < 8; ++j)
+                n += scnpr(b + n, blen - n, "%02x", descp[16 + j]);
+            n += scnpr(b + n, blen - n, "\n");
+            break;
+        case 0xe:       /* Added in SPC-5 rev 6 (for Bind/Unbind) */
+            n += scnpr(b + n, blen - n, "Device designation\n");
+            j = (int)(sizeof(dd_usage_reason_str_arr) /
+                      sizeof(dd_usage_reason_str_arr[0]));
+            if (descp[3] < j)
+                n += scnpr(b + n, blen - n, "%s    Usage reason: %s\n", lip,
+                           dd_usage_reason_str_arr[descp[3]]);
+            else
+                n += scnpr(b + n, blen - n, "%s    Usage reason: "
+                           "reserved[%d]\n", lip, descp[3]);
+            n += sg_get_designation_descriptor_str(z, descp + 4, descp[1] - 2,
+                                                   true, false, blen - n,
+                                                   b + n);
+            break;
+        case 0xf:       /* Added in SPC-5 rev 10 (for Write buffer) */
+            n += scnpr(b + n, blen - n, "Microcode activation ");
+            if (add_d_len < 6) {
+                n += scnpr(b + n, blen - n, "%s\n", dtsp);
+                processed = false;
+                break;
+            }
+            progress = sg_get_unaligned_be16(descp + 6);
+            n += scnpr(b + n, blen - n, "time: ");
+            if (0 == progress)
+                n += scnpr(b + n, blen - n, "unknown\n");
+            else
+                n += scnpr(b + n, blen - n, "%d seconds\n", progress);
+            break;
+        default:
+            if (descp[0] >= 0x80)
+                n += scnpr(b + n, blen - n, "Vendor specific [0x%x]\n",
+                           descp[0]);
+            else
+                n += scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]);
+            processed = false;
+            break;
+        }
+        if (! processed) {
+            if (add_d_len > 0) {
+                n += scnpr(b + n, blen - n, "%s    ", lip);
+                for (j = 0; j < add_d_len; ++j) {
+                    if ((j > 0) && (0 == (j % 24)))
+                        n += scnpr(b + n, blen - n, "\n%s    ", lip);
+                    n += scnpr(b + n, blen - n, "%02x ", descp[j + 2]);
+                }
+                n += scnpr(b + n, blen - n, "\n");
+            }
+        }
+        if (add_d_len < 0)
+            n += scnpr(b + n, blen - n, "%s    short descriptor\n", lip);
+    }
+    return n;
+}
+
+/* Decode SAT ATA PASS-THROUGH fixed format sense. Shows "+" after 'count'
+ * and/or 'lba' values to indicate that not all data in those fields is shown.
+ * That extra field information may be available in the ATA pass-through
+ * results log page parameter with the corresponding 'log_index'. */
+static int
+sg_get_sense_sat_pt_fixed_str(const char * lip, const unsigned char * sp,
+                              int slen, int blen, char * b)
+{
+    int n = 0;
+    bool extend, count_upper_nz, lba_upper_nz;
+
+    if ((blen < 1) || (slen < 12))
+        return n;
+    if (NULL == lip)
+        lip = "";
+    if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2]))
+        n += scnpr(b + n, blen - n, "%s  >> expected Sense key: Recovered "
+                   "Error ??\n", lip);
+    /* Fixed sense command-specific information field starts at sp + 8 */
+    extend = !!(0x80 & sp[8]);
+    count_upper_nz = !!(0x40 & sp[8]);
+    lba_upper_nz = !!(0x20 & sp[8]);
+    /* Fixed sense information field starts at sp + 3 */
+    n += scnpr(b + n, blen - n, "%s  error=0x%x, status=0x%x, device=0x%x, "
+               "count(7:0)=0x%x%c\n", lip, sp[3], sp[4], sp[5], sp[6],
+               (count_upper_nz ? '+' : ' '));
+    n += scnpr(b + n, blen - n, "%s  extend=%d, log_index=0x%x, "
+               "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", lip, (int)extend,
+               (0xf & sp[8]), sp[9], sp[10], sp[11],
+               (lba_upper_nz ? '+' : ' '));
+    return n;
+}
+
+/* Fetch sense information */
+int
+sg_get_sense_str(const char * lip, const unsigned char * sbp, int sb_len,
+                 bool raw_sinfo, int cblen, char * cbp)
+{
+    bool descriptor_format = false;
+    bool sdat_ovfl = false;
+    bool valid;
+    int len, progress, n, r, pr, rem, blen;
+    unsigned int info;
+    uint8_t resp_code;
+    const char * ebp = NULL;
+    char ebuff[64];
+    char b[256];
+    struct sg_scsi_sense_hdr ssh;
+
+    if ((NULL == cbp) || (cblen <= 0))
+        return 0;
+    else if (1 == cblen) {
+        cbp[0] = '\0';
+        return 0;
+    }
+    blen = sizeof(b);
+    n = 0;
+    if (NULL == lip)
+        lip = "";
+    if ((NULL == sbp) || (sb_len < 1)) {
+            n += scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip);
+            return n;
+    }
+    resp_code = 0x7f & sbp[0];
+    valid = !!(sbp[0] & 0x80);
+    len = sb_len;
+    if (sg_scsi_normalize_sense(sbp, sb_len, &ssh)) {
+        switch (ssh.response_code) {
+        case 0x70:      /* fixed, current */
+            ebp = "Fixed format, current";
+            len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
+            len = (len > sb_len) ? sb_len : len;
+            sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
+            break;
+        case 0x71:      /* fixed, deferred */
+            /* error related to a previous command */
+            ebp = "Fixed format, <<<deferred>>>";
+            len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
+            len = (len > sb_len) ? sb_len : len;
+            sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
+            break;
+        case 0x72:      /* descriptor, current */
+            descriptor_format = true;
+            ebp = "Descriptor format, current";
+            sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+            break;
+        case 0x73:      /* descriptor, deferred */
+            descriptor_format = true;
+            ebp = "Descriptor format, <<<deferred>>>";
+            sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+            break;
+        case 0x0:
+            ebp = "Response code: 0x0 (?)";
+            break;
+        default:
+            scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x",
+                  ssh.response_code);
+            ebp = ebuff;
+            break;
+        }
+        n += scnpr(cbp + n, cblen - n, "%s%s; Sense key: %s\n", lip, ebp,
+                   sg_lib_sense_key_desc[ssh.sense_key]);
+        if (sdat_ovfl)
+            n += scnpr(cbp + n, cblen - n, "%s<<<Sense data overflow>>>\n",
+                       lip);
+        if (descriptor_format) {
+            n += scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+                       sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+            n += sg_get_sense_descriptors_str(lip, sbp, len,
+                                              cblen - n, cbp + n);
+        } else if ((len > 12) && (0 == ssh.asc) &&
+                   (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
+            /* SAT ATA PASS-THROUGH fixed format */
+            n += scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+                       sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+            n += sg_get_sense_sat_pt_fixed_str(lip, sbp, len,
+                                               cblen - n, cbp + n);
+        } else if (len > 2) {   /* fixed format */
+            if (len > 12)
+                n += scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+                           sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+            r = 0;
+            if (strlen(lip) > 0)
+                r += scnpr(b + r, blen - r, "%s", lip);
+            if (len > 6) {
+                info = sg_get_unaligned_be32(sbp + 3);
+                if (valid)
+                    r += scnpr(b + r, blen - r, "  Info fld=0x%x [%u] ",
+                               info, info);
+                else if (info > 0)
+                    r += scnpr(b + r, blen - r, "  Valid=0, Info fld=0x%x "
+                               "[%u] ", info, info);
+            } else
+                info = 0;
+            if (sbp[2] & 0xe0) {
+                if (sbp[2] & 0x80)
+                   r += scnpr(b + r, blen - r, " FMK");
+                            /* current command has read a filemark */
+                if (sbp[2] & 0x40)
+                   r += scnpr(b + r, blen - r, " EOM");
+                            /* end-of-medium condition exists */
+                if (sbp[2] & 0x20)
+                   r += scnpr(b + r, blen - r, " ILI");
+                            /* incorrect block length requested */
+                r += scnpr(b + r, blen - r, "\n");
+            } else if (valid || (info > 0))
+                r += scnpr(b + r, blen - r, "\n");
+            if ((len >= 14) && sbp[14])
+                r += scnpr(b + r, blen - r, "%s  Field replaceable unit "
+                           "code: %d\n", lip, sbp[14]);
+            if ((len >= 18) && (sbp[15] & 0x80)) {
+                /* sense key specific decoding */
+                switch (ssh.sense_key) {
+                case SPC_SK_ILLEGAL_REQUEST:
+                    r += scnpr(b + r, blen - r, "%s  Sense Key Specific: "
+                               "Error in %s: byte %d", lip,
+                               ((sbp[15] & 0x40) ? "Command" :
+                                                   "Data parameters"),
+                             sg_get_unaligned_be16(sbp + 16));
+                    if (sbp[15] & 0x08)
+                        r += scnpr(b + r, blen - r, " bit %d\n",
+                                   sbp[15] & 0x07);
+                    else
+                        r += scnpr(b + r, blen - r, "\n");
+                    break;
+                case SPC_SK_NO_SENSE:
+                case SPC_SK_NOT_READY:
+                    progress = sg_get_unaligned_be16(sbp + 16);
+                    pr = (progress * 100) / 65536;
+                    rem = ((progress * 100) % 65536) / 656;
+                    r += scnpr(b + r, blen - r, "%s  Progress indication: "
+                               "%d.%02d%%\n", lip, pr, rem);
+                    break;
+                case SPC_SK_HARDWARE_ERROR:
+                case SPC_SK_MEDIUM_ERROR:
+                case SPC_SK_RECOVERED_ERROR:
+                    r += scnpr(b + r, blen - r, "%s  Actual retry count: "
+                               "0x%02x%02x\n", lip, sbp[16], sbp[17]);
+                    break;
+                case SPC_SK_COPY_ABORTED:
+                    r += scnpr(b + r, blen - r, "%s  Segment pointer: ", lip);
+                    r += scnpr(b + r, blen - r, "Relative to start of %s, "
+                               "byte %d", ((sbp[15] & 0x20) ?
+                                     "segment descriptor" : "parameter list"),
+                               sg_get_unaligned_be16(sbp + 16));
+                    if (sbp[15] & 0x08)
+                        r += scnpr(b + r, blen - r, " bit %d\n",
+                                   sbp[15] & 0x07);
+                    else
+                        r += scnpr(b + r, blen - r, "\n");
+                    break;
+                case SPC_SK_UNIT_ATTENTION:
+                    r += scnpr(b + r, blen - r, "%s  Unit attention "
+                               "condition queue: ", lip);
+                    r += scnpr(b + r, blen - r, "overflow flag is %d\n",
+                               !!(sbp[15] & 0x1));
+                    break;
+                default:
+                    r += scnpr(b + r, blen - r, "%s  Sense_key: 0x%x "
+                               "unexpected\n", lip, ssh.sense_key);
+                    break;
+                }
+            }
+            if (r > 0)
+                n += scnpr(cbp + n, cblen - n, "%s", b);
+        } else
+            n += scnpr(cbp + n, cblen - n, "%s fixed descriptor length "
+                       "too short, len=%d\n", lip, len);
+    } else {    /* unable to normalise sense buffer, something irregular */
+        if (sb_len < 4) {       /* Too short */
+            n += scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 "
+                       "byte minimum)\n", lip);
+            goto check_raw;
+        }
+        if (0x7f == resp_code) {        /* Vendor specific */
+            n += scnpr(cbp + n, cblen - n, "%sVendor specific sense buffer, "
+                       "in hex:\n", lip);
+            n += hex2str(sbp, sb_len, lip, -1, cblen - n, cbp + n);
+            return n;   /* no need to check raw, just output in hex */
+        }
+        /* non-extended SCSI-1 sense data ?? */
+        r = 0;
+        if (strlen(lip) > 0)
+            r += scnpr(b + r, blen - r, "%s", lip);
+        r += scnpr(b + r, blen - r, "Probably uninitialized data.\n%s  Try "
+                   "to view as SCSI-1 non-extended sense:\n", lip);
+        r += scnpr(b + r, blen - r, "  AdValid=%d  Error class=%d  Error "
+                   "code=%d\n", valid, ((sbp[0] >> 4) & 0x7),
+                   (sbp[0] & 0xf));
+        if (valid)
+            scnpr(b + r, blen - r, "%s  lba=0x%x\n", lip,
+                  sg_get_unaligned_be24(sbp + 1) & 0x1fffff);
+        n += scnpr(cbp + n, cblen - n, "%s\n", b);
+        len = sb_len;
+        if (len > 32)
+            len = 32;   /* trim in case there is a lot of rubbish */
+    }
+check_raw:
+    if (raw_sinfo) {
+        char z[64];
+
+        n += scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex):\n",
+                   lip);
+        if (n >= (cblen - 1))
+            return n;
+        scnpr(z, sizeof(z), "%.50s        ", lip);
+        n += hex2str(sbp, len, z,  -1, cblen - n, cbp + n);
+    }
+    return n;
+}
+
+/* Print sense information */
+void
+sg_print_sense(const char * leadin, const unsigned char * sbp, int sb_len,
+               bool raw_sinfo)
+{
+    uint32_t pg_sz = sg_get_page_size();
+    char *cp;
+    uint8_t *free_cp;
+
+    cp = (char *)sg_memalign(pg_sz, pg_sz, &free_cp, 0);
+    if (NULL == cp)
+        return;
+    sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp);
+    pr2ws("%s", cp);
+    free(free_cp);
+}
+
+/* Following examines exit_status and outputs a clear error message to
+ * warnings_strm (usually stderr) if one is known and returns true.
+ * Otherwise it doesn't print anything and returns false. Note that
+ * if exit_status==0 then returns true but prints nothing and if
+ * exit_status<0 ("some error occurred") false is returned. If leadin is
+ * non-NULL then it is printed before the error message. */
+bool
+sg_if_can2stderr(const char * leadin, int exit_status)
+{
+    const char * s = leadin ? leadin : "";
+
+    if (exit_status < 0)
+        return false;
+    else if (0 == exit_status)
+        return true;
+
+    switch (exit_status) {
+    case SG_LIB_CAT_NOT_READY:          /* 2 */
+        pr2ws("%sDevice not ready\n", s);
+        return true;
+    case SG_LIB_CAT_MEDIUM_HARD:        /* 3 */
+        pr2ws("%sMedium or hardware error\n", s); /* 3 sense keys: Medium, */
+        return true;    /* hardware error or 'Blank check' for tapes */
+    case SG_LIB_CAT_UNIT_ATTENTION:     /* 6 */
+        pr2ws("%sDevice reported 'Unit attention'\n", s);
+        return true;
+    case SG_LIB_CAT_DATA_PROTECT:       /* 7 */
+        pr2ws("%sDevice reported 'Data protect', read-only?\n", s);
+        return true;
+    case SG_LIB_CAT_COPY_ABORTED:       /* 10 */
+        pr2ws("%sCopy aborted\n", s);
+        return true;
+    case SG_LIB_CAT_ABORTED_COMMAND:    /* 11 */
+        pr2ws("%sCommand aborted\n", s);
+        return true;
+    case SG_LIB_CAT_MISCOMPARE:         /* 14 */
+        pr2ws("%sMiscompare\n", s);
+        return true;
+    case SG_LIB_CAT_RES_CONFLICT:       /* 24 */
+        pr2ws("%sReservation conflict\n", s);
+        return true;
+    case SG_LIB_CAT_BUSY:               /* 26 */
+        pr2ws("%sDevice is busy, try again\n", s);
+        return true;
+    case SG_LIB_CAT_TASK_ABORTED:       /* 29 */
+        pr2ws("%sTask aborted\n", s);
+        return true;
+    case SG_LIB_CAT_TIMEOUT:            /* 33 */
+        pr2ws("%sTime out\n", s);
+        return true;
+    case SG_LIB_CAT_PROTECTION:         /* 40 */
+        pr2ws("%sProtection error\n", s);
+        return true;
+    case SG_LIB_NVME_STATUS:            /* 48 */
+        pr2ws("%sNVMe error (non-zero status)\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + EACCES:   /* 50 + */
+        pr2ws("%sPermission denied\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + ENOMEM:
+        pr2ws("%sUtility unable to allocate memory\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + ENOTTY:
+        pr2ws("%sInappropriate I/O control operation\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + EPERM:
+        pr2ws("%sNot permitted\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + EINTR:
+        pr2ws("%sInterrupted system call\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + EIO:
+        pr2ws("%sInput/output error\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + ENODEV:
+        pr2ws("%sNo such device\n", s);
+        return true;
+    case SG_LIB_OS_BASE_ERR + ENOENT:
+        pr2ws("%sNo such file or directory\n", s);
+        return true;
+    default:
+        return false;
+    }
+    return false;
+}
+
+/* If os_err_num is within bounds then the returned value is 'os_err_num +
+ * SG_LIB_OS_BASE_ERR' otherwise -1 is returned. If os_err_num is 0 then 0
+ * is returned. */
+int
+sg_convert_errno(int os_err_num)
+{
+    if (os_err_num <= 0) {
+        if (os_err_num < -1)
+            return -1;
+        return os_err_num;
+    }
+    if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR))
+        return SG_LIB_OS_BASE_ERR + os_err_num;
+    return -1;
+}
+
+/* See description in sg_lib.h header file */
+bool
+sg_scsi_normalize_sense(const unsigned char * sbp, int sb_len,
+                        struct sg_scsi_sense_hdr * sshp)
+{
+    uint8_t resp_code;
+    if (sshp)
+        memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
+    if ((NULL == sbp) || (sb_len < 1))
+        return false;
+    resp_code = 0x7f & sbp[0];
+    if ((resp_code < 0x70) || (resp_code > 0x73))
+        return false;
+    if (sshp) {
+        sshp->response_code = resp_code;
+        if (sshp->response_code >= 0x72) {  /* descriptor format */
+            if (sb_len > 1)
+                sshp->sense_key = (0xf & sbp[1]);
+            if (sb_len > 2)
+                sshp->asc = sbp[2];
+            if (sb_len > 3)
+                sshp->ascq = sbp[3];
+            if (sb_len > 7)
+                sshp->additional_length = sbp[7];
+        } else {                              /* fixed format */
+            if (sb_len > 2)
+                sshp->sense_key = (0xf & sbp[2]);
+            if (sb_len > 7) {
+                sb_len = (sb_len < (sbp[7] + 8)) ? sb_len : (sbp[7] + 8);
+                if (sb_len > 12)
+                    sshp->asc = sbp[12];
+                if (sb_len > 13)
+                    sshp->ascq = sbp[13];
+            }
+        }
+    }
+    return true;
+}
+
+/* Returns a SG_LIB_CAT_* value. If cannot decode sense buffer (sbp) or a
+ * less common sense key then return SG_LIB_CAT_SENSE .*/
+int
+sg_err_category_sense(const unsigned char * sbp, int sb_len)
+{
+    struct sg_scsi_sense_hdr ssh;
+
+    if ((sbp && (sb_len > 2)) &&
+        (sg_scsi_normalize_sense(sbp, sb_len, &ssh))) {
+        switch (ssh.sense_key) {        /* 0 to 0x1f */
+        case SPC_SK_NO_SENSE:
+            return SG_LIB_CAT_NO_SENSE;
+        case SPC_SK_RECOVERED_ERROR:
+            return SG_LIB_CAT_RECOVERED;
+        case SPC_SK_NOT_READY:
+            return SG_LIB_CAT_NOT_READY;
+        case SPC_SK_MEDIUM_ERROR:
+        case SPC_SK_HARDWARE_ERROR:
+        case SPC_SK_BLANK_CHECK:
+            return SG_LIB_CAT_MEDIUM_HARD;
+        case SPC_SK_UNIT_ATTENTION:
+            return SG_LIB_CAT_UNIT_ATTENTION;
+            /* used to return SG_LIB_CAT_MEDIA_CHANGED when ssh.asc==0x28 */
+        case SPC_SK_ILLEGAL_REQUEST:
+            if ((0x20 == ssh.asc) && (0x0 == ssh.ascq))
+                return SG_LIB_CAT_INVALID_OP;
+            else
+                return SG_LIB_CAT_ILLEGAL_REQ;
+            break;
+        case SPC_SK_ABORTED_COMMAND:
+            if (0x10 == ssh.asc)
+                return SG_LIB_CAT_PROTECTION;
+            else
+                return SG_LIB_CAT_ABORTED_COMMAND;
+        case SPC_SK_MISCOMPARE:
+            return SG_LIB_CAT_MISCOMPARE;
+        case SPC_SK_DATA_PROTECT:
+            return SG_LIB_CAT_DATA_PROTECT;
+        case SPC_SK_COPY_ABORTED:
+            return SG_LIB_CAT_COPY_ABORTED;
+        case SPC_SK_COMPLETED:
+        case SPC_SK_VOLUME_OVERFLOW:
+            return SG_LIB_CAT_SENSE;
+        default:
+            ;   /* reserved and vendor specific sense keys fall through */
+        }
+    }
+    return SG_LIB_CAT_SENSE;
+}
+
+/* Beware: gives wrong answer for variable length command (opcode=0x7f) */
+int
+sg_get_command_size(unsigned char opcode)
+{
+    switch ((opcode >> 5) & 0x7) {
+    case 0:
+        return 6;
+    case 1: case 2: case 6: case 7:
+        return 10;
+    case 3: case 5:
+        return 12;
+        break;
+    case 4:
+        return 16;
+    default:
+        return 10;
+    }
+}
+
+void
+sg_get_command_name(const unsigned char * cmdp, int peri_type, int buff_len,
+                    char * buff)
+{
+    int service_action;
+
+    if ((NULL == buff) || (buff_len < 1))
+        return;
+    else if (1 == buff_len) {
+        buff[0] = '\0';
+        return;
+    }
+    if (NULL == cmdp) {
+        scnpr(buff, buff_len, "%s", "<null> command pointer");
+        return;
+    }
+    service_action = (SG_VARIABLE_LENGTH_CMD == cmdp[0]) ?
+                     sg_get_unaligned_be16(cmdp + 8) : (cmdp[1] & 0x1f);
+    sg_get_opcode_sa_name(cmdp[0], service_action, peri_type, buff_len, buff);
+}
+
+struct op_code2sa_t {
+    int op_code;
+    int pdt_match;      /* -1->all; 0->disk,ZBC,RCB, 1->tape+adc+smc */
+    struct sg_lib_value_name_t * arr;
+    const char * prefix;
+};
+
+static struct op_code2sa_t op_code2sa_arr[] = {
+    {SG_VARIABLE_LENGTH_CMD, -1, sg_lib_variable_length_arr, NULL},
+    {SG_MAINTENANCE_IN, -1, sg_lib_maint_in_arr, NULL},
+    {SG_MAINTENANCE_OUT, -1, sg_lib_maint_out_arr, NULL},
+    {SG_SERVICE_ACTION_IN_12, -1, sg_lib_serv_in12_arr, NULL},
+    {SG_SERVICE_ACTION_OUT_12, -1, sg_lib_serv_out12_arr, NULL},
+    {SG_SERVICE_ACTION_IN_16, -1, sg_lib_serv_in16_arr, NULL},
+    {SG_SERVICE_ACTION_OUT_16, -1, sg_lib_serv_out16_arr, NULL},
+    {SG_SERVICE_ACTION_BIDI, -1, sg_lib_serv_bidi_arr, NULL},
+    {SG_PERSISTENT_RESERVE_IN, -1, sg_lib_pr_in_arr, "Persistent reserve in"},
+    {SG_PERSISTENT_RESERVE_OUT, -1, sg_lib_pr_out_arr,
+     "Persistent reserve out"},
+    {SG_3PARTY_COPY_OUT, -1, sg_lib_xcopy_sa_arr, NULL},
+    {SG_3PARTY_COPY_IN, -1, sg_lib_rec_copy_sa_arr, NULL},
+    {SG_READ_BUFFER, -1, sg_lib_read_buff_arr, "Read buffer(10)"},
+    {SG_READ_BUFFER_16, -1, sg_lib_read_buff_arr, "Read buffer(16)"},
+    {SG_READ_ATTRIBUTE, -1, sg_lib_read_attr_arr, "Read attribute"},
+    {SG_READ_POSITION, 1, sg_lib_read_pos_arr, "Read position"},
+    {SG_SANITIZE, 0, sg_lib_sanitize_sa_arr, "Sanitize"},
+    {SG_WRITE_BUFFER, -1, sg_lib_write_buff_arr, "Write buffer"},
+    {SG_ZONING_IN, 0, sg_lib_zoning_in_arr, NULL},
+    {SG_ZONING_OUT, 0, sg_lib_zoning_out_arr, NULL},
+    {0xffff, -1, NULL, NULL},
+};
+
+void
+sg_get_opcode_sa_name(unsigned char cmd_byte0, int service_action,
+                      int peri_type, int buff_len, char * buff)
+{
+    int d_pdt;
+    const struct sg_lib_value_name_t * vnp;
+    const struct op_code2sa_t * osp;
+    char b[80];
+
+    if ((NULL == buff) || (buff_len < 1))
+        return;
+    else if (1 == buff_len) {
+        buff[0] = '\0';
+        return;
+    }
+
+    if (peri_type < 0)
+        peri_type = 0;
+    d_pdt = sg_lib_pdt_decay(peri_type);
+    for (osp = op_code2sa_arr; osp->arr; ++osp) {
+        if ((int)cmd_byte0 == osp->op_code) {
+            if ((osp->pdt_match < 0) || (d_pdt == osp->pdt_match)) {
+                vnp = get_value_name(osp->arr, service_action, peri_type);
+                if (vnp) {
+                    if (osp->prefix)
+                        scnpr(buff, buff_len, "%s, %s", osp->prefix,
+                              vnp->name);
+                    else
+                        scnpr(buff, buff_len, "%s", vnp->name);
+                } else {
+                    sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b);
+                    scnpr(buff, buff_len, "%s service action=0x%x", b,
+                          service_action);
+                }
+            } else
+                sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
+            return;
+        }
+    }
+    sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
+}
+
+void
+sg_get_opcode_name(unsigned char cmd_byte0, int peri_type, int buff_len,
+                   char * buff)
+{
+    const struct sg_lib_value_name_t * vnp;
+    int grp;
+
+    if ((NULL == buff) || (buff_len < 1))
+        return;
+    else if (1 == buff_len) {
+        buff[0] = '\0';
+        return;
+    }
+    if (SG_VARIABLE_LENGTH_CMD == cmd_byte0) {
+        scnpr(buff, buff_len, "%s", "Variable length");
+        return;
+    }
+    grp = (cmd_byte0 >> 5) & 0x7;
+    switch (grp) {
+    case 0:
+    case 1:
+    case 2:
+    case 4:
+    case 5:
+        vnp = get_value_name(sg_lib_normal_opcodes, cmd_byte0, peri_type);
+        if (vnp)
+            scnpr(buff, buff_len, "%s", vnp->name);
+        else
+            scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
+        break;
+    case 3:
+        scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0);
+        break;
+    case 6:
+    case 7:
+        scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0);
+        break;
+    default:
+        scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
+        break;
+    }
+}
+
+/* Iterates to next designation descriptor in the device identification
+ * VPD page. The 'initial_desig_desc' should point to start of first
+ * descriptor with 'page_len' being the number of valid bytes in that
+ * and following descriptors. To start, 'off' should point to a negative
+ * value, thereafter it should point to the value yielded by the previous
+ * call. If 0 returned then 'initial_desig_desc + *off' should be a valid
+ * descriptor; returns -1 if normal end condition and -2 for an abnormal
+ * termination. Matches association, designator_type and/or code_set when
+ * any of those values are greater than or equal to zero. */
+int
+sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len,
+                   int * off, int m_assoc, int m_desig_type, int m_code_set)
+{
+    bool fltr = ((m_assoc >= 0) || (m_desig_type >= 0) || (m_code_set >= 0));
+    int k = *off;
+    const unsigned char * bp = initial_desig_desc;
+
+    while ((k + 3) < page_len) {
+        k = (k < 0) ? 0 : (k + bp[k + 3] + 4);
+        if ((k + 4) > page_len)
+            break;
+        if (fltr) {
+            if (m_code_set >= 0) {
+                if ((bp[k] & 0xf) != m_code_set)
+                    continue;
+            }
+            if (m_assoc >= 0) {
+                if (((bp[k + 1] >> 4) & 0x3) != m_assoc)
+                    continue;
+            }
+            if (m_desig_type >= 0) {
+                if ((bp[k + 1] & 0xf) != m_desig_type)
+                    continue;
+            }
+        }
+        *off = k;
+        return 0;
+    }
+    return (k == page_len) ? -1 : -2;
+}
+
+static const char * const bad_sense_cat = "Bad sense category";
+
+/* Yield string associated with sense category. Returns 'buff' (or pointer
+ * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then
+ * yield "Sense category: <sense_cat>" string. */
+const char *
+sg_get_category_sense_str(int sense_cat, int buff_len, char * buff,
+                          int verbose)
+{
+    int n;
+
+    if (NULL == buff)
+        return bad_sense_cat;
+    if (buff_len <= 0)
+        return buff;
+    switch (sense_cat) {
+    case SG_LIB_CAT_CLEAN:              /* 0 */
+        scnpr(buff, buff_len, "No errors");
+        break;
+    case SG_LIB_SYNTAX_ERROR:           /* 1 */
+        scnpr(buff, buff_len, "Syntax error");
+        break;
+    case SG_LIB_CAT_NOT_READY:          /* 2 */
+        n = scnpr(buff, buff_len, "Not ready");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_MEDIUM_HARD:        /* 3 */
+        n = scnpr(buff, buff_len, "Medium or hardware error");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key (plus blank check)");
+        break;
+    case SG_LIB_CAT_ILLEGAL_REQ:        /* 5 */
+        n = scnpr(buff, buff_len, "Illegal request");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key, apart from Invalid "
+                  "opcode");
+        break;
+    case SG_LIB_CAT_UNIT_ATTENTION:     /* 6 */
+        n = scnpr(buff, buff_len, "Unit attention");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_DATA_PROTECT:       /* 7 */
+        n = scnpr(buff, buff_len, "Data protect");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key, write protected "
+                     "media?");
+        break;
+    case SG_LIB_CAT_INVALID_OP:         /* 9 */
+        n = scnpr(buff, buff_len, "Illegal request, invalid opcode");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_COPY_ABORTED:       /* 10 */
+        n = scnpr(buff, buff_len, "Copy aborted");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_ABORTED_COMMAND:    /* 11 */
+        n = scnpr(buff, buff_len, "Aborted command");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key, other than "
+                     "protection related (asc=0x10)");
+        break;
+    case SG_LIB_CAT_MISCOMPARE:         /* 14 */
+        n = scnpr(buff, buff_len, "Miscompare");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_FILE_ERROR:             /* 15 */
+        scnpr(buff, buff_len, "File error");
+        break;
+    case SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO:  /* 17 */
+        scnpr(buff, buff_len, "Illegal request with info");
+        break;
+    case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO:  /* 18 */
+        scnpr(buff, buff_len, "Medium or hardware error with info");
+        break;
+    case SG_LIB_CAT_NO_SENSE:           /* 20 */
+        n = scnpr(buff, buff_len, "No sense key");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " probably additional sense "
+                     "information");
+        break;
+    case SG_LIB_CAT_RECOVERED:          /* 21 */
+        n = scnpr(buff, buff_len, "Recovered error");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " sense key");
+        break;
+    case SG_LIB_CAT_RES_CONFLICT:       /* 24 */
+        n = scnpr(buff, buff_len, "Reservation conflict");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_CONDITION_MET:      /* 25 */
+        n = scnpr(buff, buff_len, "Condition met");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_BUSY:               /* 26 */
+        n = scnpr(buff, buff_len, "Busy");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_TS_FULL:            /* 27 */
+        n = scnpr(buff, buff_len, "Task set full");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_ACA_ACTIVE:         /* 28 */
+        n = scnpr(buff, buff_len, "ACA active");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_TASK_ABORTED:       /* 29 */
+        n = scnpr(buff, buff_len, "Task aborted");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " SCSI status");
+        break;
+    case SG_LIB_CAT_TIMEOUT:            /* 33 */
+        scnpr(buff, buff_len, "SCSI command timeout");
+        break;
+    case SG_LIB_CAT_PROTECTION:         /* 40 */
+        n = scnpr(buff, buff_len, "Aborted command, protection");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " information (PI) problem");
+        break;
+    case SG_LIB_CAT_PROTECTION_WITH_INFO: /* 41 */
+        n = scnpr(buff, buff_len, "Aborted command with info, protection");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " information (PI) problem");
+        break;
+    case SG_LIB_CAT_MALFORMED:          /* 97 */
+        n = scnpr(buff, buff_len, "Malformed response");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, " to SCSI command");
+        break;
+    case SG_LIB_CAT_SENSE:              /* 98 */
+        n = scnpr(buff, buff_len, "Some other sense data problem");
+        if (verbose && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, ", try '-v' option for more "
+                     "information");
+        break;
+    case SG_LIB_CAT_OTHER:              /* 99 */
+        n = scnpr(buff, buff_len, "Some other error/warning has occurred");
+        if ((0 == verbose) && (n < (buff_len - 1)))
+            scnpr(buff + n, buff_len - n, ", possible transport of driver "
+                     "issue");
+        break;
+    default:
+        if ((sense_cat > SG_LIB_OS_BASE_ERR) &&
+            (sense_cat < (SG_LIB_OS_BASE_ERR + 47))) {
+            int k = sense_cat - SG_LIB_OS_BASE_ERR;
+
+            n = scnpr(buff, buff_len, "OS error: %s [%d]", safe_strerror(k),
+                      k);
+        } else {
+            n = scnpr(buff, buff_len, "Sense category: %d", sense_cat);
+            if ((0 == verbose) && (n < (buff_len - 1)))
+                scnpr(buff + n, buff_len - n, ", try '-v' option for more "
+                      "information");
+        }
+        break;
+    }
+    return buff;
+}
+
+static const char * sg_sfs_spc_reserved = "SPC Reserved";
+static const char * sg_sfs_sbc_reserved = "SBC Reserved";
+static const char * sg_sfs_ssc_reserved = "SSC Reserved";
+static const char * sg_sfs_zbc_reserved = "ZBC Reserved";
+static const char * sg_sfs_reserved = "Reserved";
+
+/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31)
+ * returns pointer to string (same as 'buff') associated with 'sfs_code'.
+ * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match
+ * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL
+ * then where it points is set to true if a match is found else it is set to
+ * false. If 'buff' is not NULL then in the case of a match a descriptive
+ * string is written to 'buff' while if there is not a not then a string
+ * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC
+ * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL.
+ * Example:
+ *    char b[64];
+ *    ...
+ *    printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0));
+ */
+const char *
+sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, char * buff,
+               bool * foundp, int verbose)
+{
+    const struct sg_lib_value_name_t * vnp = NULL;
+    int n = 0;
+    int my_pdt;
+
+    if ((NULL == buff) || (buff_len < 1)) {
+        if (foundp)
+            *foundp = false;
+        return NULL;
+    } else if (1 == buff_len) {
+        buff[0] = '\0';
+        if (foundp)
+            *foundp = false;
+        return NULL;
+    }
+    my_pdt = ((peri_type < -1) || (peri_type > 0x1f)) ? -2 : peri_type;
+    vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt);
+    if (vnp && (-2 != my_pdt)) {
+        if (peri_type != vnp->peri_dev_type)
+            vnp = NULL;         /* shouldn't really happen */
+    }
+    if (foundp)
+        *foundp = vnp ? true : false;
+    if (sfs_code < 0x100) {             /* SPC Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "SPC %s", vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved);
+    } else if (sfs_code < 0x200) {      /* SBC Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "SBC %s", vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved);
+    } else if (sfs_code < 0x300) {      /* SSC Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "SSC %s", vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved);
+    } else if (sfs_code < 0x400) {      /* ZBC Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "ZBC %s", vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved);
+    } else {                            /* Other SCSI Feature Sets */
+        if (vnp) {
+            if (verbose)
+                n += scnpr(buff, buff_len, "[unrecognized PDT] %s",
+                           vnp->name);
+            else
+                n += scnpr(buff, buff_len, "%s", vnp->name);
+        } else
+            n += scnpr(buff, buff_len, "%s", sg_sfs_reserved);
+
+    }
+    if (verbose > 4)
+        pr2serr("%s: length of returned string (n) %d\n", __func__, n);
+    return buff;
+}
+
+/* This is a heuristic that takes into account the command bytes and length
+ * to decide whether the presented unstructured sequence of bytes could be
+ * a SCSI command. If so it returns true otherwise false. Vendor specific
+ * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed
+ * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The
+ * only SCSI commands considered above 16 bytes of length 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.
+ * No NVMe commands (64 bytes long plus some extra added by some OSes) have
+ * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS
+ * structures that are sent across the wire. The FIS register structure is
+ * used to move a command from a SATA host to device, but the ATA 'command'
+ * is not the first byte. So it is harder to say what will happen if a
+ * FIS structure is presented as a SCSI command, hopfully there is a low
+ * probability this function will yield true in that case. */
+bool
+sg_is_scsi_cdb(const uint8_t * cdbp, int clen)
+{
+    int ilen, sa;
+    uint8_t opcode;
+    uint8_t top3bits;
+
+    if (clen < 6)
+        return false;
+    opcode = cdbp[0];
+    top3bits = opcode >> 5;
+    if (0x3 == top3bits) {
+        if ((clen < 12) || (clen % 4))
+            return false;       /* must be modulo 4 and 12 or more bytes */
+        switch (opcode) {
+        case 0x7e:      /* Extended cdb (XCDB) */
+            ilen = 4 + sg_get_unaligned_be16(cdbp + 2);
+            return (ilen == clen);
+        case 0x7f:      /* Variable Length cdb */
+            ilen = 8 + cdbp[7];
+            sa = sg_get_unaligned_be16(cdbp + 8);
+            /* service action (sa) 0x0 is reserved */
+            return ((ilen == clen) && sa);
+        default:
+            return false;
+        }
+    } else if (clen <= 16) {
+        switch (clen) {
+        case 6:
+            if (top3bits > 0x5)         /* vendor */
+                return true;
+            return (0x0 == top3bits);   /* 6 byte cdb */
+        case 10:
+            if (top3bits > 0x5)         /* vendor */
+                return true;
+            return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */
+        case 16:
+            if (top3bits > 0x5)         /* vendor */
+                return true;
+            return (0x4 == top3bits);   /* 16 byte cdb */
+        case 12:
+            if (top3bits > 0x5)         /* vendor */
+                return true;
+            return (0x5 == top3bits);   /* 12 byte cdb */
+        default:
+            return false;
+        }
+    }
+    /* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or
+     * opcode > 0x7f). */
+    return false;
+}
+
+/* Yield string associated with NVMe command status value in sct_sc. It
+ * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25
+ * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC).
+ * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found
+ * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated.
+ * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/
+char *
+sg_get_nvme_cmd_status_str(uint16_t sct_sc, int b_len, char * b)
+{
+    int k;
+    uint16_t s = 0x3ff & sct_sc;
+    const struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr;
+
+    if ((b_len <= 0) || (NULL == b))
+        return b;
+    else if (1 == b_len) {
+        b[0] = '\0';
+        return b;
+    }
+    for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) {
+        if (s == (uint16_t)vp->value) {
+            strncpy(b, vp->name, b_len);
+            b[b_len - 1] = '\0';
+            return b;
+        }
+    }
+    if (k >= 1000)
+        pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n",
+                        __func__);
+    snprintf(b, b_len, "Reserved [0x%x]", sct_sc);
+    return b;
+}
+
+/* Attempts to map NVMe status value ((SCT << 8) | SC) to SCSI status,
+ * sense_key, asc and ascq tuple. If successful returns true and writes to
+ * non-NULL pointer arguments; otherwise returns false. */
+bool
+sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p,
+                    uint8_t * asc_p, uint8_t * ascq_p)
+{
+    int k, ind;
+    uint16_t s = 0x3ff & sct_sc;
+    struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr;
+    struct sg_lib_4tuple_u8 * mp = sg_lib_scsi_status_sense_arr;
+
+    for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) {
+        if (s == (uint16_t)vp->value)
+            break;
+    }
+    if (k >= 1000) {
+        pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n",
+              __func__);
+        return false;
+    }
+    if (NULL == vp->name)
+        return false;
+    ind = vp->peri_dev_type;
+
+
+    for (k = 0; (0xff != mp->t2) && k < 1000; ++k, ++mp)
+        ;       /* count entries for valid index range */
+    if (k >= 1000) {
+        pr2ws("%s: where is sentinel for sg_lib_scsi_status_sense_arr ??\n",
+              __func__);
+        return false;
+    } else if (ind >= k)
+        return false;
+    mp = sg_lib_scsi_status_sense_arr + ind;
+    if (status_p)
+        *status_p = mp->t1;
+    if (sk_p)
+        *sk_p = mp->t2;
+    if (asc_p)
+        *asc_p = mp->t3;
+    if (ascq_p)
+        *ascq_p = mp->t4;
+    return true;
+}
+
+/* safe_strerror() contributed by Clayton Weaver <cgweav at email dot com>
+ * Allows for situation in which strerror() is given a wild value (or the
+ * C library is incomplete) and returns NULL. Still not thread safe.
+ */
+
+static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ',
+                               'e', 'r', 'r', 'n', 'o', ':', ' ', 0};
+
+char *
+safe_strerror(int errnum)
+{
+    size_t len;
+    char * errstr;
+
+    if (errnum < 0)
+        errnum = -errnum;
+    errstr = strerror(errnum);
+    if (NULL == errstr) {
+        len = strlen(safe_errbuf);
+        scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum);
+        return safe_errbuf;
+    }
+    return errstr;
+}
+
+static void
+trimTrailingSpaces(char * b)
+{
+    int k;
+
+    for (k = ((int)strlen(b) - 1); k >= 0; --k) {
+        if (' ' != b[k])
+            break;
+    }
+    if ('\0' != b[k + 1])
+        b[k + 1] = '\0';
+}
+
+/* Note the ASCII-hex output goes to stdout. [Most other output from functions
+ * in this file go to sg_warnings_strm (default stderr).]
+ * 'no_ascii' allows for 3 output types:
+ *     > 0     each line has address then up to 16 ASCII-hex bytes
+ *     = 0     in addition, the bytes are listed in ASCII to the right
+ *     < 0     only the ASCII-hex bytes are listed (i.e. without address) */
+static void
+dStrHexFp(const char* str, int len, int no_ascii, FILE * fp)
+{
+    const char * p = str;
+    const char * formatstr;
+    unsigned char c;
+    char buff[82];
+    int a = 0;
+    int bpstart = 5;
+    const int cpstart = 60;
+    int cpos = cpstart;
+    int bpos = bpstart;
+    int i, k, blen;
+
+    if (len <= 0)
+        return;
+    blen = (int)sizeof(buff);
+    if (0 == no_ascii)  /* address at left and ASCII at right */
+        formatstr = "%.76s\n";
+    else                        /* previously when > 0 str was "%.58s\n" */
+        formatstr = "%s\n";     /* when < 0 str was: "%.48s\n" */
+    memset(buff, ' ', 80);
+    buff[80] = '\0';
+    if (no_ascii < 0) {
+        bpstart = 0;
+        bpos = bpstart;
+        for (k = 0; k < len; k++) {
+            c = *p++;
+            if (bpos == (bpstart + (8 * 3)))
+                bpos++;
+            scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c);
+            buff[bpos + 2] = ' ';
+            if ((k > 0) && (0 == ((k + 1) % 16))) {
+                trimTrailingSpaces(buff);
+                fprintf(fp, formatstr, buff);
+                bpos = bpstart;
+                memset(buff, ' ', 80);
+            } else
+                bpos += 3;
+        }
+        if (bpos > bpstart) {
+            buff[bpos + 2] = '\0';
+            trimTrailingSpaces(buff);
+            fprintf(fp, "%s\n", buff);
+        }
+        return;
+    }
+    /* no_ascii>=0, start each line with address (offset) */
+    k = scnpr(buff + 1, blen - 1, "%.2x", a);
+    buff[k + 1] = ' ';
+
+    for (i = 0; i < len; i++) {
+        c = *p++;
+        bpos += 3;
+        if (bpos == (bpstart + (9 * 3)))
+            bpos++;
+        scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c);
+        buff[bpos + 2] = ' ';
+        if (no_ascii)
+            buff[cpos++] = ' ';
+        else {
+            if (! my_isprint(c))
+                c = '.';
+            buff[cpos++] = c;
+        }
+        if (cpos > (cpstart + 15)) {
+            if (no_ascii)
+                trimTrailingSpaces(buff);
+            fprintf(fp, formatstr, buff);
+            bpos = bpstart;
+            cpos = cpstart;
+            a += 16;
+            memset(buff, ' ', 80);
+            k = scnpr(buff + 1, blen - 1, "%.2x", a);
+            buff[k + 1] = ' ';
+        }
+    }
+    if (cpos > cpstart) {
+        buff[cpos] = '\0';
+        if (no_ascii)
+            trimTrailingSpaces(buff);
+        fprintf(fp, "%s\n", buff);
+    }
+}
+
+void
+dStrHex(const char* str, int len, int no_ascii)
+{
+    dStrHexFp(str, len, no_ascii, stdout);
+}
+
+void
+dStrHexErr(const char* str, int len, int no_ascii)
+{
+    dStrHexFp(str, len, no_ascii,
+              (sg_warnings_strm ? sg_warnings_strm : stderr));
+}
+
+#define DSHS_LINE_BLEN 160
+#define DSHS_BPL 16
+
+/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space
+ * separated) to 'b' not to exceed 'b_len' characters. Each line
+ * starts with 'leadin' (NULL for no leadin) and there are 16 bytes
+ * per line with an extra space between the 8th and 9th bytes. 'format'
+ * is 0 for repeat in printable ASCII ('.' for non printable) to
+ * right of each line; 1 don't (so just output ASCII hex). Returns
+ * number of bytes written to 'b' excluding the trailing '\0'. */
+int
+dStrHexStr(const char * str, int len, const char * leadin, int format,
+           int b_len, char * b)
+{
+    unsigned char c;
+    int bpstart, bpos, k, n, prior_ascii_len;
+    bool want_ascii;
+    char buff[DSHS_LINE_BLEN + 2];
+    char a[DSHS_BPL + 1];
+    const char * p = str;
+
+    if (len <= 0) {
+        if (b_len > 0)
+            b[0] = '\0';
+        return 0;
+    }
+    if (b_len <= 0)
+        return 0;
+    want_ascii = !format;
+    if (want_ascii) {
+        memset(a, ' ', DSHS_BPL);
+        a[DSHS_BPL] = '\0';
+    }
+    if (leadin) {
+        bpstart = strlen(leadin);
+        /* Cap leadin at (DSHS_LINE_BLEN - 70) characters */
+        if (bpstart > (DSHS_LINE_BLEN - 70))
+            bpstart = DSHS_LINE_BLEN - 70;
+    } else
+        bpstart = 0;
+    bpos = bpstart;
+    prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1;
+    n = 0;
+    memset(buff, ' ', DSHS_LINE_BLEN);
+    buff[DSHS_LINE_BLEN] = '\0';
+    if (bpstart > 0)
+        memcpy(buff, leadin, bpstart);
+    for (k = 0; k < len; k++) {
+        c = *p++;
+        if (bpos == (bpstart + ((DSHS_BPL / 2) * 3)))
+            bpos++;     /* for extra space in middle of each line's hex */
+        scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x",
+              (int)(unsigned char)c);
+        buff[bpos + 2] = ' ';
+        if (want_ascii)
+            a[k % DSHS_BPL] = my_isprint(c) ? c : '.';
+        if ((k > 0) && (0 == ((k + 1) % DSHS_BPL))) {
+            trimTrailingSpaces(buff);
+            if (want_ascii) {
+                n += scnpr(b + n, b_len - n, "%-*s   %s\n", prior_ascii_len,
+                           buff, a);
+                memset(a, ' ', DSHS_BPL);
+            } else
+                n += scnpr(b + n, b_len - n, "%s\n", buff);
+            if (n >= (b_len - 1))
+                return n;
+            memset(buff, ' ', DSHS_LINE_BLEN);
+            bpos = bpstart;
+            if (bpstart > 0)
+                memcpy(buff, leadin, bpstart);
+        } else
+            bpos += 3;
+    }
+    if (bpos > bpstart) {
+        trimTrailingSpaces(buff);
+        if (want_ascii)
+            n += scnpr(b + n, b_len - n, "%-*s   %s\n", prior_ascii_len,
+                       buff, a);
+        else
+            n += scnpr(b + n, b_len - n, "%s\n", buff);
+    }
+    return n;
+}
+
+void
+hex2stdout(const uint8_t * b_str, int len, int no_ascii)
+{
+    dStrHex((const char *)b_str, len, no_ascii);
+}
+
+void
+hex2stderr(const uint8_t * b_str, int len, int no_ascii)
+{
+    dStrHexErr((const char *)b_str, len, no_ascii);
+}
+
+int
+hex2str(const uint8_t * b_str, int len, const char * leadin, int format,
+        int b_len, char * b)
+{
+    return dStrHexStr((const char *)b_str, len, leadin, format, b_len, b);
+}
+
+/* Returns true when executed on big endian machine; else returns false.
+ * Useful for displaying ATA identify words (which need swapping on a
+ * big endian machine). */
+bool
+sg_is_big_endian()
+{
+    union u_t {
+        uint16_t s;
+        unsigned char c[sizeof(uint16_t)];
+    } u;
+
+    u.s = 0x0102;
+    return (u.c[0] == 0x01);     /* The lowest address contains
+                                    the most significant byte */
+}
+
+bool
+sg_all_zeros(const uint8_t * bp, int b_len)
+{
+    if ((NULL == bp) || (b_len <= 0))
+        return false;
+    for (--b_len; b_len >= 0; --b_len) {
+        if (0x0 != bp[b_len])
+            return false;
+    }
+    return true;
+}
+
+bool
+sg_all_ffs(const uint8_t * bp, int b_len)
+{
+    if ((NULL == bp) || (b_len <= 0))
+        return false;
+    for (--b_len; b_len >= 0; --b_len) {
+        if (0xff != bp[b_len])
+            return false;
+    }
+    return true;
+}
+
+static uint16_t
+swapb_uint16(uint16_t u)
+{
+    uint16_t r;
+
+    r = (u >> 8) & 0xff;
+    r |= ((u & 0xff) << 8);
+    return r;
+}
+
+/* Note the ASCII-hex output goes to stdout. [Most other output from functions
+ * in this file go to sg_warnings_strm (default stderr).]
+ * 'no_ascii' allows for 3 output types:
+ *     > 0     each line has address then up to 8 ASCII-hex 16 bit words
+ *     = 0     in addition, the ASCI bytes pairs are listed to the right
+ *     = -1    only the ASCII-hex words are listed (i.e. without address)
+ *     = -2    only the ASCII-hex words, formatted for "hdparm --Istdin"
+ *     < -2    same as -1
+ * If 'swapb' is true then bytes in each word swapped. Needs to be set
+ * for ATA IDENTIFY DEVICE response on big-endian machines. */
+void
+dWordHex(const uint16_t* words, int num, int no_ascii, bool swapb)
+{
+    const uint16_t * p = words;
+    uint16_t c;
+    char buff[82];
+    unsigned char upp, low;
+    int a = 0;
+    const int bpstart = 3;
+    const int cpstart = 52;
+    int cpos = cpstart;
+    int bpos = bpstart;
+    int i, k, blen;
+
+    if (num <= 0)
+        return;
+    blen = (int)sizeof(buff);
+    memset(buff, ' ', 80);
+    buff[80] = '\0';
+    if (no_ascii < 0) {
+        for (k = 0; k < num; k++) {
+            c = *p++;
+            if (swapb)
+                c = swapb_uint16(c);
+            bpos += 5;
+            scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c);
+            buff[bpos + 4] = ' ';
+            if ((k > 0) && (0 == ((k + 1) % 8))) {
+                if (-2 == no_ascii)
+                    printf("%.39s\n", buff +8);
+                else
+                    printf("%.47s\n", buff);
+                bpos = bpstart;
+                memset(buff, ' ', 80);
+            }
+        }
+        if (bpos > bpstart) {
+            if (-2 == no_ascii)
+                printf("%.39s\n", buff +8);
+            else
+                printf("%.47s\n", buff);
+        }
+        return;
+    }
+    /* no_ascii>=0, start each line with address (offset) */
+    k = scnpr(buff + 1, blen - 1, "%.2x", a);
+    buff[k + 1] = ' ';
+
+    for (i = 0; i < num; i++) {
+        c = *p++;
+        if (swapb)
+            c = swapb_uint16(c);
+        bpos += 5;
+        scnpr(buff + bpos, blen - bpos, "%.4x", (unsigned int)c);
+        buff[bpos + 4] = ' ';
+        if (no_ascii) {
+            buff[cpos++] = ' ';
+            buff[cpos++] = ' ';
+            buff[cpos++] = ' ';
+        } else {
+            upp = (c >> 8) & 0xff;
+            low = c & 0xff;
+            if (! my_isprint(upp))
+                upp = '.';
+            buff[cpos++] = upp;
+            if (! my_isprint(low))
+                low = '.';
+            buff[cpos++] = low;
+            buff[cpos++] = ' ';
+        }
+        if (cpos > (cpstart + 23)) {
+            printf("%.76s\n", buff);
+            bpos = bpstart;
+            cpos = cpstart;
+            a += 8;
+            memset(buff, ' ', 80);
+            k = scnpr(buff + 1, blen - 1, "%.2x", a);
+            buff[k + 1] = ' ';
+        }
+    }
+    if (cpos > cpstart)
+        printf("%.76s\n", buff);
+}
+
+/* If the number in 'buf' can be decoded or the multiplier is unknown
+ * then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal
+ * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
+ * Main (SI) multipliers supported: K, M, G. Ignore leading spaces and
+ * tabs; accept comma, hyphen, space, tab and hash as terminator. */
+int
+sg_get_num(const char * buf)
+{
+    int res, num, n, len;
+    unsigned int unum;
+    char * cp;
+    const char * b;
+    char c = 'c';
+    char c2 = '\0';     /* keep static checker happy */
+    char c3 = '\0';     /* keep static checker happy */
+    char lb[16];
+
+    if ((NULL == buf) || ('\0' == buf[0]))
+        return -1;
+    len = strlen(buf);
+    n = strspn(buf, " \t");
+    if (n > 0) {
+        if (n == len)
+            return -1;
+        buf += n;
+        len -= n;
+    }
+    /* following hack to keep C++ happy */
+    cp = strpbrk((char *)buf, " \t,#-");
+    if (cp) {
+        len = cp - buf;
+        n = (int)sizeof(lb) - 1;
+        len = (len < n) ? len : n;
+        memcpy(lb, buf, len);
+        lb[len] = '\0';
+        b = lb;
+    } else
+        b = buf;
+    if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+        res = sscanf(b + 2, "%x", &unum);
+        num = unum;
+    } else if ('H' == toupper((int)b[len - 1])) {
+        res = sscanf(b, "%x", &unum);
+        num = unum;
+    } else
+        res = sscanf(b, "%d%c%c%c", &num, &c, &c2, &c3);
+    if (res < 1)
+        return -1LL;
+    else if (1 == res)
+        return num;
+    else {
+        if (res > 2)
+            c2 = toupper((int)c2);
+        if (res > 3)
+            c3 = toupper((int)c3);
+        switch (toupper((int)c)) {
+        case 'C':
+            return num;
+        case 'W':
+            return num * 2;
+        case 'B':
+            return num * 512;
+        case 'K':
+            if (2 == res)
+                return num * 1024;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1024;
+            return -1;
+        case 'M':
+            if (2 == res)
+                return num * 1048576;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1048576;
+            return -1;
+        case 'G':
+            if (2 == res)
+                return num * 1073741824;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1073741824;
+            return -1;
+        case 'X':
+            cp = (char *)strchr(b, 'x');
+            if (NULL == cp)
+                cp = (char *)strchr(b, 'X');
+            if (cp) {
+                n = sg_get_num(cp + 1);
+                if (-1 != n)
+                    return num * n;
+            }
+            return -1;
+        default:
+            pr2ws("unrecognized multiplier\n");
+            return -1;
+        }
+    }
+}
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. */
+int
+sg_get_num_nomult(const char * buf)
+{
+    int res, len, num;
+    unsigned int unum;
+    char * commap;
+
+    if ((NULL == buf) || ('\0' == buf[0]))
+        return -1;
+    len = strlen(buf);
+    commap = (char *)strchr(buf + 1, ',');
+    if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
+        res = sscanf(buf + 2, "%x", &unum);
+        num = unum;
+    } else if (commap && ('H' == toupper((int)*(commap - 1)))) {
+        res = sscanf(buf, "%x", &unum);
+        num = unum;
+    } else if ((NULL == commap) && ('H' == toupper((int)buf[len - 1]))) {
+        res = sscanf(buf, "%x", &unum);
+        num = unum;
+    } else
+        res = sscanf(buf, "%d", &num);
+    if (1 == res)
+        return num;
+    else
+        return -1;
+}
+
+/* If the number in 'buf' can be decoded or the multiplier is unknown
+ * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a decimal
+ * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
+ * Main (SI) multipliers supported: K, M, G, T, P. Ignore leading spaces
+ * and tabs; accept comma, hyphen, space, tab and hash as terminator. */
+int64_t
+sg_get_llnum(const char * buf)
+{
+    int res, len, n;
+    int64_t num, ll;
+    uint64_t unum;
+    char * cp;
+    const char * b;
+    char c = 'c';
+    char c2 = '\0';     /* keep static checker happy */
+    char c3 = '\0';     /* keep static checker happy */
+    char lb[32];
+
+    if ((NULL == buf) || ('\0' == buf[0]))
+        return -1LL;
+    len = strlen(buf);
+    n = strspn(buf, " \t");
+    if (n > 0) {
+        if (n == len)
+            return -1LL;
+        buf += n;
+        len -= n;
+    }
+    /* following hack to keep C++ happy */
+    cp = strpbrk((char *)buf, " \t,#-");
+    if (cp) {
+        len = cp - buf;
+        n = (int)sizeof(lb) - 1;
+        len = (len < n) ? len : n;
+        memcpy(lb, buf, len);
+        lb[len] = '\0';
+        b = lb;
+    } else
+        b = buf;
+    if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+        res = sscanf(b + 2, "%" SCNx64 , &unum);
+        num = unum;
+    } else if ('H' == toupper((int)b[len - 1])) {
+        res = sscanf(b, "%" SCNx64 , &unum);
+        num = unum;
+    } else
+        res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3);
+    if (res < 1)
+        return -1LL;
+    else if (1 == res)
+        return num;
+    else {
+        if (res > 2)
+            c2 = toupper((int)c2);
+        if (res > 3)
+            c3 = toupper((int)c3);
+        switch (toupper((int)c)) {
+        case 'C':
+            return num;
+        case 'W':
+            return num * 2;
+        case 'B':
+            return num * 512;
+        case 'K':
+            if (2 == res)
+                return num * 1024;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1024;
+            return -1LL;
+        case 'M':
+            if (2 == res)
+                return num * 1048576;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1048576;
+            return -1LL;
+        case 'G':
+            if (2 == res)
+                return num * 1073741824;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1073741824;
+            return -1LL;
+        case 'T':
+            if (2 == res)
+                return num * 1099511627776LL;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000000000LL;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1099511627776LL;
+            return -1LL;
+        case 'P':
+            if (2 == res)
+                return num * 1099511627776LL * 1024;
+            if (('B' == c2) || ('D' == c2))
+                return num * 1000000000000LL * 1000;
+            if (('I' == c2) && (4 == res) && ('B' == c3))
+                return num * 1099511627776LL * 1024;
+            return -1LL;
+        case 'X':
+            cp = (char *)strchr(b, 'x');
+            if (NULL == cp)
+                cp = (char *)strchr(b, 'X');
+            if (cp) {
+                ll = sg_get_llnum(cp + 1);
+                if (-1LL != ll)
+                    return num * ll;
+            }
+            return -1LL;
+        default:
+            pr2ws("unrecognized multiplier\n");
+            return -1LL;
+        }
+    }
+}
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * negative numbers and '-1' must be treated separately. */
+int64_t
+sg_get_llnum_nomult(const char * buf)
+{
+    int res, len;
+    int64_t num;
+    uint64_t unum;
+
+    if ((NULL == buf) || ('\0' == buf[0]))
+        return -1;
+    len = strlen(buf);
+    if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
+        res = sscanf(buf + 2, "%" SCNx64 "", &unum);
+        num = unum;
+    } else if ('H' == toupper(buf[len - 1])) {
+        res = sscanf(buf, "%" SCNx64 "", &unum);
+        num = unum;
+    } else
+        res = sscanf(buf, "%" SCNd64 "", &num);
+    return (1 == res) ? num : -1;
+}
+
+/* Extract character sequence from ATA words as in the model string
+ * in a IDENTIFY DEVICE response. Returns number of characters
+ * written to 'ochars' before 0 character is found or 'num' words
+ * are processed. */
+int
+sg_ata_get_chars(const uint16_t * word_arr, int start_word,
+                 int num_words, bool is_big_endian, char * ochars)
+{
+    int k;
+    uint16_t s;
+    char a, b;
+    char * op = ochars;
+
+    for (k = start_word; k < (start_word + num_words); ++k) {
+        s = word_arr[k];
+        if (is_big_endian) {
+            a = s & 0xff;
+            b = (s >> 8) & 0xff;
+        } else {
+            a = (s >> 8) & 0xff;
+            b = s & 0xff;
+        }
+        if (a == 0)
+            break;
+        *op++ = a;
+        if (b == 0)
+            break;
+        *op++ = b;
+    }
+    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;
+}
+
+#ifdef SG_LIB_FREEBSD
+#include <sys/param.h>
+#elif defined(SG_LIB_WIN32)
+#include <windows.h>
+
+static bool got_page_size = false;
+static uint32_t win_page_size;
+#endif
+
+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)
+    if (! got_page_size) {
+        SYSTEM_INFO si;
+
+        GetSystemInfo(&si);
+        win_page_size = si.dwPageSize;
+        got_page_size = true;
+    }
+    return win_page_size;
+#elif defined(SG_LIB_FREEBSD)
+    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;
+
+    if (buff_to_free)   /* make sure buff_to_free is NULL if alloc fails */
+        *buff_to_free = NULL;
+    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_ma, len=%d, ", __func__, num_bytes);
+            if (buff_to_free)
+                pr2ws("wrkBuffp=%p, ", (void *)res);
+            pr2ws("psz=%u, rp=%p\n", (unsigned int)psz, (void *)res);
+        }
+        return res;
+    }
+#else
+    {
+        void * wrkBuff;
+        sg_uintptr_t align_1 = psz - 1;
+
+        wrkBuff = (uint8_t *)calloc(num_bytes + psz, 1);
+        if (NULL == wrkBuff) {
+            if (buff_to_free)
+                *buff_to_free = NULL;
+            return NULL;
+        } else if (buff_to_free)
+            *buff_to_free = (uint8_t *)wrkBuff;
+        res = (uint8_t *)(void *)
+            (((sg_uintptr_t)wrkBuff + align_1) & (~align_1));
+        if (vb) {
+            pr2ws("%s: hack, len=%d, ", __func__, num_bytes);
+            if (buff_to_free)
+                pr2ws("buff_to_free=%p, ", wrkBuff);
+            pr2ws("align_1=%lu, rp=%p\n", (unsigned long)align_1, (void *)res);
+        }
+        return res;
+    }
+#endif
+}
+
+const char *
+sg_lib_version()
+{
+    return sg_lib_version_str;
+}
+
+
+#ifdef SG_LIB_MINGW
+/* Non Unix OSes distinguish between text and binary files.
+   Set text mode on fd. Does nothing in Unix. Returns negative number on
+   failure. */
+
+#include <unistd.h>
+#include <fcntl.h>
+
+int
+sg_set_text_mode(int fd)
+{
+    return setmode(fd, O_TEXT);
+}
+
+/* Set binary mode on fd. Does nothing in Unix. Returns negative number on
+   failure. */
+int
+sg_set_binary_mode(int fd)
+{
+    return setmode(fd, O_BINARY);
+}
+
+#else
+/* For Unix the following functions are dummies. */
+int
+sg_set_text_mode(int fd)
+{
+    return fd;  /* fd should be >= 0 */
+}
+
+int
+sg_set_binary_mode(int fd)
+{
+    return fd;
+}
+
+#endif
diff --git a/tools/sg_write_buffer/sg_lib_data.c b/tools/sg_write_buffer/sg_lib_data.c
new file mode 100644
index 0000000..e59c355
--- /dev/null
+++ b/tools/sg_write_buffer/sg_lib_data.c
@@ -0,0 +1,1688 @@
+/*
+ * Copyright (c) 2007-2018 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 <stdlib.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#else
+#define SG_SCSI_STRINGS 1
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+
+
+const char * sg_lib_version_str = "2.38 20180122";/* spc5r17, sbc4r15 */
+
+
+/* indexed by pdt; those that map to own index do not decay */
+int sg_lib_pdt_decay_arr[32] = {
+    PDT_DISK, PDT_TAPE, PDT_TAPE /* printer */, PDT_PROCESSOR,
+    PDT_DISK /* WO */, PDT_MMC, PDT_SCANNER, PDT_DISK /* optical */,
+    PDT_MCHANGER, PDT_COMMS, 0xa, 0xb,
+    PDT_SAC, PDT_SES, PDT_DISK /* rbc */, PDT_OCRW,
+    PDT_BCC, PDT_OSD, PDT_TAPE /* adc */, PDT_SMD,
+    PDT_DISK /* zbc */, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, PDT_WLUN, PDT_UNKNOWN
+};
+
+#ifdef SG_SCSI_STRINGS
+struct sg_lib_value_name_t sg_lib_normal_opcodes[] = {
+    {0, 0, "Test Unit Ready"},
+    {0x1, 0, "Rezero Unit"},
+    {0x1, PDT_TAPE, "Rewind"},
+    {0x3, 0, "Request Sense"},
+    {0x4, 0, "Format Unit"},
+    {0x4, PDT_TAPE, "Format medium"},
+    {0x4, PDT_PRINTER, "Format"},
+    {0x5, 0, "Read Block Limits"},
+    {0x7, 0, "Reassign Blocks"},
+    {0x7, PDT_MCHANGER, "Initialize element status"},
+    {0x8, 0, "Read(6)"},        /* obsolete in sbc3r30 */
+    {0x8, PDT_PROCESSOR, "Receive"},
+    {0xa, 0, "Write(6)"},       /* obsolete in sbc3r30 */
+    {0xa, PDT_PRINTER, "Print"},
+    {0xa, PDT_PROCESSOR, "Send"},
+    {0xb, 0, "Seek(6)"},
+    {0xb, PDT_TAPE, "Set capacity"},
+    {0xb, PDT_PRINTER, "Slew and print"},
+    {0xf, 0, "Read reverse(6)"},
+    {0x10, 0, "Write filemarks(6)"},
+    {0x10, PDT_PRINTER, "Synchronize buffer"},
+    {0x11, 0, "Space(6)"},
+    {0x12, 0, "Inquiry"},
+    {0x13, 0, "Verify(6)"},  /* SSC */
+    {0x14, 0, "Recover buffered data"},
+    {0x15, 0, "Mode select(6)"}, /* SBC-3 r31 recommends Mode select(10) */
+    {0x16, 0, "Reserve(6)"},    /* obsolete in SPC-4 r11 */
+    {0x16, PDT_MCHANGER, "Reserve element(6)"},
+    {0x17, 0, "Release(6)"},    /* obsolete in SPC-4 r11 */
+    {0x17, PDT_MCHANGER, "Release element(6)"},
+    {0x18, 0, "Copy"},          /* obsolete in SPC-4 r11 */
+    {0x19, 0, "Erase(6)"},
+    {0x1a, 0, "Mode sense(6)"}, /* SBC-3 r31 recommends Mode sense(10) */
+    {0x1b, 0, "Start stop unit"},
+    {0x1b, PDT_TAPE, "Load unload"},
+    {0x1b, PDT_ADC, "Load unload"},
+    {0x1b, PDT_PRINTER, "Stop print"},
+    {0x1c, 0, "Receive diagnostic results"},
+    {0x1d, 0, "Send diagnostic"},
+    {0x1e, 0, "Prevent allow medium removal"},
+    {0x23, 0, "Read Format capacities"},
+    {0x24, 0, "Set window"},
+    {0x25, 0, "Read capacity(10)"},
+                        /* SBC-3 r31 recommends Read capacity(16) */
+    {0x25, PDT_OCRW, "Read card capacity"},
+    {0x28, 0, "Read(10)"},      /* SBC-3 r31 recommends Read(16) */
+    {0x29, 0, "Read generation"},
+    {0x2a, 0, "Write(10)"},     /* SBC-3 r31 recommends Write(16) */
+    {0x2b, 0, "Seek(10)"},
+    {0x2b, PDT_TAPE, "Locate(10)"},
+    {0x2b, PDT_MCHANGER, "Position to element"},
+    {0x2c, 0, "Erase(10)"},
+    {0x2d, 0, "Read updated block"},
+    {0x2e, 0, "Write and verify(10)"},
+                        /* SBC-3 r31 recommends Write and verify(16) */
+    {0x2f, 0, "Verify(10)"},    /* SBC-3 r31 recommends Verify(16) */
+    {0x30, 0, "Search data high(10)"},
+    {0x31, 0, "Search data equal(10)"},
+    {0x32, 0, "Search data low(10)"},
+    {0x33, 0, "Set limits(10)"},
+    {0x34, 0, "Pre-fetch(10)"}, /* SBC-3 r31 recommends Pre-fetch(16) */
+    {0x34, PDT_TAPE, "Read position"},
+    {0x35, 0, "Synchronize cache(10)"},
+                        /* SBC-3 r31 recommends Synchronize cache(16) */
+    {0x36, 0, "Lock unlock cache(10)"},
+    {0x37, 0, "Read defect data(10)"},
+                        /* SBC-3 r31 recommends Read defect data(12) */
+    {0x37, PDT_MCHANGER, "Initialize element status with range"},
+    {0x38, 0, "Medium scan"},
+    {0x39, 0, "Compare"},               /* obsolete in SPC-4 r11 */
+    {0x3a, 0, "Copy and verify"},       /* obsolete in SPC-4 r11 */
+    {0x3b, 0, "Write buffer"},
+    {0x3c, 0, "Read buffer(10)"},
+    {0x3d, 0, "Update block"},
+    {0x3e, 0, "Read long(10)"},         /* obsolete in SBC-4 r7 */
+    {0x3f, 0, "Write long(10)"}, /* SBC-3 r31 recommends Write long(16) */
+    {0x40, 0, "Change definition"},     /* obsolete in SPC-4 r11 */
+    {0x41, 0, "Write same(10)"}, /* SBC-3 r31 recommends Write same(16) */
+    {0x42, 0, "Unmap"},                 /* added SPC-4 rev 18 */
+    {0x42, PDT_MMC, "Read sub-channel"},
+    {0x43, PDT_MMC, "Read TOC/PMA/ATIP"},
+    {0x44, 0, "Report density support"},
+    {0x45, PDT_MMC, "Play audio(10)"},
+    {0x46, PDT_MMC, "Get configuration"},
+    {0x47, PDT_MMC, "Play audio msf"},
+    {0x48, 0, "Sanitize"},
+    {0x4a, PDT_MMC, "Get event status notification"},
+    {0x4b, PDT_MMC, "Pause/resume"},
+    {0x4c, 0, "Log select"},
+    {0x4d, 0, "Log sense"},
+    {0x4e, 0, "Stop play/scan"},
+    {0x50, 0, "Xdwrite(10)"},           /* obsolete in SBC-3 r31 */
+    {0x51, 0, "Xpwrite(10)"},           /* obsolete in SBC-4 r15 */
+    {0x51, PDT_MMC, "Read disk information"},
+    {0x52, 0, "Xdread(10)"},            /* obsolete in SBC-3 r31 */
+    {0x52, PDT_MMC, "Read track information"},
+    {0x53, 0, "Xdwriteread(10)"},       /* obsolete in SBC-4 r15 */
+    {0x54, 0, "Send OPC information"},
+    {0x55, 0, "Mode select(10)"},
+    {0x56, 0, "Reserve(10)"},           /* obsolete in SPC-4 r11 */
+    {0x56, PDT_MCHANGER, "Reserve element(10)"},
+    {0x57, 0, "Release(10)"},           /* obsolete in SPC-4 r11 */
+    {0x57, PDT_MCHANGER, "Release element(10)"},
+    {0x58, 0, "Repair track"},
+    {0x5a, 0, "Mode sense(10)"},
+    {0x5b, 0, "Close track/session"},
+    {0x5c, 0, "Read buffer capacity"},
+    {0x5d, 0, "Send cue sheet"},
+    {0x5e, 0, "Persistent reserve in"},
+    {0x5f, 0, "Persistent reserve out"},
+    {0x7e, 0, "Extended cdb (XCBD)"},           /* added in SPC-4 r12 */
+    {0x80, 0, "Xdwrite extended(16)"},          /* obsolete in SBC-4 r15 */
+    {0x80, PDT_TAPE, "Write filemarks(16)"},
+    {0x81, 0, "Rebuild(16)"},
+    {0x81, PDT_TAPE, "Read reverse(16)"},
+    {0x82, 0, "Regenerate(16)"},
+    {0x83, 0, "Third party copy out"},  /* Extended copy, before spc4r34 */
+        /* Following was "Receive copy results", before spc4r34 */
+    {0x84, 0, "Third party copy in"},
+    {0x85, 0, "ATA pass-through(16)"},  /* was 0x98 in spc3 rev21c */
+    {0x86, 0, "Access control in"},
+    {0x87, 0, "Access control out"},
+    {0x88, 0, "Read(16)"},
+    {0x89, 0, "Compare and write"},
+    {0x8a, 0, "Write(16)"},
+    {0x8b, 0, "Orwrite(16)"},
+    {0x8c, 0, "Read attribute"},
+    {0x8d, 0, "Write attribute"},
+    {0x8e, 0, "Write and verify(16)"},
+    {0x8f, 0, "Verify(16)"},
+    {0x90, 0, "Pre-fetch(16)"},
+    {0x91, 0, "Synchronize cache(16)"},
+    {0x91, PDT_TAPE, "Space(16)"},
+    {0x92, 0, "Lock unlock cache(16)"},
+    {0x92, PDT_TAPE, "Locate(16)"},
+    {0x93, 0, "Write same(16)"},
+    {0x93, PDT_TAPE, "Erase(16)"},
+    {0x94, PDT_ZBC, "ZBC out"},  /* new sbc4r04, has service actions */
+    {0x95, PDT_ZBC, "ZBC in"},   /* new sbc4r04, has service actions */
+    {0x9a, 0, "Write stream(16)"},      /* added sbc4r07 */
+    {0x9b, 0, "Read buffer(16)"},       /* added spc5r02 */
+    {0x9c, 0, "Write atomic(16)"},
+    {0x9d, 0, "Service action bidirectional"},  /* added spc4r35 */
+    {0x9e, 0, "Service action in(16)"},
+    {0x9f, 0, "Service action out(16)"},
+    {0xa0, 0, "Report luns"},
+    {0xa1, 0, "ATA pass-through(12)"},
+    {0xa1, PDT_MMC, "Blank"},
+    {0xa2, 0, "Security protocol in"},
+    {0xa3, 0, "Maintenance in"},
+    {0xa3, PDT_MMC, "Send key"},
+    {0xa4, 0, "Maintenance out"},
+    {0xa4, PDT_MMC, "Report key"},
+    {0xa5, 0, "Move medium"},
+    {0xa5, PDT_MMC, "Play audio(12)"},
+    {0xa6, 0, "Exchange medium"},
+    {0xa6, PDT_MMC, "Load/unload medium"},
+    {0xa7, 0, "Move medium attached"},
+    {0xa7, PDT_MMC, "Set read ahead"},
+    {0xa8, 0, "Read(12)"},      /* SBC-3 r31 recommends Read(16) */
+    {0xa9, 0, "Service action out(12)"},
+    {0xaa, 0, "Write(12)"},     /* SBC-3 r31 recommends Write(16) */
+    {0xab, 0, "Service action in(12)"},
+    {0xac, 0, "erase(12)"},
+    {0xac, PDT_MMC, "Get performance"},
+    {0xad, PDT_MMC, "Read DVD/BD structure"},
+    {0xae, 0, "Write and verify(12)"},
+                        /* SBC-3 r31 recommends Write and verify(16) */
+    {0xaf, 0, "Verify(12)"},    /* SBC-3 r31 recommends Verify(16) */
+    {0xb0, 0, "Search data high(12)"},
+    {0xb1, 0, "Search data equal(12)"},
+    {0xb1, PDT_MCHANGER, "Open/close import/export element"},
+    {0xb2, 0, "Search data low(12)"},
+    {0xb3, 0, "Set limits(12)"},
+    {0xb4, 0, "Read element status attached"},
+    {0xb5, 0, "Security protocol out"},
+    {0xb5, PDT_MCHANGER, "Request volume element address"},
+    {0xb6, 0, "Send volume tag"},
+    {0xb6, PDT_MMC, "Set streaming"},
+    {0xb7, 0, "Read defect data(12)"},
+    {0xb8, 0, "Read element status"},
+    {0xb9, 0, "Read CD msf"},
+    {0xba, 0, "Redundancy group in"},
+    {0xba, PDT_MMC, "Scan"},
+    {0xbb, 0, "Redundancy group out"},
+    {0xbb, PDT_MMC, "Set CD speed"},
+    {0xbc, 0, "Spare in"},
+    {0xbd, 0, "Spare out"},
+    {0xbd, PDT_MMC, "Mechanism status"},
+    {0xbe, 0, "Volume set in"},
+    {0xbe, PDT_MMC, "Read CD"},
+    {0xbf, 0, "Volume set out"},
+    {0xbf, PDT_MMC, "Send DVD/BD structure"},
+    {0xffff, 0, NULL},
+};
+
+/* Read buffer(10) [0x3c] and Read buffer(16) [0x9b] service actions (sa),
+ * need prefix */
+struct sg_lib_value_name_t sg_lib_read_buff_arr[] = {
+    {0x0, 0, "combined header and data [or multiple modes]"},
+    {0x2, 0, "data"},
+    {0x3, 0, "descriptor"},
+    {0xa, 0, "read data from echo buffer"},
+    {0xb, 0, "echo buffer descriptor"},
+    {0x1a, 0, "enable expander comms protocol and echo buffer"},
+    {0x1c, 0, "error history"},
+    {0xffff, 0, NULL},
+};
+
+/* Write buffer [0x3b] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_write_buff_arr[] = {
+    {0x0, 0, "combined header and data [or multiple modes]"},
+    {0x2, 0, "data"},
+    {0x4, 0, "download microcode and activate"},
+    {0x5, 0, "download microcode, save, and activate"},
+    {0x6, 0, "download microcode with offsets and activate"},
+    {0x7, 0, "download microcode with offsets, save, and activate"},
+    {0xa, 0, "write data to echo buffer"},
+    {0xd, 0, "download microcode with offsets, select activation events, "
+             "save and defer activate"},
+    {0xe, 0, "download microcode with offsets, save and defer activate"},
+    {0xf, 0, "activate deferred microcode"},
+    {0x1a, 0, "enable expander comms protocol and echo buffer"},
+    {0x1b, 0, "disable expander comms protocol"},
+    {0x1c, 0, "download application client error history"},
+    {0xffff, 0, NULL},
+};
+
+/* Read position (SSC) [0x34] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_read_pos_arr[] = {
+    {0x0, PDT_TAPE, "short form - block id"},
+    {0x1, PDT_TAPE, "short form - vendor specific"},
+    {0x6, PDT_TAPE, "long form"},
+    {0x8, PDT_TAPE, "extended form"},
+    {0xffff, 0, NULL},
+};
+
+/* Maintenance in [0xa3] service actions */
+struct sg_lib_value_name_t sg_lib_maint_in_arr[] = {
+    {0x0, PDT_SAC, "Report assigned/unassigned p_extent"},
+    {0x1, PDT_SAC, "Report component device"},
+    {0x2, PDT_SAC, "Report component device attachments"},
+    {0x3, PDT_SAC, "Report peripheral device"},
+    {0x4, PDT_SAC, "Report peripheral device associations"},
+    {0x5, 0, "Report identifying information"},
+                /* was "Report device identifier" prior to spc4r07 */
+    {0x6, PDT_SAC, "Report states"},
+    {0x7, PDT_SAC, "Report device identification"},
+    {0x8, PDT_SAC, "Report unconfigured capacity"},
+    {0x9, PDT_SAC, "Report supported configuration method"},
+    {0xa, 0, "Report target port groups"},
+    {0xb, 0, "Report aliases"},
+    {0xc, 0, "Report supported operation codes"},
+    {0xd, 0, "Report supported task management functions"},
+    {0xe, 0, "Report priority"},
+    {0xf, 0, "Report timestamp"},
+    {0x10, 0, "Management protocol in"},
+    {0x1d, PDT_DISK, "Report provisioning initialization pattern"},
+        /* added in sbc4r07, shares sa 0x1d with ssc5r01 (tape) */
+    {0x1d, PDT_TAPE, "Receive recommended access order"},
+    {0x1e, PDT_TAPE, "Read dynamic runtime attribute"},
+    {0x1e, PDT_ADC, "Report automation device attributes"},
+    {0x1f, 0, "Maintenance in vendor specific"},
+    {0xffff, 0, NULL},
+};
+
+/* Maintenance out [0xa4] service actions */
+struct sg_lib_value_name_t sg_lib_maint_out_arr[] = {
+    {0x0, PDT_SAC, "Add peripheral device / component device"},
+    {0x1, PDT_SAC, "Attach to component device"},
+    {0x2, PDT_SAC, "Exchange p_extent"},
+    {0x3, PDT_SAC, "Exchange peripheral device / component device"},
+    {0x4, PDT_SAC, "Instruct component device"},
+    {0x5, PDT_SAC, "Remove peripheral device / component device"},
+    {0x6, 0, "Set identifying information"},
+                /* was "Set device identifier" prior to spc4r07 */
+    {0x7, PDT_SAC, "Break peripheral device / component device"},
+    {0xa, 0, "Set target port groups"},
+    {0xb, 0, "Change aliases"},
+    {0xc, 0, "Remove I_T nexus"},
+    {0xe, 0, "Set priority"},
+    {0xf, 0, "Set timestamp"},
+    {0x10, 0, "Management protocol out"},
+    {0x1d, PDT_TAPE, "Generate recommended access order"},
+    {0x1e, PDT_TAPE, "write dynamic runtime attribute"},
+    {0x1e, PDT_ADC, "Set automation device attributes"},
+    {0x1f, 0, "Maintenance out vendor specific"},
+    {0xffff, 0, NULL},
+};
+
+/* Sanitize [0x48] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = {
+    {0x1, 0, "overwrite"},
+    {0x2, 0, "block erase"},
+    {0x3, 0, "cryptographic erase"},
+    {0x1f, 0, "exit failure mode"},
+    {0xffff, 0, NULL},
+};
+
+/* Service action in(12) [0xab] service actions */
+struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = {
+    {0x1, 0, "Read media serial number"},
+    {0xffff, 0, NULL},
+};
+
+/* Service action out(12) [0xa9] service actions */
+struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = {
+    {0xff, 0, "Impossible command name"},
+    {0xffff, 0, NULL},
+};
+
+/* Service action in(16) [0x9e] service actions */
+struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = {
+    {0xf, 0, "Receive binding report"}, /* added spc5r11 */
+    {0x10, 0, "Read capacity(16)"},
+    {0x11, 0, "Read long(16)"},         /* obsolete in SBC-4 r7 */
+    {0x12, 0, "Get LBA status(16)"},    /* 32 byte variant added in sbc4r14 */
+    {0x13, 0, "Report referrals"},
+    {0x14, 0, "Stream control"},
+    {0x15, 0, "Background control"},
+    {0x16, 0, "Get stream status"},
+    {0x17, 0, "Get physical element status"},   /* added sbc4r13 */
+    {0x18, 0, "Remove element and truncate"},   /* added sbc4r13 */
+    {0xffff, 0, NULL},
+};
+
+/* Service action out(16) [0x9f] service actions */
+struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = {
+    {0x0b, 0, "Test bind"},             /* added spc5r13 */
+    {0x0c, 0, "Prepare bind report"},   /* added spc5r11 */
+    {0x0d, 0, "Set affiliation"},
+    {0x0e, 0, "Bind"},
+    {0x0f, 0, "Unbind"},
+    {0x11, 0, "Write long(16)"},
+    {0x12, 0, "Write scattered(16)"},   /* added sbc4r11 */
+    {0x14, PDT_ZBC, "Reset write pointer"},
+    {0x1f, PDT_ADC, "Notify data transfer device(16)"},
+    {0xffff, 0, NULL},
+};
+
+/* Service action bidirectional [0x9d] service actions */
+struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+/* Persistent reserve in [0x5e] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_pr_in_arr[] = {
+    {0x0, 0, "read keys"},
+    {0x1, 0, "read reservation"},
+    {0x2, 0, "report capabilities"},
+    {0x3, 0, "read full status"},
+    {0xffff, 0, NULL},
+};
+
+/* Persistent reserve out [0x5f] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_pr_out_arr[] = {
+    {0x0, 0, "register"},
+    {0x1, 0, "reserve"},
+    {0x2, 0, "release"},
+    {0x3, 0, "clear"},
+    {0x4, 0, "preempt"},
+    {0x5, 0, "preempt and abort"},
+    {0x6, 0, "register and ignore existing key"},
+    {0x7, 0, "register and move"},
+    {0x8, 0, "replace lost reservation"},
+    {0xffff, 0, NULL},
+};
+
+/* Third party copy in [0x83] service actions
+ * Opcode 'Receive copy results' was renamed 'Third party copy in' in spc4r34
+ * LID1 is an abbreviation of List Identifier length of 1 byte */
+struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = {
+    {0x0, 0, "Extended copy(LID1)"},
+    {0x1, 0, "Extended copy(LID4)"},
+    {0x10, 0, "Populate token"},
+    {0x11, 0, "Write using token"},
+    {0x1c, 0, "Copy operation abort"},
+    {0xffff, 0, NULL},
+};
+
+/* Third party copy out [0x84] service actions
+ * Opcode 'Extended copy' was renamed 'Third party copy out' in spc4r34
+ * LID4 is an abbreviation of List Identifier length of 4 bytes */
+struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = {
+    {0x0, 0, "Receive copy status(LID1)"},
+    {0x1, 0, "Receive copy data(LID1)"},
+    {0x3, 0, "Receive copy operating parameters"},
+    {0x4, 0, "Receive copy failure details(LID1)"},
+    {0x5, 0, "Receive copy status(LID4)"},
+    {0x6, 0, "Receive copy data(LID4)"},
+    {0x7, 0, "Receive ROD token information"},
+    {0x8, 0, "Report all ROD tokens"},
+    {0xffff, 0, NULL},
+};
+
+/* Variable length cdb [0x7f] service actions (more than 16 bytes long) */
+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 */
+    {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 */
+    {0x8, 0, "Xdwrite extended(64)"},   /* obsolete in SBC-4 r15 */
+    {0x9, 0, "Read(32)"},
+    {0xa, 0, "Verify(32)"},
+    {0xb, 0, "Write(32)"},
+    {0xc, 0, "Write and verify(32)"},
+    {0xd, 0, "Write same(32)"},
+    {0xe, 0, "Orwrite(32)"},            /* added sbc3r25 */
+    {0xf, 0, "Atomic write(32)"},       /* added sbc4r02 */
+    {0x10, 0, "Write stream(32)"},      /* added sbc4r07 */
+    {0x11, 0, "Write scattered(32)"},   /* added sbc4r11 */
+    {0x12, 0, "Get LBA status(32)"},    /* added sbc4r14 */
+    {0x1800, 0, "Receive credential"},
+    {0x1ff0, 0, "ATA pass-through(32)"},/* added sat4r05 */
+    {0x8801, 0, "Format OSD (osd)"},
+    {0x8802, 0, "Create (osd)"},
+    {0x8803, 0, "List (osd)"},
+    {0x8805, 0, "Read (osd)"},
+    {0x8806, 0, "Write (osd)"},
+    {0x8807, 0, "Append (osd)"},
+    {0x8808, 0, "Flush (osd)"},
+    {0x880a, 0, "Remove (osd)"},
+    {0x880b, 0, "Create partition (osd)"},
+    {0x880c, 0, "Remove partition (osd)"},
+    {0x880e, 0, "Get attributes (osd)"},
+    {0x880f, 0, "Set attributes (osd)"},
+    {0x8812, 0, "Create and write (osd)"},
+    {0x8815, 0, "Create collection (osd)"},
+    {0x8816, 0, "Remove collection (osd)"},
+    {0x8817, 0, "List collection (osd)"},
+    {0x8818, 0, "Set key (osd)"},
+    {0x8819, 0, "Set master key (osd)"},
+    {0x881a, 0, "Flush collection (osd)"},
+    {0x881b, 0, "Flush partition (osd)"},
+    {0x881c, 0, "Flush OSD (osd)"},
+    {0x8880, 0, "Object structure check (osd-2)"},
+    {0x8881, 0, "Format OSD (osd-2)"},
+    {0x8882, 0, "Create (osd-2)"},
+    {0x8883, 0, "List (osd-2)"},
+    {0x8884, 0, "Punch (osd-2)"},
+    {0x8885, 0, "Read (osd-2)"},
+    {0x8886, 0, "Write (osd-2)"},
+    {0x8887, 0, "Append (osd-2)"},
+    {0x8888, 0, "Flush (osd-2)"},
+    {0x8889, 0, "Clear (osd-2)"},
+    {0x888a, 0, "Remove (osd-2)"},
+    {0x888b, 0, "Create partition (osd-2)"},
+    {0x888c, 0, "Remove partition (osd-2)"},
+    {0x888e, 0, "Get attributes (osd-2)"},
+    {0x888f, 0, "Set attributes (osd-2)"},
+    {0x8892, 0, "Create and write (osd-2)"},
+    {0x8895, 0, "Create collection (osd-2)"},
+    {0x8896, 0, "Remove collection (osd-2)"},
+    {0x8897, 0, "List collection (osd-2)"},
+    {0x8898, 0, "Set key (osd-2)"},
+    {0x8899, 0, "Set master key (osd-2)"},
+    {0x889a, 0, "Flush collection (osd-2)"},
+    {0x889b, 0, "Flush partition (osd-2)"},
+    {0x889c, 0, "Flush OSD (osd-2)"},
+    {0x88a0, 0, "Query (osd-2)"},
+    {0x88a1, 0, "Remove member objects (osd-2)"},
+    {0x88a2, 0, "Get member attributes (osd-2)"},
+    {0x88a3, 0, "Set member attributes (osd-2)"},
+    {0x88b1, 0, "Read map (osd-2)"},
+    {0x8f7c, 0, "Perform SCSI command (osd-2)"},
+    {0x8f7d, 0, "Perform task management function (osd-2)"},
+    {0x8f7e, 0, "Perform SCSI command (osd)"},
+    {0x8f7f, 0, "Perform task management function (osd)"},
+    {0xffff, 0, NULL},
+};
+
+/* Zoning out [0x94] service actions */
+struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = {
+    {0x1, PDT_ZBC, "Close zone"},
+    {0x2, PDT_ZBC, "Finish zone"},
+    {0x3, PDT_ZBC, "Open zone"},
+    {0x4, PDT_ZBC, "Reset write pointer"},
+    {0xffff, 0, NULL},
+};
+
+/* Zoning in [0x95] service actions */
+struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = {
+    {0x0, PDT_ZBC, "Report zones"},
+    {0xffff, 0, NULL},
+};
+
+/* Read attribute [0x8c] service actions */
+struct sg_lib_value_name_t sg_lib_read_attr_arr[] = {
+    {0x0, 0, "attribute values"},
+    {0x1, 0, "attribute list"},
+    {0x2, 0, "logical volume list"},
+    {0x3, 0, "partition list"},
+    {0x5, 0, "supported attributes"},
+    {0xffff, 0, NULL},
+};
+
+#else   /* SG_SCSI_STRINGS */
+
+struct sg_lib_value_name_t sg_lib_normal_opcodes[] = {
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_read_buff_arr[] = {  /* opcode 0x3c */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_write_buff_arr[] = {  /* opcode 0x3b */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_read_pos_arr[] = {  /* opcode 0x34 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_maint_in_arr[] = {  /* opcode 0xa3 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_maint_out_arr[] = {  /* opcode 0xa4 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = {  /* opcode 0x94 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { /* opcode 0xab */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { /* opcode 0xa9 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { /* opcode 0x9e */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { /* opcode 0x9f */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { /* opcode 0x9d */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { /* opcode 0x5e */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { /* opcode 0x5f */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { /* opcode 0x83 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { /* opcode 0x84 */
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_variable_length_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_read_attr_arr[] = {
+    {0xffff, 0, NULL},
+};
+
+#endif  /* SG_SCSI_STRINGS */
+
+/* A conveniently formatted list of SCSI ASC/ASCQ codes and their
+ * corresponding text can be found at: www.t10.org/lists/asc-num.txt
+ * The following should match asc-num.txt dated 20150423 */
+
+#ifdef SG_SCSI_STRINGS
+struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] =
+{
+    {0x40,0x01,0x7f,"Ram failure [0x%x]"},
+    {0x40,0x80,0xff,"Diagnostic failure on component [0x%x]"},
+    {0x41,0x01,0xff,"Data path failure [0x%x]"},
+    {0x42,0x01,0xff,"Power-on or self-test failure [0x%x]"},
+    {0x4d,0x00,0xff,"Tagged overlapped commands [0x%x]"},
+    {0x70,0x00,0xff,"Decompression exception short algorithm id of 0x%x"},
+    {0, 0, 0, NULL}
+};
+
+struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] =
+{
+    {0x00,0x00,"No additional sense information"},
+    {0x00,0x01,"Filemark detected"},
+    {0x00,0x02,"End-of-partition/medium detected"},
+    {0x00,0x03,"Setmark detected"},
+    {0x00,0x04,"Beginning-of-partition/medium detected"},
+    {0x00,0x05,"End-of-data detected"},
+    {0x00,0x06,"I/O process terminated"},
+    {0x00,0x07,"Programmable early warning detected"},
+    {0x00,0x11,"Audio play operation in progress"},
+    {0x00,0x12,"Audio play operation paused"},
+    {0x00,0x13,"Audio play operation successfully completed"},
+    {0x00,0x14,"Audio play operation stopped due to error"},
+    {0x00,0x15,"No current audio status to return"},
+    {0x00,0x16,"operation in progress"},
+    {0x00,0x17,"Cleaning requested"},
+    {0x00,0x18,"Erase operation in progress"},
+    {0x00,0x19,"Locate operation in progress"},
+    {0x00,0x1a,"Rewind operation in progress"},
+    {0x00,0x1b,"Set capacity operation in progress"},
+    {0x00,0x1c,"Verify operation in progress"},
+    {0x00,0x1d,"ATA pass through information available"},
+    {0x00,0x1e,"Conflicting SA creation request"},
+    {0x00,0x1f,"Logical unit transitioning to another power condition"},
+    {0x00,0x20,"Extended copy information available"},
+    {0x00,0x21,"Atomic command aborted due to ACA"},
+    {0x00,0x22,"Deferred microcode is pending"},
+    {0x01,0x00,"No index/sector signal"},
+    {0x02,0x00,"No seek complete"},
+    {0x03,0x00,"Peripheral device write fault"},
+    {0x03,0x01,"No write current"},
+    {0x03,0x02,"Excessive write errors"},
+    {0x04,0x00,"Logical unit not ready, cause not reportable"},
+    {0x04,0x01,"Logical unit is in process of becoming ready"},
+    {0x04,0x02,"Logical unit not ready, "
+                "initializing command required"},
+    {0x04,0x03,"Logical unit not ready, "
+                "manual intervention required"},
+    {0x04,0x04,"Logical unit not ready, format in progress"},
+    {0x04,0x05,"Logical unit not ready, rebuild in progress"},
+    {0x04,0x06,"Logical unit not ready, recalculation in progress"},
+    {0x04,0x07,"Logical unit not ready, operation in progress"},
+    {0x04,0x08,"Logical unit not ready, long write in progress"},
+    {0x04,0x09,"Logical unit not ready, self-test in progress"},
+    {0x04,0x0a,"Logical unit "
+                "not accessible, asymmetric access state transition"},
+    {0x04,0x0b,"Logical unit "
+                "not accessible, target port in standby state"},
+    {0x04,0x0c,"Logical unit "
+                "not accessible, target port in unavailable state"},
+    {0x04,0x0d,"Logical unit not ready, structure check required"},
+    {0x04,0x0e,"Logical unit not ready, security session in progress"},
+    {0x04,0x10,"Logical unit not ready, "
+                "auxiliary memory not accessible"},
+    {0x04,0x11,"Logical unit not ready, "
+                "notify (enable spinup) required"},
+    {0x04,0x12,"Logical unit not ready, offline"},
+    {0x04,0x13,"Logical unit not ready, SA creation in progress"},
+    {0x04,0x14,"Logical unit not ready, space allocation in progress"},
+    {0x04,0x15,"Logical unit not ready, robotics disabled"},
+    {0x04,0x16,"Logical unit not ready, configuration required"},
+    {0x04,0x17,"Logical unit not ready, calibration required"},
+    {0x04,0x18,"Logical unit not ready, a door is open"},
+    {0x04,0x19,"Logical unit not ready, operating in sequential mode"},
+    {0x04,0x1a,"Logical unit not ready, start stop unit command in progress"},
+    {0x04,0x1b,"Logical unit not ready, sanitize in progress"},
+    {0x04,0x1c,"Logical unit not ready, additional power use not yet "
+                "granted"},
+    {0x04,0x1d,"Logical unit not ready, configuration in progress"},
+    {0x04,0x1e,"Logical unit not ready, microcode activation required"},
+    {0x04,0x1f,"Logical unit not ready, microcode download required"},
+    {0x04,0x20,"Logical unit not ready, logical unit reset required"},
+    {0x04,0x21,"Logical unit not ready, hard reset required"},
+    {0x04,0x22,"Logical unit not ready, power cycle required"},
+    {0x04,0x23,"Logical unit not ready, affiliation required"},
+    {0x05,0x00,"Logical unit does not respond to selection"},
+    {0x06,0x00,"No reference position found"},
+    {0x07,0x00,"Multiple peripheral devices selected"},
+    {0x08,0x00,"Logical unit communication failure"},
+    {0x08,0x01,"Logical unit communication time-out"},
+    {0x08,0x02,"Logical unit communication parity error"},
+    {0x08,0x03,"Logical unit communication CRC error (Ultra-DMA/32)"},
+    {0x08,0x04,"Unreachable copy target"},
+    {0x09,0x00,"Track following error"},
+    {0x09,0x01,"Tracking servo failure"},
+    {0x09,0x02,"Focus servo failure"},
+    {0x09,0x03,"Spindle servo failure"},
+    {0x09,0x04,"Head select fault"},
+    {0x09,0x05,"Vibration induced tracking error"},
+    {0x0A,0x00,"Error log overflow"},
+    {0x0B,0x00,"Warning"},
+    {0x0B,0x01,"Warning - specified temperature exceeded"},
+    {0x0B,0x02,"Warning - enclosure degraded"},
+    {0x0B,0x03,"Warning - background self-test failed"},
+    {0x0B,0x04,"Warning - background pre-scan detected medium error"},
+    {0x0B,0x05,"Warning - background medium scan detected medium error"},
+    {0x0B,0x06,"Warning - non-volatile cache now volatile"},
+    {0x0B,0x07,"Warning - degraded power to non-volatile cache"},
+    {0x0B,0x08,"Warning - power loss expected"},
+    {0x0B,0x09,"Warning - device statistics notification active"},
+    {0x0B,0x0A,"Warning - high critical temperature limit exceeded"},
+    {0x0B,0x0B,"Warning - low critical temperature limit exceeded"},
+    {0x0B,0x0C,"Warning - high operating temperature limit exceeded"},
+    {0x0B,0x0D,"Warning - low operating temperature limit exceeded"},
+    {0x0B,0x0E,"Warning - high critical humidity limit exceeded"},
+    {0x0B,0x0F,"Warning - low critical humidity limit exceeded"},
+    {0x0B,0x10,"Warning - high operating humidity limit exceeded"},
+    {0x0B,0x11,"Warning - low operating humidity limit exceeded"},
+    {0x0B,0x12,"Warning - microcode security at risk"},
+    {0x0B,0x13,"Warning - microcode digital signature validation failure"},
+    {0x0C,0x00,"Write error"},
+    {0x0C,0x01,"Write error - recovered with auto reallocation"},
+    {0x0C,0x02,"Write error - auto reallocation failed"},
+    {0x0C,0x03,"Write error - recommend reassignment"},
+    {0x0C,0x04,"Compression check miscompare error"},
+    {0x0C,0x05,"Data expansion occurred during compression"},
+    {0x0C,0x06,"Block not compressible"},
+    {0x0C,0x07,"Write error - recovery needed"},
+    {0x0C,0x08,"Write error - recovery failed"},
+    {0x0C,0x09,"Write error - loss of streaming"},
+    {0x0C,0x0A,"Write error - padding blocks added"},
+    {0x0C,0x0B,"Auxiliary memory write error"},
+    {0x0C,0x0C,"Write error - unexpected unsolicited data"},
+    {0x0C,0x0D,"Write error - not enough unsolicited data"},
+    {0x0C,0x0E,"Multiple write errors"},
+    {0x0C,0x0F,"Defects in error window"},
+    {0x0C,0x10,"Incomplete multiple atomic write operations"},
+    {0x0C,0x11,"Write error - recovery scan needed"},
+    {0x0C,0x12,"Write error - insufficient zone resources"},
+    {0x0D,0x00,"Error detected by third party temporary initiator"},
+    {0x0D,0x01,"Third party device failure"},
+    {0x0D,0x02,"Copy target device not reachable"},
+    {0x0D,0x03,"Incorrect copy target device type"},
+    {0x0D,0x04,"Copy target device data underrun"},
+    {0x0D,0x05,"Copy target device data overrun"},
+    {0x0E,0x00,"Invalid information unit"},
+    {0x0E,0x01,"Information unit too short"},
+    {0x0E,0x02,"Information unit too long"},
+    {0x0E,0x03,"Invalid field in command information unit"},
+    {0x10,0x00,"Id CRC or ECC error"},
+    {0x10,0x01,"Logical block guard check failed"},
+    {0x10,0x02,"Logical block application tag check failed"},
+    {0x10,0x03,"Logical block reference tag check failed"},
+    {0x10,0x04,"Logical block protection error on recover buffered data"},
+    {0x10,0x05,"Logical block protection method error"},
+    {0x11,0x00,"Unrecovered read error"},
+    {0x11,0x01,"Read retries exhausted"},
+    {0x11,0x02,"Error too long to correct"},
+    {0x11,0x03,"Multiple read errors"},
+    {0x11,0x04,"Unrecovered read error - auto reallocate failed"},
+    {0x11,0x05,"L-EC uncorrectable error"},
+    {0x11,0x06,"CIRC unrecovered error"},
+    {0x11,0x07,"Data re-synchronization error"},
+    {0x11,0x08,"Incomplete block read"},
+    {0x11,0x09,"No gap found"},
+    {0x11,0x0A,"Miscorrected error"},
+    {0x11,0x0B,"Unrecovered read error - recommend reassignment"},
+    {0x11,0x0C,"Unrecovered read error - recommend rewrite the data"},
+    {0x11,0x0D,"De-compression CRC error"},
+    {0x11,0x0E,"Cannot decompress using declared algorithm"},
+    {0x11,0x0F,"Error reading UPC/EAN number"},
+    {0x11,0x10,"Error reading ISRC number"},
+    {0x11,0x11,"Read error - loss of streaming"},
+    {0x11,0x12,"Auxiliary memory read error"},
+    {0x11,0x13,"Read error - failed retransmission request"},
+    {0x11,0x14,"Read error - LBA marked bad by application client"},
+    {0x11,0x15,"Write after sanitize required"},
+    {0x12,0x00,"Address mark not found for id field"},
+    {0x13,0x00,"Address mark not found for data field"},
+    {0x14,0x00,"Recorded entity not found"},
+    {0x14,0x01,"Record not found"},
+    {0x14,0x02,"Filemark or setmark not found"},
+    {0x14,0x03,"End-of-data not found"},
+    {0x14,0x04,"Block sequence error"},
+    {0x14,0x05,"Record not found - recommend reassignment"},
+    {0x14,0x06,"Record not found - data auto-reallocated"},
+    {0x14,0x07,"Locate operation failure"},
+    {0x15,0x00,"Random positioning error"},
+    {0x15,0x01,"Mechanical positioning error"},
+    {0x15,0x02,"Positioning error detected by read of medium"},
+    {0x16,0x00,"Data synchronization mark error"},
+    {0x16,0x01,"Data sync error - data rewritten"},
+    {0x16,0x02,"Data sync error - recommend rewrite"},
+    {0x16,0x03,"Data sync error - data auto-reallocated"},
+    {0x16,0x04,"Data sync error - recommend reassignment"},
+    {0x17,0x00,"Recovered data with no error correction applied"},
+    {0x17,0x01,"Recovered data with retries"},
+    {0x17,0x02,"Recovered data with positive head offset"},
+    {0x17,0x03,"Recovered data with negative head offset"},
+    {0x17,0x04,"Recovered data with retries and/or circ applied"},
+    {0x17,0x05,"Recovered data using previous sector id"},
+    {0x17,0x06,"Recovered data without ECC - data auto-reallocated"},
+    {0x17,0x07,"Recovered data without ECC - recommend reassignment"},
+    {0x17,0x08,"Recovered data without ECC - recommend rewrite"},
+    {0x17,0x09,"Recovered data without ECC - data rewritten"},
+    {0x18,0x00,"Recovered data with error correction applied"},
+    {0x18,0x01,"Recovered data with error corr. & retries applied"},
+    {0x18,0x02,"Recovered data - data auto-reallocated"},
+    {0x18,0x03,"Recovered data with CIRC"},
+    {0x18,0x04,"Recovered data with L-EC"},
+    {0x18,0x05,"Recovered data - recommend reassignment"},
+    {0x18,0x06,"Recovered data - recommend rewrite"},
+    {0x18,0x07,"Recovered data with ECC - data rewritten"},
+    {0x18,0x08,"Recovered data with linking"},
+    {0x19,0x00,"Defect list error"},
+    {0x19,0x01,"Defect list not available"},
+    {0x19,0x02,"Defect list error in primary list"},
+    {0x19,0x03,"Defect list error in grown list"},
+    {0x1A,0x00,"Parameter list length error"},
+    {0x1B,0x00,"Synchronous data transfer error"},
+    {0x1C,0x00,"Defect list not found"},
+    {0x1C,0x01,"Primary defect list not found"},
+    {0x1C,0x02,"Grown defect list not found"},
+    {0x1D,0x00,"Miscompare during verify operation"},
+    {0x1D,0x01,"Miscompare verify of unmapped lba"},
+    {0x1E,0x00,"Recovered id with ECC correction"},
+    {0x1F,0x00,"Partial defect list transfer"},
+    {0x20,0x00,"Invalid command operation code"},
+    {0x20,0x01,"Access denied - initiator pending-enrolled"},
+    {0x20,0x02,"Access denied - no access rights"},
+    {0x20,0x03,"Access denied - invalid mgmt id key"},
+    {0x20,0x04,"Illegal command while in write capable state"},
+    {0x20,0x05,"Write type operation while in read capable state (obs)"},
+    {0x20,0x06,"Illegal command while in explicit address mode"},
+    {0x20,0x07,"Illegal command while in implicit address mode"},
+    {0x20,0x08,"Access denied - enrollment conflict"},
+    {0x20,0x09,"Access denied - invalid LU identifier"},
+    {0x20,0x0A,"Access denied - invalid proxy token"},
+    {0x20,0x0B,"Access denied - ACL LUN conflict"},
+    {0x20,0x0C,"Illegal command when not in append-only mode"},
+    {0x20,0x0D,"Not an administrative logical unit"},
+    {0x20,0x0E,"Not a subsidiary logical unit"},
+    {0x20,0x0F,"Not a conglomerate logical unit"},
+    {0x21,0x00,"Logical block address out of range"},
+    {0x21,0x01,"Invalid element address"},
+    {0x21,0x02,"Invalid address for write"},
+    {0x21,0x03,"Invalid write crossing layer jump"},
+    {0x21,0x04,"Unaligned write command"},
+    {0x21,0x05,"Write boundary violation"},
+    {0x21,0x06,"Attempt to read invalid data"},
+    {0x21,0x07,"Read boundary violation"},
+    {0x21,0x08,"Misaligned write command"},
+    {0x22,0x00,"Illegal function (use 20 00, 24 00, or 26 00)"},
+    {0x23,0x00,"Invalid token operation, cause not reportable"},
+    {0x23,0x01,"Invalid token operation, unsupported token type"},
+    {0x23,0x02,"Invalid token operation, remote token usage not supported"},
+    {0x23,0x03,"invalid token operation, remote rod token creation not "
+               "supported"},
+    {0x23,0x04,"Invalid token operation, token unknown"},
+    {0x23,0x05,"Invalid token operation, token corrupt"},
+    {0x23,0x06,"Invalid token operation, token revoked"},
+    {0x23,0x07,"Invalid token operation, token expired"},
+    {0x23,0x08,"Invalid token operation, token cancelled"},
+    {0x23,0x09,"Invalid token operation, token deleted"},
+    {0x23,0x0a,"Invalid token operation, invalid token length"},
+    {0x24,0x00,"Invalid field in cdb"},
+    {0x24,0x01,"CDB decryption error"},
+    {0x24,0x02,"Invalid cdb field while in explicit block model (obs)"},
+    {0x24,0x03,"Invalid cdb field while in implicit block model (obs)"},
+    {0x24,0x04,"Security audit value frozen"},
+    {0x24,0x05,"Security working key frozen"},
+    {0x24,0x06,"Nonce not unique"},
+    {0x24,0x07,"Nonce timestamp out of range"},
+    {0x24,0x08,"Invalid xcdb"},
+    {0x24,0x09,"Invalid fast format"},
+    {0x25,0x00,"Logical unit not supported"},
+    {0x26,0x00,"Invalid field in parameter list"},
+    {0x26,0x01,"Parameter not supported"},
+    {0x26,0x02,"Parameter value invalid"},
+    {0x26,0x03,"Threshold parameters not supported"},
+    {0x26,0x04,"Invalid release of persistent reservation"},
+    {0x26,0x05,"Data decryption error"},
+    {0x26,0x06,"Too many target descriptors"},
+    {0x26,0x07,"Unsupported target descriptor type code"},
+    {0x26,0x08,"Too many segment descriptors"},
+    {0x26,0x09,"Unsupported segment descriptor type code"},
+    {0x26,0x0A,"Unexpected inexact segment"},
+    {0x26,0x0B,"Inline data length exceeded"},
+    {0x26,0x0C,"Invalid operation for copy source or destination"},
+    {0x26,0x0D,"Copy segment granularity violation"},
+    {0x26,0x0E,"Invalid parameter while port is enabled"},
+    {0x26,0x0F,"Invalid data-out buffer integrity check value"},
+    {0x26,0x10,"Data decryption key fail limit reached"},
+    {0x26,0x11,"Incomplete key-associated data set"},
+    {0x26,0x12,"Vendor specific key reference not found"},
+    {0x26,0x13,"Application tag mode page is invalid"},
+    {0x26,0x14,"Tape stream mirroring prevented"},
+    {0x26,0x15,"Copy source or copy destination not authorized"},
+    {0x27,0x00,"Write protected"},
+    {0x27,0x01,"Hardware write protected"},
+    {0x27,0x02,"Logical unit software write protected"},
+    {0x27,0x03,"Associated write protect"},
+    {0x27,0x04,"Persistent write protect"},
+    {0x27,0x05,"Permanent write protect"},
+    {0x27,0x06,"Conditional write protect"},
+    {0x27,0x07,"Space allocation failed write protect"},
+    {0x27,0x08,"Zone is read only"},
+    {0x28,0x00,"Not ready to ready change, medium may have changed"},
+    {0x28,0x01,"Import or export element accessed"},
+    {0x28,0x02,"Format-layer may have changed"},
+    {0x28,0x03,"Import/export element accessed, medium changed"},
+    {0x29,0x00,"Power on, reset, or bus device reset occurred"},
+    {0x29,0x01,"Power on occurred"},
+    {0x29,0x02,"SCSI bus reset occurred"},
+    {0x29,0x03,"Bus device reset function occurred"},
+    {0x29,0x04,"Device internal reset"},
+    {0x29,0x05,"Transceiver mode changed to single-ended"},
+    {0x29,0x06,"Transceiver mode changed to lvd"},
+    {0x29,0x07,"I_T nexus loss occurred"},
+    {0x2A,0x00,"Parameters changed"},
+    {0x2A,0x01,"Mode parameters changed"},
+    {0x2A,0x02,"Log parameters changed"},
+    {0x2A,0x03,"Reservations preempted"},
+    {0x2A,0x04,"Reservations released"},
+    {0x2A,0x05,"Registrations preempted"},
+    {0x2A,0x06,"Asymmetric access state changed"},
+    {0x2A,0x07,"Implicit asymmetric access state transition failed"},
+    {0x2A,0x08,"Priority changed"},
+    {0x2A,0x09,"Capacity data has changed"},
+    {0x2A,0x0c, "Error recovery attributes have changed"},
+    {0x2A,0x0d, "Data encryption capabilities changed"},
+    {0x2A,0x10,"Timestamp changed"},
+    {0x2A,0x11,"Data encryption parameters changed by another i_t nexus"},
+    {0x2A,0x12,"Data encryption parameters changed by vendor specific event"},
+    {0x2A,0x13,"Data encryption key instance counter has changed"},
+    {0x2A,0x0a,"Error history i_t nexus cleared"},
+    {0x2A,0x0b,"Error history snapshot released"},
+    {0x2A,0x14,"SA creation capabilities data has changed"},
+    {0x2A,0x15,"Medium removal prevention preempted"},
+    {0x2A,0x16,"Zone reset write pointer recommended"},
+    {0x2B,0x00,"Copy cannot execute since host cannot disconnect"},
+    {0x2C,0x00,"Command sequence error"},
+    {0x2C,0x01,"Too many windows specified"},
+    {0x2C,0x02,"Invalid combination of windows specified"},
+    {0x2C,0x03,"Current program area is not empty"},
+    {0x2C,0x04,"Current program area is empty"},
+    {0x2C,0x05,"Illegal power condition request"},
+    {0x2C,0x06,"Persistent prevent conflict"},
+    {0x2C,0x07,"Previous busy status"},
+    {0x2C,0x08,"Previous task set full status"},
+    {0x2C,0x09,"Previous reservation conflict status"},
+    {0x2C,0x0A,"Partition or collection contains user objects"},
+    {0x2C,0x0B,"Not reserved"},
+    {0x2C,0x0C,"ORWRITE generation does not match"},
+    {0x2C,0x0D,"Reset write pointer not allowed"},
+    {0x2C,0x0E,"Zone is offline"},
+    {0x2C,0x0F,"Stream not open"},
+    {0x2C,0x10,"Unwritten data in zone"},
+    {0x2C,0x11,"Descriptor format sense data required"},
+    {0x2D,0x00,"Overwrite error on update in place"},
+    {0x2E,0x00,"Insufficient time for operation"},
+    {0x2E,0x01,"Command timeout before processing"},
+    {0x2E,0x02,"Command timeout during processing"},
+    {0x2E,0x03,"Command timeout during processing due to error recovery"},
+    {0x2F,0x00,"Commands cleared by another initiator"},
+    {0x2F,0x01,"Commands cleared by power loss notification"},
+    {0x2F,0x02,"Commands cleared by device server"},
+    {0x2F,0x03,"Some commands cleared by queuing layer event"},
+    {0x30,0x00,"Incompatible medium installed"},
+    {0x30,0x01,"Cannot read medium - unknown format"},
+    {0x30,0x02,"Cannot read medium - incompatible format"},
+    {0x30,0x03,"Cleaning cartridge installed"},
+    {0x30,0x04,"Cannot write medium - unknown format"},
+    {0x30,0x05,"Cannot write medium - incompatible format"},
+    {0x30,0x06,"Cannot format medium - incompatible medium"},
+    {0x30,0x07,"Cleaning failure"},
+    {0x30,0x08,"Cannot write - application code mismatch"},
+    {0x30,0x09,"Current session not fixated for append"},
+    {0x30,0x0A,"Cleaning request rejected"},
+    {0x30,0x0B,"Cleaning tape expired"},
+    {0x30,0x0C,"WORM medium - overwrite attempted"},
+    {0x30,0x0D,"WORM medium - integrity check"},
+    {0x30,0x10,"Medium not formatted"},
+    {0x30,0x11,"Incompatible volume type"},
+    {0x30,0x12,"Incompatible volume qualifier"},
+    {0x30,0x13,"Cleaning volume expired"},
+    {0x31,0x00,"Medium format corrupted"},
+    {0x31,0x01,"Format command failed"},
+    {0x31,0x02,"Zoned formatting failed due to spare linking"},
+    {0x31,0x03,"Sanitize command failed"},
+    {0x32,0x00,"No defect spare location available"},
+    {0x32,0x01,"Defect list update failure"},
+    {0x33,0x00,"Tape length error"},
+    {0x34,0x00,"Enclosure failure"},
+    {0x35,0x00,"Enclosure services failure"},
+    {0x35,0x01,"Unsupported enclosure function"},
+    {0x35,0x02,"Enclosure services unavailable"},
+    {0x35,0x03,"Enclosure services transfer failure"},
+    {0x35,0x04,"Enclosure services transfer refused"},
+    {0x35,0x05,"Enclosure services checksum error"},
+    {0x36,0x00,"Ribbon, ink, or toner failure"},
+    {0x37,0x00,"Rounded parameter"},
+    {0x38,0x00,"Event status notification"},
+    {0x38,0x02,"Esn - power management class event"},
+    {0x38,0x04,"Esn - media class event"},
+    {0x38,0x06,"Esn - device busy class event"},
+    {0x38,0x07,"Thin provisioning soft threshold reached"},
+    {0x39,0x00,"Saving parameters not supported"},
+    {0x3A,0x00,"Medium not present"},
+    {0x3A,0x01,"Medium not present - tray closed"},
+    {0x3A,0x02,"Medium not present - tray open"},
+    {0x3A,0x03,"Medium not present - loadable"},
+    {0x3A,0x04,"Medium not present - medium auxiliary memory accessible"},
+    {0x3B,0x00,"Sequential positioning error"},
+    {0x3B,0x01,"Tape position error at beginning-of-medium"},
+    {0x3B,0x02,"Tape position error at end-of-medium"},
+    {0x3B,0x03,"Tape or electronic vertical forms unit not ready"},
+    {0x3B,0x04,"Slew failure"},
+    {0x3B,0x05,"Paper jam"},
+    {0x3B,0x06,"Failed to sense top-of-form"},
+    {0x3B,0x07,"Failed to sense bottom-of-form"},
+    {0x3B,0x08,"Reposition error"},
+    {0x3B,0x09,"Read past end of medium"},
+    {0x3B,0x0A,"Read past beginning of medium"},
+    {0x3B,0x0B,"Position past end of medium"},
+    {0x3B,0x0C,"Position past beginning of medium"},
+    {0x3B,0x0D,"Medium destination element full"},
+    {0x3B,0x0E,"Medium source element empty"},
+    {0x3B,0x0F,"End of medium reached"},
+    {0x3B,0x11,"Medium magazine not accessible"},
+    {0x3B,0x12,"Medium magazine removed"},
+    {0x3B,0x13,"Medium magazine inserted"},
+    {0x3B,0x14,"Medium magazine locked"},
+    {0x3B,0x15,"Medium magazine unlocked"},
+    {0x3B,0x16,"Mechanical positioning or changer error"},
+    {0x3B,0x17,"Read past end of user object"},
+    {0x3B,0x18,"Element disabled"},
+    {0x3B,0x19,"Element enabled"},
+    {0x3B,0x1a,"Data transfer device removed"},
+    {0x3B,0x1b,"Data transfer device inserted"},
+    {0x3B,0x1c,"Too many logical objects on partition to support operation"},
+    {0x3D,0x00,"Invalid bits in identify message"},
+    {0x3E,0x00,"Logical unit has not self-configured yet"},
+    {0x3E,0x01,"Logical unit failure"},
+    {0x3E,0x02,"Timeout on logical unit"},
+    {0x3E,0x03,"Logical unit failed self-test"},
+    {0x3E,0x04,"Logical unit unable to update self-test log"},
+    {0x3F,0x00,"Target operating conditions have changed"},
+    {0x3F,0x01,"Microcode has been changed"},
+    {0x3F,0x02,"Changed operating definition"},
+    {0x3F,0x03,"Inquiry data has changed"},
+    {0x3F,0x04,"Component device attached"},
+    {0x3F,0x05,"Device identifier changed"},
+    {0x3F,0x06,"Redundancy group created or modified"},
+    {0x3F,0x07,"Redundancy group deleted"},
+    {0x3F,0x08,"Spare created or modified"},
+    {0x3F,0x09,"Spare deleted"},
+    {0x3F,0x0A,"Volume set created or modified"},
+    {0x3F,0x0B,"Volume set deleted"},
+    {0x3F,0x0C,"Volume set deassigned"},
+    {0x3F,0x0D,"Volume set reassigned"},
+    {0x3F,0x0E,"Reported luns data has changed"},
+    {0x3F,0x0F,"Echo buffer overwritten"},
+    {0x3F,0x10,"Medium loadable"},
+    {0x3F,0x11,"Medium auxiliary memory accessible"},
+    {0x3F,0x12,"iSCSI IP address added"},
+    {0x3F,0x13,"iSCSI IP address removed"},
+    {0x3F,0x14,"iSCSI IP address changed"},
+    {0x3F,0x15,"Inspect referrals sense descriptors"},
+    {0x3F,0x16,"Microcode has been changed without reset"},
+    {0x3F,0x17,"Zone transition to full"},
+    {0x3F,0x18,"Bind completed"},
+    {0x3F,0x19,"Bind redirected"},
+    {0x3F,0x1A,"Subsidiary binding changed"},
+
+    /*
+     * ASC 0x40, 0x41 and 0x42 overridden by "additional2" array entries
+     * for ascq > 1. Preferred error message for this group is
+     * "Diagnostic failure on component nn (80h-ffh)".
+     */
+    {0x40,0x00,"Ram failure (should use 40 nn)"},
+    {0x41,0x00,"Data path failure (should use 40 nn)"},
+    {0x42,0x00,"Power-on or self-test failure (should use 40 nn)"},
+
+    {0x43,0x00,"Message error"},
+    {0x44,0x00,"Internal target failure"},
+    {0x44,0x01,"Persistent reservation information lost"},
+    {0x44,0x71,"ATA device failed Set Features"},
+    {0x45,0x00,"Select or reselect failure"},
+    {0x46,0x00,"Unsuccessful soft reset"},
+    {0x47,0x00,"SCSI parity error"},
+    {0x47,0x01,"Data phase CRC error detected"},
+    {0x47,0x02,"SCSI parity error detected during st data phase"},
+    {0x47,0x03,"Information unit iuCRC error detected"},
+    {0x47,0x04,"Asynchronous information protection error detected"},
+    {0x47,0x05,"Protocol service CRC error"},
+    {0x47,0x06,"Phy test function in progress"},
+    {0x47,0x7F,"Some commands cleared by iSCSI protocol event"},
+    {0x48,0x00,"Initiator detected error message received"},
+    {0x49,0x00,"Invalid message error"},
+    {0x4A,0x00,"Command phase error"},
+    {0x4B,0x00,"Data phase error"},
+    {0x4B,0x01,"Invalid target port transfer tag received"},
+    {0x4B,0x02,"Too much write data"},
+    {0x4B,0x03,"Ack/nak timeout"},
+    {0x4B,0x04,"Nak received"},
+    {0x4B,0x05,"Data offset error"},
+    {0x4B,0x06,"Initiator response timeout"},
+    {0x4B,0x07,"Connection lost"},
+    {0x4B,0x08,"Data-in buffer overflow - data buffer size"},
+    {0x4B,0x09,"Data-in buffer overflow - data buffer descriptor area"},
+    {0x4B,0x0A,"Data-in buffer error"},
+    {0x4B,0x0B,"Data-out buffer overflow - data buffer size"},
+    {0x4B,0x0C,"Data-out buffer overflow - data buffer descriptor area"},
+    {0x4B,0x0D,"Data-out buffer error"},
+    {0x4B,0x0E,"PCIe fabric error"},
+    {0x4B,0x0f,"PCIe completion timeout"},
+    {0x4B,0x10,"PCIe completer abort"},
+    {0x4B,0x11,"PCIe poisoned tlp received"},
+    {0x4B,0x12,"PCIe ecrc check failed"},
+    {0x4B,0x13,"PCIe unsupported request"},
+    {0x4B,0x14,"PCIe acs violation"},
+    {0x4B,0x15,"PCIe tlp prefix blocked"},
+    {0x4C,0x00,"Logical unit failed self-configuration"},
+    /*
+     * ASC 0x4D overridden by an "additional2" array entry
+     * so there is no need to have them here.
+     */
+    /* {0x4D,0x00,"Tagged overlapped commands (nn = queue tag)"}, */
+
+    {0x4E,0x00,"Overlapped commands attempted"},
+    {0x50,0x00,"Write append error"},
+    {0x50,0x01,"Write append position error"},
+    {0x50,0x02,"Position error related to timing"},
+    {0x51,0x00,"Erase failure"},
+    {0x51,0x01,"Erase failure - incomplete erase operation detected"},
+    {0x52,0x00,"Cartridge fault"},
+    {0x53,0x00,"Media load or eject failed"},
+    {0x53,0x01,"Unload tape failure"},
+    {0x53,0x02,"Medium removal prevented"},
+    {0x53,0x03,"Medium removal prevented by data transfer element"},
+    {0x53,0x04,"Medium thread or unthread failure"},
+    {0x53,0x05,"Volume identifier invalid"},
+    {0x53,0x06,"Volume identifier missing"},
+    {0x53,0x07,"Duplicate volume identifier"},
+    {0x53,0x08,"Element status unknown"},
+    {0x53,0x09,"Data transfer device error - load failed"},
+    {0x53,0x0A,"Data transfer device error - unload failed"},
+    {0x53,0x0B,"Data transfer device error - unload missing"},
+    {0x53,0x0C,"Data transfer device error - eject failed"},
+    {0x53,0x0D,"Data transfer device error - library communication failed"},
+    {0x54,0x00,"SCSI to host system interface failure"},
+    {0x55,0x00,"System resource failure"},
+    {0x55,0x01,"System buffer full"},
+    {0x55,0x02,"Insufficient reservation resources"},
+    {0x55,0x03,"Insufficient resources"},
+    {0x55,0x04,"Insufficient registration resources"},
+    {0x55,0x05,"Insufficient access control resources"},
+    {0x55,0x06,"Auxiliary memory out of space"},
+    {0x55,0x07,"Quota error"},
+    {0x55,0x08,"Maximum number of supplemental decryption keys exceeded"},
+    {0x55,0x09,"Medium auxiliary memory not accessible"},
+    {0x55,0x0a,"Data currently unavailable"},
+    {0x55,0x0b,"Insufficient power for operation"},
+    {0x55,0x0c,"Insufficient resources to create rod"},
+    {0x55,0x0d,"Insufficient resources to create rod token"},
+    {0x55,0x0e,"Insufficient zone resources"},
+    {0x55,0x0f,"Insufficient zone resources to complete write"},
+    {0x55,0x10,"Maximum number of streams open"},
+    {0x55,0x11,"Insufficient resources to bind"},
+    {0x57,0x00,"Unable to recover table-of-contents"},
+    {0x58,0x00,"Generation does not exist"},
+    {0x59,0x00,"Updated block read"},
+    {0x5A,0x00,"Operator request or state change input"},
+    {0x5A,0x01,"Operator medium removal request"},
+    {0x5A,0x02,"Operator selected write protect"},
+    {0x5A,0x03,"Operator selected write permit"},
+    {0x5B,0x00,"Log exception"},
+    {0x5B,0x01,"Threshold condition met"},
+    {0x5B,0x02,"Log counter at maximum"},
+    {0x5B,0x03,"Log list codes exhausted"},
+    {0x5C,0x00,"Rpl status change"},
+    {0x5C,0x01,"Spindles synchronized"},
+    {0x5C,0x02,"Spindles not synchronized"},
+    {0x5D,0x00,"Failure prediction threshold exceeded"},
+    {0x5D,0x01,"Media failure prediction threshold exceeded"},
+    {0x5D,0x02,"Logical unit failure prediction threshold exceeded"},
+    {0x5D,0x03,"spare area exhaustion prediction threshold exceeded"},
+    {0x5D,0x10,"Hardware impending failure general hard drive failure"},
+    {0x5D,0x11,"Hardware impending failure drive error rate too high" },
+    {0x5D,0x12,"Hardware impending failure data error rate too high" },
+    {0x5D,0x13,"Hardware impending failure seek error rate too high" },
+    {0x5D,0x14,"Hardware impending failure too many block reassigns"},
+    {0x5D,0x15,"Hardware impending failure access times too high" },
+    {0x5D,0x16,"Hardware impending failure start unit times too high" },
+    {0x5D,0x17,"Hardware impending failure channel parametrics"},
+    {0x5D,0x18,"Hardware impending failure controller detected"},
+    {0x5D,0x19,"Hardware impending failure throughput performance"},
+    {0x5D,0x1A,"Hardware impending failure seek time performance"},
+    {0x5D,0x1B,"Hardware impending failure spin-up retry count"},
+    {0x5D,0x1C,"Hardware impending failure drive calibration retry count"},
+    {0x5D,0x1D,"Hardware impending failure power loss protection circuit"},
+    {0x5D,0x20,"Controller impending failure general hard drive failure"},
+    {0x5D,0x21,"Controller impending failure drive error rate too high" },
+    {0x5D,0x22,"Controller impending failure data error rate too high" },
+    {0x5D,0x23,"Controller impending failure seek error rate too high" },
+    {0x5D,0x24,"Controller impending failure too many block reassigns"},
+    {0x5D,0x25,"Controller impending failure access times too high" },
+    {0x5D,0x26,"Controller impending failure start unit times too high" },
+    {0x5D,0x27,"Controller impending failure channel parametrics"},
+    {0x5D,0x28,"Controller impending failure controller detected"},
+    {0x5D,0x29,"Controller impending failure throughput performance"},
+    {0x5D,0x2A,"Controller impending failure seek time performance"},
+    {0x5D,0x2B,"Controller impending failure spin-up retry count"},
+    {0x5D,0x2C,"Controller impending failure drive calibration retry count"},
+    {0x5D,0x30,"Data channel impending failure general hard drive failure"},
+    {0x5D,0x31,"Data channel impending failure drive error rate too high" },
+    {0x5D,0x32,"Data channel impending failure data error rate too high" },
+    {0x5D,0x33,"Data channel impending failure seek error rate too high" },
+    {0x5D,0x34,"Data channel impending failure too many block reassigns"},
+    {0x5D,0x35,"Data channel impending failure access times too high" },
+    {0x5D,0x36,"Data channel impending failure start unit times too high" },
+    {0x5D,0x37,"Data channel impending failure channel parametrics"},
+    {0x5D,0x38,"Data channel impending failure controller detected"},
+    {0x5D,0x39,"Data channel impending failure throughput performance"},
+    {0x5D,0x3A,"Data channel impending failure seek time performance"},
+    {0x5D,0x3B,"Data channel impending failure spin-up retry count"},
+    {0x5D,0x3C,"Data channel impending failure drive calibration retry count"},
+    {0x5D,0x40,"Servo impending failure general hard drive failure"},
+    {0x5D,0x41,"Servo impending failure drive error rate too high" },
+    {0x5D,0x42,"Servo impending failure data error rate too high" },
+    {0x5D,0x43,"Servo impending failure seek error rate too high" },
+    {0x5D,0x44,"Servo impending failure too many block reassigns"},
+    {0x5D,0x45,"Servo impending failure access times too high" },
+    {0x5D,0x46,"Servo impending failure start unit times too high" },
+    {0x5D,0x47,"Servo impending failure channel parametrics"},
+    {0x5D,0x48,"Servo impending failure controller detected"},
+    {0x5D,0x49,"Servo impending failure throughput performance"},
+    {0x5D,0x4A,"Servo impending failure seek time performance"},
+    {0x5D,0x4B,"Servo impending failure spin-up retry count"},
+    {0x5D,0x4C,"Servo impending failure drive calibration retry count"},
+    {0x5D,0x50,"Spindle impending failure general hard drive failure"},
+    {0x5D,0x51,"Spindle impending failure drive error rate too high" },
+    {0x5D,0x52,"Spindle impending failure data error rate too high" },
+    {0x5D,0x53,"Spindle impending failure seek error rate too high" },
+    {0x5D,0x54,"Spindle impending failure too many block reassigns"},
+    {0x5D,0x55,"Spindle impending failure access times too high" },
+    {0x5D,0x56,"Spindle impending failure start unit times too high" },
+    {0x5D,0x57,"Spindle impending failure channel parametrics"},
+    {0x5D,0x58,"Spindle impending failure controller detected"},
+    {0x5D,0x59,"Spindle impending failure throughput performance"},
+    {0x5D,0x5A,"Spindle impending failure seek time performance"},
+    {0x5D,0x5B,"Spindle impending failure spin-up retry count"},
+    {0x5D,0x5C,"Spindle impending failure drive calibration retry count"},
+    {0x5D,0x60,"Firmware impending failure general hard drive failure"},
+    {0x5D,0x61,"Firmware impending failure drive error rate too high" },
+    {0x5D,0x62,"Firmware impending failure data error rate too high" },
+    {0x5D,0x63,"Firmware impending failure seek error rate too high" },
+    {0x5D,0x64,"Firmware impending failure too many block reassigns"},
+    {0x5D,0x65,"Firmware impending failure access times too high" },
+    {0x5D,0x66,"Firmware impending failure start unit times too high" },
+    {0x5D,0x67,"Firmware impending failure channel parametrics"},
+    {0x5D,0x68,"Firmware impending failure controller detected"},
+    {0x5D,0x69,"Firmware impending failure throughput performance"},
+    {0x5D,0x6A,"Firmware impending failure seek time performance"},
+    {0x5D,0x6B,"Firmware impending failure spin-up retry count"},
+    {0x5D,0x6C,"Firmware impending failure drive calibration retry count"},
+    {0x5D,0x73,"Media impending failure endurance limit met"},
+    {0x5D,0xFF,"Failure prediction threshold exceeded (false)"},
+    {0x5E,0x00,"Low power condition on"},
+    {0x5E,0x01,"Idle condition activated by timer"},
+    {0x5E,0x02,"Standby condition activated by timer"},
+    {0x5E,0x03,"Idle condition activated by command"},
+    {0x5E,0x04,"Standby condition activated by command"},
+    {0x5E,0x05,"Idle_b condition activated by timer"},
+    {0x5E,0x06,"Idle_b condition activated by command"},
+    {0x5E,0x07,"Idle_c condition activated by timer"},
+    {0x5E,0x08,"Idle_c condition activated by command"},
+    {0x5E,0x09,"Standby_y condition activated by timer"},
+    {0x5E,0x0a,"Standby_y condition activated by command"},
+    {0x5E,0x41,"Power state change to active"},
+    {0x5E,0x42,"Power state change to idle"},
+    {0x5E,0x43,"Power state change to standby"},
+    {0x5E,0x45,"Power state change to sleep"},
+    {0x5E,0x47,"Power state change to device control"},
+    {0x60,0x00,"Lamp failure"},
+    {0x61,0x00,"Video acquisition error"},
+    {0x61,0x01,"Unable to acquire video"},
+    {0x61,0x02,"Out of focus"},
+    {0x62,0x00,"Scan head positioning error"},
+    {0x63,0x00,"End of user area encountered on this track"},
+    {0x63,0x01,"Packet does not fit in available space"},
+    {0x64,0x00,"Illegal mode for this track"},
+    {0x64,0x01,"Invalid packet size"},
+    {0x65,0x00,"Voltage fault"},
+    {0x66,0x00,"Automatic document feeder cover up"},
+    {0x66,0x01,"Automatic document feeder lift up"},
+    {0x66,0x02,"Document jam in automatic document feeder"},
+    {0x66,0x03,"Document miss feed automatic in document feeder"},
+    {0x67,0x00,"Configuration failure"},
+    {0x67,0x01,"Configuration of incapable logical units failed"},
+    {0x67,0x02,"Add logical unit failed"},
+    {0x67,0x03,"Modification of logical unit failed"},
+    {0x67,0x04,"Exchange of logical unit failed"},
+    {0x67,0x05,"Remove of logical unit failed"},
+    {0x67,0x06,"Attachment of logical unit failed"},
+    {0x67,0x07,"Creation of logical unit failed"},
+    {0x67,0x08,"Assign failure occurred"},
+    {0x67,0x09,"Multiply assigned logical unit"},
+    {0x67,0x0A,"Set target port groups command failed"},
+    {0x67,0x0B,"ATA device feature not enabled"},
+    {0x67,0x0C,"Command rejected"},
+    {0x67,0x0D,"Explicit bind not allowed"},
+    {0x68,0x00,"Logical unit not configured"},
+    {0x68,0x01,"Subsidiary logical unit not configured"},
+    {0x69,0x00,"Data loss on logical unit"},
+    {0x69,0x01,"Multiple logical unit failures"},
+    {0x69,0x02,"Parity/data mismatch"},
+    {0x6A,0x00,"Informational, refer to log"},
+    {0x6B,0x00,"State change has occurred"},
+    {0x6B,0x01,"Redundancy level got better"},
+    {0x6B,0x02,"Redundancy level got worse"},
+    {0x6C,0x00,"Rebuild failure occurred"},
+    {0x6D,0x00,"Recalculate failure occurred"},
+    {0x6E,0x00,"Command to logical unit failed"},
+    {0x6F,0x00,"Copy protection key exchange failure - authentication "
+               "failure"},
+    {0x6F,0x01,"Copy protection key exchange failure - key not present"},
+    {0x6F,0x02,"Copy protection key exchange failure - key not established"},
+    {0x6F,0x03,"Read of scrambled sector without authentication"},
+    {0x6F,0x04,"Media region code is mismatched to logical unit region"},
+    {0x6F,0x05,"Drive region must be permanent/region reset count error"},
+    {0x6F,0x06,"Insufficient block count for binding nonce recording"},
+    {0x6F,0x07,"Conflict in binding nonce recording"},
+    {0x6F,0x08,"Insufficient permission"},
+    {0x6F,0x09,"Invalid drive-host pairing server"},
+    {0x6F,0x0A,"Drive-host pairing suspended"},
+    /*
+     * ASC 0x70 overridden by an "additional2" array entry
+     * so there is no need to have them here.
+     */
+    /* {0x70,0x00,"Decompression exception short algorithm id of nn"}, */
+
+    {0x71,0x00,"Decompression exception long algorithm id"},
+    {0x72,0x00,"Session fixation error"},
+    {0x72,0x01,"Session fixation error writing lead-in"},
+    {0x72,0x02,"Session fixation error writing lead-out"},
+    {0x72,0x03,"Session fixation error - incomplete track in session"},
+    {0x72,0x04,"Empty or partially written reserved track"},
+    {0x72,0x05,"No more track reservations allowed"},
+    {0x72,0x06,"RMZ extension is not allowed"},
+    {0x72,0x07,"No more test zone extensions are allowed"},
+    {0x73,0x00,"CD control error"},
+    {0x73,0x01,"Power calibration area almost full"},
+    {0x73,0x02,"Power calibration area is full"},
+    {0x73,0x03,"Power calibration area error"},
+    {0x73,0x04,"Program memory area update failure"},
+    {0x73,0x05,"Program memory area is full"},
+    {0x73,0x06,"RMA/PMA is almost full"},
+    {0x73,0x10,"Current power calibration area almost full"},
+    {0x73,0x11,"Current power calibration area is full"},
+    {0x73,0x17,"RDZ is full"},
+    {0x74,0x00,"Security error"},
+    {0x74,0x01,"Unable to decrypt data"},
+    {0x74,0x02,"Unencrypted data encountered while decrypting"},
+    {0x74,0x03,"Incorrect data encryption key"},
+    {0x74,0x04,"Cryptographic integrity validation failed"},
+    {0x74,0x05,"Error decrypting data"},
+    {0x74,0x06,"Unknown signature verification key"},
+    {0x74,0x07,"Encryption parameters not useable"},
+    {0x74,0x08,"Digital signature validation failure"},
+    {0x74,0x09,"Encryption mode mismatch on read"},
+    {0x74,0x0a,"Encrypted block not raw read enabled"},
+    {0x74,0x0b,"Incorrect Encryption parameters"},
+    {0x74,0x0c,"Unable to decrypt parameter list"},
+    {0x74,0x0d,"Encryption algorithm disabled"},
+    {0x74,0x10,"SA creation parameter value invalid"},
+    {0x74,0x11,"SA creation parameter value rejected"},
+    {0x74,0x12,"Invalid SA usage"},
+    {0x74,0x21,"Data encryption configuration prevented"},
+    {0x74,0x30,"SA creation parameter not supported"},
+    {0x74,0x40,"Authentication failed"},
+    {0x74,0x61,"External data encryption key manager access error"},
+    {0x74,0x62,"External data encryption key manager error"},
+    {0x74,0x63,"External data encryption key not found"},
+    {0x74,0x64,"External data encryption request not authorized"},
+    {0x74,0x6e,"External data encryption control timeout"},
+    {0x74,0x6f,"External data encryption control error"},
+    {0x74,0x71,"Logical unit access not authorized"},
+    {0x74,0x79,"Security conflict in translated device"},
+    {0, 0, NULL}
+};
+
+#else   /* SG_SCSI_STRINGS */
+
+struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] =
+{
+    {0, 0, 0, NULL}
+};
+
+struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] =
+{
+    {0, 0, NULL}
+};
+#endif /* SG_SCSI_STRINGS */
+
+const char * sg_lib_sense_key_desc[] = {
+    "No Sense",                 /* Filemark, ILI and/or EOM; progress
+                                   indication (during FORMAT); power
+                                   condition sensing (REQUEST SENSE) */
+    "Recovered Error",          /* The last command completed successfully
+                                   but used error correction */
+    "Not Ready",                /* The addressed target is not ready */
+    "Medium Error",             /* Data error detected on the medium */
+    "Hardware Error",           /* Controller or device failure */
+    "Illegal Request",
+    "Unit Attention",           /* Removable medium was changed, or
+                                   the target has been reset */
+    "Data Protect",             /* Access to the data is blocked */
+    "Blank Check",              /* Reached unexpected written or unwritten
+                                   region of the medium */
+    "Vendor specific(9)",       /* Vendor specific */
+    "Copy Aborted",             /* COPY or COMPARE was aborted */
+    "Aborted Command",          /* The target aborted the command */
+    "Equal",                    /* SEARCH DATA found data equal (obsolete) */
+    "Volume Overflow",          /* Medium full with data to be written */
+    "Miscompare",               /* Source data and data on the medium
+                                   do not agree */
+    "Completed"                 /* may occur for successful cmd (spc4r23) */
+};
+
+const char * sg_lib_pdt_strs[32] = {    /* should have 2**5 elements */
+    /* 0 */ "disk",
+    "tape",
+    "printer",                  /* obsolete, spc5r01 */
+    "processor",        /* often SAF-TE device, copy manager */
+    "write once optical disk",  /* obsolete, spc5r01 */
+    /* 5 */ "cd/dvd",
+    "scanner",                  /* obsolete */
+    "optical memory device",
+    "medium changer",
+    "communications",           /* obsolete */
+    /* 0xa */ "graphics [0xa]", /* obsolete */
+    "graphics [0xb]",           /* obsolete */
+    "storage array controller",
+    "enclosure services device",
+    "simplified direct access device",
+    "optical card reader/writer device",
+    /* 0x10 */ "bridge controller commands",
+    "object based storage",
+    "automation/driver interface",
+    "security manager device",  /* obsolete, spc5r01 */
+    "zoned block commands",
+    "0x15", "0x16", "0x17", "0x18",
+    "0x19", "0x1a", "0x1b", "0x1c", "0x1d",
+    "well known logical unit",
+    "no physical device on this lu",
+};
+
+const char * sg_lib_transport_proto_strs[] =
+{
+    "Fibre Channel Protocol for SCSI (FCP-4)",
+    "SCSI Parallel Interface (SPI-5)",  /* obsolete in spc5r01 */
+    "Serial Storage Architecture SCSI-3 Protocol (SSA-S3P)",
+    "Serial Bus Protocol for IEEE 1394 (SBP-3)",
+    "SCSI RDMA Protocol (SRP)",
+    "Internet SCSI (iSCSI)",
+    "Serial Attached SCSI Protocol (SPL-4)",
+    "Automation/Drive Interface Transport (ADT-2)",
+    "AT Attachment Interface (ACS-2)",          /* 0x8 */
+    "USB Attached SCSI (UAS-2)",
+    "SCSI over PCI Express (SOP)",
+    "PCIe",                             /* added in spc5r02 */
+    "Oxc", "Oxd", "Oxe",
+    "No specific protocol"
+};
+
+/* SCSI Feature Sets array. code->value, pdt->peri_dev_type (-1 for SPC) */
+struct sg_lib_value_name_t sg_lib_scsi_feature_sets[] =
+{
+    {SCSI_FS_SPC_DISCOVERY_2016, -1, "Discovery 2016"},
+    {SCSI_FS_SBC_BASE_2010, PDT_DISK, "SBC Base 2010"},
+    {SCSI_FS_SBC_BASE_2016, PDT_DISK, "SBC Base 2016"},
+    {SCSI_FS_SBC_BASIC_PROV_2016, PDT_DISK, "Basic provisioning 2016"},
+    {SCSI_FS_SBC_DRIVE_MAINT_2016, PDT_DISK, "Drive maintenance 2016"},
+    {0x0, 0, NULL},     /* 0x0 is reserved sfs; trailing sentinel */
+};
+
+#if (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME))
+
+/* .value is completion queue's DW3 as follows: ((DW3 >> 17) & 0x3ff)
+ * .peri_dev_type is an index for the sg_lib_scsi_status_sense_arr[]
+ * .name is taken from NVMe 1.3a document, section 4.6.1.2.1 with less
+ *   capitalization.
+ * NVMe term bits 31:17 of DW3 in the completion field as the "Status
+ * Field" (SF). Bit 31 is "Do not retry" (DNR) and bit 30 is "More" (M).
+ * Bits 29:28 are reserved, bit 27:25 are the "Status Code Type" (SCT)
+ * and bits 24:17 are the Status Code (SC). This table is in ascending
+ * order of its .value field so a binary search could be done on it.  */
+struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] =
+{
+    /* Generic command status values, Status Code Type (SCT): 0h
+     * Lowest 8 bits are the Status Code (SC), in this case:
+     *   00h - 7Fh: Applicable to Admin Command Set, or across multiple
+     *              command sets
+     *   80h - BFh: I/O Command Set Specific status codes
+     *   c0h - FFh: I/O Vendor Specific status codes            */
+    {0x0,   0, "Successful completion"},
+    {0x1,   1, "Invalid command opcode"},
+    {0x2,   2, "Invalid field in command"},
+    {0x3,   2, "Command id conflict"},
+    {0x4,   3, "Data transfer error"},
+    {0x5,   4, "Command aborted due to power loss notication"},
+    {0x6,   5, "Internal error"},
+    {0x7,   6, "Command abort requested"},
+    {0x8,   6, "Command aborted due to SQ deletion"},
+    {0x9,   6, "Command aborted due to failed fused command"},
+    {0xa,   6, "Command aborted due to missing fused command"},
+    {0xb,   7, "Invalid namespace or format"},
+    {0xc,   5, "Command sequence error"},
+    {0xd,   5, "Invalid SGL segment descriptor"},
+    {0xe,   5, "Invalid number of SGL descriptors"},
+    {0xf,   5, "Data SGL length invalid"},
+    {0x10,  5, "Matadata SGL length invalid"},
+    {0x11,  5, "SGL descriptor type invalid"},
+    {0x12,  5, "Invalid use of controller memory buffer"},
+    {0x13,  5, "PRP offset invalid"},
+    {0x14,  2, "Atomic write unit exceeded"},
+    {0x15,  8, "Operation denied"},
+    {0x16,  5, "SGL offset invalid"},
+    {0x17,  5, "Reserved [0x17]"},
+    {0x18,  5, "Host identifier inconsistent format"},
+    {0x19,  5, "Keep alive timeout expired"},
+    {0x1a,  5, "Keep alive timeout invalid"},
+    {0x1b,  6, "Command aborted due to Preempt and Abort"},
+    {0x1c, 10, "Sanitize failed"},
+    {0x1d, 11, "Sanitize in progress"},
+    {0x1e,  5, "SGL data block granularity invalid"},
+    {0x1f,  5, "Command not supported for queue in CMB"},
+
+    /* Generic command status values, NVM (I/O) Command Set */
+    {0x80, 12, "LBA out of range"},
+    {0x81,  3, "Capacity exceeded"},
+    {0x82, 13, "Namespace not ready"},
+    {0x83, 14, "Reservation conflict"},
+    {0x84, 15, "Format in progress"},
+    /* 0xc0 - 0xff: vendor specific */
+
+    /* Command specific status values, Status Code Type (SCT): 1h */
+    {0x100, 5, "Completion queue invalid"},
+    {0x101, 5, "Invalid queue identifier"},
+    {0x102, 5, "Invalid queue size"},
+    {0x103, 5, "Abort command limit exceeded"},
+    {0x104, 5, "Reserved [0x104]"},
+    {0x105, 5, "Asynchronous event request limit exceeded"},
+    {0x106, 5, "Invalid firmware slot"},
+    {0x107, 5, "Invalid firmware image"},
+    {0x108, 5, "Invalid interrupt vector"},
+    {0x109, 5, "Invalid log page"},
+    {0x10a,16, "Invalid format"},
+    {0x10b, 5, "Firmware activation requires conventional reset"},
+    {0x10c, 5, "Invalid queue deletion"},
+    {0x10d, 5, "Feature identifier not saveable"},
+    {0x10e, 5, "Feature not changeable"},
+    {0x10f, 5, "Feature not namespace specific"},
+    {0x110, 5, "Firmware activation requires NVM subsystem reset"},
+    {0x111, 5, "Firmware activation requires reset"},
+    {0x112, 5, "Firmware activation requires maximum time violation"},
+    {0x113, 5, "Firmware activation prohibited"},
+    {0x114, 5, "Overlapping range"},
+    {0x115, 5, "Namespace insufficient capacity"},
+    {0x116, 5, "Namespace identifier unavailable"},
+    {0x117, 5, "Reserved [0x107]"},
+    {0x118, 5, "Namespace already attached"},
+    {0x119, 5, "Namespace is private"},
+    {0x11a, 5, "Namespace not attached"},
+    {0x11b, 3, "Thin provisioning not supported"},
+    {0x11c, 3, "Controller list invalid"},
+    {0x11d,17, "Device self-test in progress"},
+    {0x11e,18, "Boot partition write prohibited"},
+    {0x11f, 5, "Invalid controller identifier"},
+    {0x120, 5, "Invalid secondary controller state"},
+    {0x121, 5, "Invalid number of controller resorces"},
+    {0x122, 5, "Invalid resorce identifier"},
+
+    /* Command specific status values, Status Code Type (SCT): 1h
+     * for NVM (I/O) Command Set */
+    {0x180, 2, "Conflicting attributes"},
+    {0x181,19, "Invalid protection information"},
+    {0x182,18, "Attempted write to read only range"},
+    /* 0x1c0 - 0x1ff: vendor specific */
+
+    /* Media and Data Integrity error values, Status Code Type (SCT): 2h */
+    {0x280,20, "Write fault"},
+    {0x281,21, "Unrecovered read error"},
+    {0x282,22, "End-to-end guard check error"},
+    {0x283,23, "End-to-end application tag check error"},
+    {0x284,24, "End-to-end reference tag check error"},
+    {0x285,25, "Compare failure"},
+    {0x286, 8, "Access denied"},
+    {0x287,26, "Deallocated or unwritten logical block"},
+    /* 0x2c0 - 0x2ff: vendor specific */
+
+    /* Leave this Sentinel value at end of this array */
+    {0x3ff, 0, NULL},
+};
+
+/* The sg_lib_nvme_cmd_status_arr[n].peri_dev_type field is an index
+ * to this array. It allows an NVMe status (error) value to be mapped
+ * to this SCSI tuple: status, sense_key, additional sense code (asc) and
+ * asc qualifier (ascq). For brevity SAM_STAT_CHECK_CONDITION is written
+ * as 0x2. */
+struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] =
+{
+    {SAM_STAT_GOOD, SPC_SK_NO_SENSE, 0, 0},     /* it's all good */ /* 0 */
+    {SAM_STAT_CHECK_CONDITION, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x0},/* opcode */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x24, 0x0},   /* field in cdb */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x0, 0x0},
+    {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0xb, 0x8},
+    {0x2, SPC_SK_HARDWARE_ERROR, 0x44, 0x0},   /* internal error */ /* 5 */
+    {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0x0, 0x0},
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x9},   /* invalid LU */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x2},   /* access denied */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x2c, 0x0},   /* cmd sequence error */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x31, 0x3},   /* sanitize failed */ /* 10 */
+    {0x2, SPC_SK_NOT_READY, 0x4, 0x1b}, /* sanitize in progress */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x21, 0x0},   /* LBA out of range */
+    {0x2, SPC_SK_NOT_READY, 0x4, 0x0},  /* not reportable; 0x1: becoming */
+    {SAM_STAT_RESERVATION_CONFLICT, 0x0, 0x0, 0x0},
+    {0x2, SPC_SK_NOT_READY, 0x4, 0x4},  /* format in progress */  /* 15 */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x31, 0x1},  /* format failed */
+    {0x2, SPC_SK_NOT_READY, 0x4, 0x9},  /* self-test in progress */
+    {0x2, SPC_SK_DATA_PROTECT, 0x27, 0x0},      /* write prohibited */
+    {0x2, SPC_SK_ILLEGAL_REQUEST, 0x10, 0x5},  /* protection info */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x3, 0x0}, /* periph dev w fault */ /* 20 */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x11, 0x0},      /* unrecoc rd */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x1},      /* PI guard */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2},      /* PI app tag */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2},      /* PI app tag */
+    {0x2, SPC_SK_MISCOMPARE, 0x1d, 0x0},        /* during verify */ /* 25 */
+    {0x2, SPC_SK_MEDIUM_ERROR, 0x21, 0x6},      /* read invalid data */
+
+    /* Leave this Sentinel value at end of this array */
+    {0xff, 0xff, 0xff, 0xff},
+};
+
+
+#else           /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */
+struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] =
+{
+
+    /* Leave this Sentinel value at end of this array */
+    {0x3ff, 0, NULL},
+};
+
+struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] =
+{
+
+    /* Leave this Sentinel value at end of this array */
+    {0xff, 0xff, 0xff, 0xff},
+};
+
+#endif           /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */
diff --git a/tools/sg_write_buffer/sg_pt_common.c b/tools/sg_write_buffer/sg_pt_common.c
new file mode 100644
index 0000000..85bc191
--- /dev/null
+++ b/tools/sg_write_buffer/sg_pt_common.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2009-2018 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_pt_nvme.h"
+
+
+static const char * scsi_pt_version_str = "3.03 20180115";
+
+static const char * nvme_scsi_vendor_str = "NVMe    ";
+
+
+const char *
+scsi_pt_version()
+{
+    return scsi_pt_version_str;
+}
+
+/* Given the NVMe Identify controller response and optionally the NVMe
+ * Identify namespace response (NULL otherwise), generate the SCSI VPD
+ * page 0x83 (device identification) descriptor(s) in dop. Return the
+ * number of bytes written which will not exceed max_do_len. Probably use
+ * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport
+ * protocol (tproto) should be -1 if not known, else SCSI value.
+ * N.B. Does not write total VPD page length into dop[2:3] . */
+int
+sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p,
+                           const uint8_t * nvme_id_ns_p, int pdt,
+                           int tproto, uint8_t * dop, int max_do_len)
+{
+    bool have_nguid, have_eui64;
+    int k, n;
+    char b[4];
+
+    if ((NULL == nvme_id_ctl_p) || (NULL == dop) || (max_do_len < 56))
+        return 0;
+
+    memset(dop, 0, max_do_len);
+    dop[0] = 0x1f & pdt;  /* (PQ=0)<<5 | (PDT=pdt); 0 or 0xd (SES) */
+    dop[1] = 0x83;      /* Device Identification VPD page number */
+    /* Build a T10 Vendor ID based designator (desig_id=1) for controller */
+    if (tproto >= 0) {
+        dop[4] = ((0xf & tproto) << 4) | 0x2;
+        dop[5] = 0xa1; /* PIV=1, ASSOC=2 (target device), desig_id=1 */
+    } else {
+        dop[4] = 0x2;  /* Prococol id=0, code_set=2 (ASCII) */
+        dop[5] = 0x21; /* PIV=0, ASSOC=2 (target device), desig_id=1 */
+    }
+    memcpy(dop + 8, nvme_scsi_vendor_str, 8); /* N.B. this is "NVMe    " */
+    memcpy(dop + 16, nvme_id_ctl_p + 24, 40);  /* MN */
+    for (k = 40; k > 0; --k) {
+        if (' ' == dop[15 + k])
+            dop[15 + k] = '_'; /* convert trailing spaces */
+        else
+            break;
+    }
+    if (40 == k)
+        --k;
+    n = 16 + 1 + k;
+    if (max_do_len < (n + 20))
+        return 0;
+    memcpy(dop + n, nvme_id_ctl_p + 4, 20); /* SN */
+    for (k = 20; k > 0; --k) {  /* trim trailing spaces */
+        if (' ' == dop[n + k - 1])
+            dop[n + k - 1] = '\0';
+        else
+            break;
+    }
+    n += k;
+    if (0 != (n % 4))
+        n = ((n / 4) + 1) * 4;  /* round up to next modulo 4 */
+    dop[7] = n - 8;
+    if (NULL == nvme_id_ns_p)
+        return n;
+
+    /* Look for NGUID (16 byte identifier) or EUI64 (8 byte) fields in
+     * NVME Identify for namespace. If found form a EUI and a SCSI string
+     * descriptor for non-zero NGUID or EUI64 (prefer NGUID if both). */
+    have_nguid = ! sg_all_zeros(nvme_id_ns_p + 104, 16);
+    have_eui64 = ! sg_all_zeros(nvme_id_ns_p + 120, 8);
+    if ((! have_nguid) && (! have_eui64))
+        return n;
+    if (have_nguid) {
+        if (max_do_len < (n + 20))
+            return n;
+        dop[n + 0] = 0x1;  /* Prococol id=0, code_set=1 (binary) */
+        dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */
+        dop[n + 3] = 16;
+        memcpy(dop + n + 4, nvme_id_ns_p + 104, 16);
+        n += 20;
+        if (max_do_len < (n + 40))
+            return n;
+        dop[n + 0] = 0x3;  /* Prococol id=0, code_set=3 (utf8) */
+        dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */
+        dop[n + 3] = 36;
+        memcpy(dop + n + 4, "eui.", 4);
+        for (k = 0; k < 16; ++k) {
+            snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[104 + k]);
+            memcpy(dop + n + 8 + (2 * k), b, 2);
+        }
+        return n + 40;
+    } else {    /* have_eui64 is true, 8 byte identifier */
+        if (max_do_len < (n + 12))
+            return n;
+        dop[n + 0] = 0x1;  /* Prococol id=0, code_set=1 (binary) */
+        dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */
+        dop[n + 3] = 8;
+        memcpy(dop + n + 4, nvme_id_ns_p + 120, 8);
+        n += 12;
+        if (max_do_len < (n + 24))
+            return n;
+        dop[n + 0] = 0x3;  /* Prococol id=0, code_set=3 (utf8) */
+        dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */
+        dop[n + 3] = 20;
+        memcpy(dop + n + 4, "eui.", 4);
+        for (k = 0; k < 8; ++k) {
+            snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[120 + k]);
+            memcpy(dop + n + 8 + (2 * k), b, 2);
+        }
+        return n + 24;
+    }
+}
diff --git a/tools/sg_write_buffer/sg_pt_linux.c b/tools/sg_write_buffer/sg_pt_linux.c
new file mode 100644
index 0000000..22ea068
--- /dev/null
+++ b/tools/sg_write_buffer/sg_pt_linux.c
@@ -0,0 +1,964 @@
+/*
+ * Copyright (c) 2005-2018 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 version 1.37 20180126 */
+
+
+#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"
+
+
+#ifdef major
+#define SG_DEV_MAJOR major
+#else
+#ifdef HAVE_LINUX_KDEV_T_H
+#include <linux/kdev_t.h>
+#endif
+#define SG_DEV_MAJOR MAJOR  /* MAJOR() macro faulty if > 255 minors */
+#endif
+
+#ifndef BLOCK_EXT_MAJOR
+#define BLOCK_EXT_MAJOR 259
+#endif
+
+#define DEF_TIMEOUT 60000       /* 60,000 millisecs (60 seconds) */
+
+static const char * linux_host_bytes[] = {
+    "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT",
+    "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR",
+    "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR",
+    "DID_IMM_RETRY", "DID_REQUEUE" /* 0xd */,
+    "DID_TRANSPORT_DISRUPTED", "DID_TRANSPORT_FAILFAST",
+    "DID_TARGET_FAILURE" /* 0x10 */,
+    "DID_NEXUS_FAILURE (reservation conflict)",
+    "DID_ALLOC_FAILURE",
+    "DID_MEDIUM_ERROR",
+};
+
+#define LINUX_HOST_BYTES_SZ \
+        (int)(sizeof(linux_host_bytes) / sizeof(linux_host_bytes[0]))
+
+static const char * linux_driver_bytes[] = {
+    "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA",
+    "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD",
+    "DRIVER_SENSE"
+};
+
+#define LINUX_DRIVER_BYTES_SZ \
+    (int)(sizeof(linux_driver_bytes) / sizeof(linux_driver_bytes[0]))
+
+#if 0
+static const char * linux_driver_suggests[] = {
+    "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP",
+    "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN",
+    "SUGGEST_SENSE"
+};
+
+#define LINUX_DRIVER_SUGGESTS_SZ \
+    (int)(sizeof(linux_driver_suggests) / sizeof(linux_driver_suggests[0]))
+#endif
+
+/*
+ * These defines are for constants that should be visible in the
+ * /usr/include/scsi directory (brought in by sg_linux_inc.h).
+ * Redefined and aliased here to decouple this code from
+ * sg_io_linux.h  N.B. the SUGGEST_* constants are no longer used.
+ */
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0
+#endif
+#ifndef DRIVER_SENSE
+#define DRIVER_SENSE 0x08
+#endif
+#define SG_LIB_DRIVER_MASK      DRIVER_MASK
+#define SG_LIB_SUGGEST_MASK     SUGGEST_MASK
+#define SG_LIB_DRIVER_SENSE    DRIVER_SENSE
+
+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__)
+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;
+}
+
+/* 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). */
+void
+sg_find_bsg_nvme_char_major(int verbose)
+{
+    bool got_one = false;
+    int n;
+    const char * proc_devices = "/proc/devices";
+    char * cp;
+    FILE *fp;
+    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));
+        return;
+    }
+    while ((cp = fgets(b, sizeof(b), fp))) {
+        if ((1 == sscanf(b, "%126s", a)) &&
+            (0 == memcmp(a, "Character", 9)))
+            break;
+    }
+    while (cp && (cp = fgets(b, sizeof(b), fp))) {
+        if (2 == sscanf(b, "%d %126s", &n, a)) {
+            if (0 == strcmp("bsg", a)) {
+                sg_bsg_major = n;
+                if (got_one)
+                    break;
+                got_one = true;
+            } else if (0 == strcmp("nvme", a)) {
+                sg_nvme_char_major = n;
+                if (got_one)
+                    break;
+                got_one = true;
+            }
+        } else
+            break;
+    }
+    if (verbose > 3) {
+        if (cp) {
+            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 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. */
+static bool
+check_file_type(int dev_fd, struct stat * dev_statp, bool * is_bsg_p,
+                bool * is_nvme_p, uint32_t * nsid_p, int * os_err_p,
+                int verbose)
+{
+    bool is_nvme = false;
+    bool is_sg = false;
+    bool is_bsg = false;
+    bool is_block = false;
+    int os_err = 0;
+    int major_num;
+    uint32_t nsid = 0;          /* invalid NSID */
+
+    if (dev_fd >= 0) {
+        if (fstat(dev_fd, dev_statp) < 0) {
+            os_err = errno;
+            if (verbose)
+                pr2ws("%s: fstat() failed: %s (errno=%d)\n", __func__,
+                      safe_strerror(os_err), os_err);
+            goto skip_out;
+        }
+        major_num = (int)SG_DEV_MAJOR(dev_statp->st_rdev);
+        if (S_ISCHR(dev_statp->st_mode)) {
+            if (SCSI_GENERIC_MAJOR == major_num)
+                is_sg = true;
+            else if (sg_bsg_major == major_num)
+                is_bsg = true;
+            else if (sg_nvme_char_major == major_num)
+                is_nvme = true;
+        } else if (S_ISBLK(dev_statp->st_mode)) {
+            is_block = true;
+            if (BLOCK_EXT_MAJOR == major_num) {
+                is_nvme = true;
+                nsid = ioctl(dev_fd, NVME_IOCTL_ID, NULL);
+                if (SG_NVME_BROADCAST_NSID == nsid) {  /* means ioctl error */
+                    os_err = errno;
+                    if (verbose)
+                        pr2ws("%s: ioctl(NVME_IOCTL_ID) failed: %s "
+                              "(errno=%d)\n", __func__, safe_strerror(os_err),
+                              os_err);
+                } else
+                    os_err = 0;
+            }
+        }
+    } else {
+        os_err = EBADF;
+        if (verbose)
+            pr2ws("%s: invalid file descriptor (%d)\n", __func__, dev_fd);
+    }
+skip_out:
+    if (verbose > 3) {
+        pr2ws("%s: file descriptor is ", __func__);
+        if (is_sg)
+            pr2ws("sg device\n");
+        else if (is_bsg)
+            pr2ws("bsg device\n");
+        else if (is_nvme && (0 == nsid))
+            pr2ws("NVMe char device\n");
+        else if (is_nvme)
+            pr2ws("NVMe block device, nsid=%lld\n",
+                  ((uint32_t)-1 == nsid) ? -1LL : (long long)nsid);
+        else if (is_block)
+            pr2ws("block device\n");
+        else
+            pr2ws("undetermined device, could be regular file\n");
+    }
+    if (is_bsg_p)
+        *is_bsg_p = is_bsg;
+    if (is_nvme_p)
+        *is_nvme_p = is_nvme;
+    if (nsid_p)
+        *nsid_p = nsid;
+    if (os_err_p)
+        *os_err_p = os_err;
+    return is_sg;
+}
+
+/* Assumes dev_fd is an "open" file handle associated with device_name. If
+ * the implementation (possibly for one OS) cannot determine from dev_fd if
+ * a SCSI or NVMe pass-through is referenced, then it might guess based on
+ * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if
+ * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is
+ * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0.
+ * If error, returns negated errno (operating system) value. */
+int
+check_pt_file_handle(int dev_fd, const char * device_name, int verbose)
+{
+    if (verbose > 4)
+        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 (! 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;
+        int err;
+        uint32_t nsid;
+        struct stat a_stat;
+
+        is_sg = check_file_type(dev_fd, &a_stat, &is_bsg, &is_nvme, &nsid,
+                                &err, verbose);
+        if (err)
+            return -err;
+        else if (is_sg)
+            return 1;
+        else if (is_bsg)
+            return 2;
+        else if (is_nvme && (0 == nsid))
+            return 3;
+        else if (is_nvme)
+            return 4;
+        else
+            return 0;
+    } else
+        return 0;
+}
+
+/*
+ * 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()
+ *      matches the char major number of the bsg entry in /proc/devices
+ * Otherwise the sg v3 interface is used.
+ *
+ * Note that in either case we prepare the data in a sg v4 structure. If
+ * the runtime tests indicate that the v3 interface is needed then
+ * do_scsi_pt_v3() transfers the input data into a v3 structure and
+ * then the output data is transferred back into a sg v4 structure.
+ * That implementation detail could change in the future.
+ *
+ * [20120806] Only use MAJOR() macro in kdev_t.h if that header file is
+ * available and major() macro [N.B. lower case] is not available.
+ */
+
+
+#ifdef major
+#define SG_DEV_MAJOR major
+#else
+#ifdef HAVE_LINUX_KDEV_T_H
+#include <linux/kdev_t.h>
+#endif
+#define SG_DEV_MAJOR MAJOR  /* MAJOR() macro faulty if > 255 minors */
+#endif
+
+
+/* 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 (! 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);
+    }
+    fd = open(device_name, flags);
+    if (fd < 0) {
+        fd = -errno;
+        if (verbose > 1)
+            pr2ws("%s: open(%s, 0x%x) failed: %s\n", __func__, device_name,
+                  flags, safe_strerror(-fd));
+    }
+    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.guard = 'Q';
+#ifdef BSG_PROTOCOL_SCSI
+            ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI;
+#endif
+#ifdef BSG_SUB_PROTOCOL_SCSI_CMD
+            ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+#endif
+        }
+    } 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_nvme_id_ctlp) {
+        free(ptp->free_nvme_id_ctlp);
+        ptp->free_nvme_id_ctlp = NULL;
+        ptp->nvme_id_ctlp = NULL;
+    }
+    if (ptp)
+        free(ptp);
+}
+
+/* Remembers previous device file descriptor */
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+    bool is_sg, is_bsg, is_nvme;
+    int fd;
+    uint32_t nvme_nsid;
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    if (ptp) {
+        fd = ptp->dev_fd;
+        is_sg = ptp->is_sg;
+        is_bsg = ptp->is_bsg;
+        is_nvme = ptp->is_nvme;
+        nvme_nsid = ptp->nvme_nsid;
+        memset(ptp, 0, sizeof(struct sg_pt_linux_scsi));
+        ptp->io_hdr.guard = 'Q';
+#ifdef BSG_PROTOCOL_SCSI
+        ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI;
+#endif
+#ifdef BSG_SUB_PROTOCOL_SCSI_CMD
+        ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+#endif
+        ptp->dev_fd = fd;
+        ptp->is_sg = is_sg;
+        ptp->is_bsg = is_bsg;
+        ptp->is_nvme = is_nvme;
+        ptp->nvme_direct = false;
+        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 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 (! 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)
+        ptp->is_sg = check_file_type(dev_fd, &a_stat, &ptp->is_bsg,
+                                     &ptp->is_nvme, &ptp->nvme_nsid,
+                                     &ptp->os_err, verbose);
+    else {
+        ptp->is_sg = false;
+        ptp->is_bsg = false;
+        ptp->is_nvme = false;
+        ptp->nvme_direct = 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.request)
+        ++ptp->in_err;
+    ptp->io_hdr.request = (__u64)(sg_uintptr_t)cdb;
+    ptp->io_hdr.request_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.response)
+        ++ptp->in_err;
+    memset(sense, 0, max_sense_len);
+    ptp->io_hdr.response = (__u64)(sg_uintptr_t)sense;
+    ptp->io_hdr.max_response_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->io_hdr.din_xferp)
+        ++ptp->in_err;
+    if (dxfer_ilen > 0) {
+        ptp->io_hdr.din_xferp = (__u64)(sg_uintptr_t)dxferp;
+        ptp->io_hdr.din_xfer_len = dxfer_ilen;
+    }
+}
+
+/* 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->io_hdr.dout_xferp)
+        ++ptp->in_err;
+    if (dxfer_olen > 0) {
+        ptp->io_hdr.dout_xferp = (__u64)(sg_uintptr_t)dxferp;
+        ptp->io_hdr.dout_xfer_len = dxfer_olen;
+    }
+}
+
+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 (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.spare_in = pack_id;
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag)
+{
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    ptp->io_hdr.request_tag = tag;
+}
+
+/* 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->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_uintptr_t)(&(ptp->tmf_request[0]));
+    ptp->io_hdr.request_len = 1;
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority)
+{
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    ptp->io_hdr.request_attr = attribute;
+    ptp->io_hdr.request_priority = priority;
+}
+
+#ifndef BSG_FLAG_Q_AT_TAIL
+#define BSG_FLAG_Q_AT_TAIL 0x10
+#endif
+#ifndef BSG_FLAG_Q_AT_HEAD
+#define BSG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+/* Need this later if translated to v3 interface */
+#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 bsg driver (sg v4) 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 |= BSG_FLAG_Q_AT_HEAD;
+        ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_TAIL;
+    } else if (SCSI_PT_FLAGS_QUEUE_AT_TAIL & flags) {
+        ptp->io_hdr.flags |= BSG_FLAG_Q_AT_TAIL;
+        ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_HEAD;
+    }
+}
+
+/* N.B. Returns din_resid and ignores dout_resid */
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    if (NULL == ptp)
+        return 0;
+    return ptp->nvme_direct ? 0 : ptp->io_hdr.din_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 (int)(ptp->nvme_direct ? ptp->nvme_status :
+                                    ptp->io_hdr.device_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->nvme_direct ? ptp->nvme_result :
+                              ptp->io_hdr.device_status;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    return ptp->io_hdr.response_len;
+}
+
+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.transport_status;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    ptp->io_hdr.transport_status = err;
+}
+
+/* Returns b which will contain a null char terminated string (if
+ * max_b_len > 0). Combined driver and transport (called "host" in Linux
+ * kernel) statuses */
+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.transport_status;
+    int n, m;
+    char * cp = b;
+    int driv;
+    const char * driv_cp = "invalid";
+
+    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 invalid\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;
+}
+
+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.device_status & 0x7e;
+
+    if (ptp->os_err)
+        return SCSI_PT_RESULT_OS_ERR;
+    else if (ptp->io_hdr.transport_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_os_err(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    return ptp->os_err;
+}
+
+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;
+}
+
+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;
+}
+
+/* Executes SCSI command using sg v3 interface */
+static int
+do_scsi_pt_v3(struct sg_pt_linux_scsi * ptp, int fd, int time_secs,
+              int verbose)
+{
+    struct sg_io_hdr v3_hdr;
+
+    memset(&v3_hdr, 0, sizeof(v3_hdr));
+    /* convert v4 to v3 header */
+    v3_hdr.interface_id = 'S';
+    v3_hdr.dxfer_direction = SG_DXFER_NONE;
+    v3_hdr.cmdp = (unsigned char *)(long)ptp->io_hdr.request;
+    v3_hdr.cmd_len = (unsigned char)ptp->io_hdr.request_len;
+    if (ptp->io_hdr.din_xfer_len > 0) {
+        if (ptp->io_hdr.dout_xfer_len > 0) {
+            if (verbose)
+                pr2ws("sgv3 doesn't support bidi\n");
+            return SCSI_PT_DO_BAD_PARAMS;
+        }
+        v3_hdr.dxferp = (void *)(long)ptp->io_hdr.din_xferp;
+        v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.din_xfer_len;
+        v3_hdr.dxfer_direction =  SG_DXFER_FROM_DEV;
+    } else if (ptp->io_hdr.dout_xfer_len > 0) {
+        v3_hdr.dxferp = (void *)(long)ptp->io_hdr.dout_xferp;
+        v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.dout_xfer_len;
+        v3_hdr.dxfer_direction =  SG_DXFER_TO_DEV;
+    }
+    if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) {
+        v3_hdr.sbp = (unsigned char *)(long)ptp->io_hdr.response;
+        v3_hdr.mx_sb_len = (unsigned char)ptp->io_hdr.max_response_len;
+    }
+    v3_hdr.pack_id = (int)ptp->io_hdr.spare_in;
+    if (BSG_FLAG_Q_AT_HEAD & ptp->io_hdr.flags)
+        v3_hdr.flags |= SG_FLAG_Q_AT_HEAD;      /* favour AT_HEAD */
+    else if (BSG_FLAG_Q_AT_TAIL & ptp->io_hdr.flags)
+        v3_hdr.flags |= SG_FLAG_Q_AT_TAIL;
+
+    if (NULL == v3_hdr.cmdp) {
+        if (verbose)
+            pr2ws("No SCSI command (cdb) given\n");
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    /* io_hdr.timeout is in milliseconds, if greater than zero */
+    v3_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT);
+    /* Finally do the v3 SG_IO ioctl */
+    if (ioctl(fd, SG_IO, &v3_hdr) < 0) {
+        ptp->os_err = errno;
+        if (verbose > 1)
+            pr2ws("ioctl(SG_IO v3) failed: %s (errno=%d)\n",
+                  safe_strerror(ptp->os_err), ptp->os_err);
+        return -ptp->os_err;
+    }
+    ptp->io_hdr.device_status = (__u32)v3_hdr.status;
+    ptp->io_hdr.driver_status = (__u32)v3_hdr.driver_status;
+    ptp->io_hdr.transport_status = (__u32)v3_hdr.host_status;
+    ptp->io_hdr.response_len = (__u32)v3_hdr.sb_len_wr;
+    ptp->io_hdr.duration = (__u32)v3_hdr.duration;
+    ptp->io_hdr.din_resid = (__s32)v3_hdr.resid;
+    /* v3_hdr.info not passed back since no mapping defined (yet) */
+    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);
+
+    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)
+            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;
+    } else
+        fd = ptp->dev_fd;
+    if (! have_checked_for_type) {
+        err = set_pt_file_handle(vp, ptp->dev_fd, verbose);
+        if (err)
+            return -ptp->os_err;
+    }
+    if (ptp->os_err)
+        return -ptp->os_err;
+    if (ptp->is_nvme)
+        return sg_do_nvme_pt(vp, -1, 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 */
+    else
+        return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+
+    if (! ptp->io_hdr.request) {
+        if (verbose)
+            pr2ws("No SCSI command (cdb) given (v4)\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 0
+    /* sense buffer already zeroed */
+    if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) {
+        void * p;
+
+        p = (void *)(long)ptp->io_hdr.response;
+        memset(p, 0, ptp->io_hdr.max_response_len);
+    }
+#endif
+    if (ioctl(fd, SG_IO, &ptp->io_hdr) < 0) {
+        ptp->os_err = errno;
+        if (verbose > 1)
+            pr2ws("ioctl(SG_IO v4) failed: %s (errno=%d)\n",
+                  safe_strerror(ptp->os_err), ptp->os_err);
+        return -ptp->os_err;
+    }
+    return 0;
+}
diff --git a/tools/sg_write_buffer/sg_pt_linux_nvme.c b/tools/sg_write_buffer/sg_pt_linux_nvme.c
new file mode 100644
index 0000000..5b08f6d
--- /dev/null
+++ b/tools/sg_write_buffer/sg_pt_linux_nvme.c
@@ -0,0 +1,1185 @@
+/*
+ * Copyright (c) 2017-2018 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.
+ *
+ * The code to use the NVMe Management Interface (MI) SES pass-through
+ * was provided by WDC in November 2017.
+ */
+
+/*
+ * Copyright 2017, Western Digital Corporation
+ *
+ * Written by Berck Nash
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * Based on the NVM-Express command line utility, which bore the following
+ * notice:
+ *
+ * Copyright (c) 2014-2015, Intel Corporation.
+ *
+ * Written by Keith Busch <keith.busch@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *                   MA 02110-1301, USA.
+ */
+
+/* sg_pt_linux_nvme version 1.04 20180115 */
+
+
+#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>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.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_REPORT_LUNS_OPC 0xa0
+#define SCSI_TEST_UNIT_READY_OPC  0x0
+#define SCSI_REQUEST_SENSE_OPC  0x3
+#define SCSI_SEND_DIAGNOSTIC_OPC  0x1d
+#define SCSI_RECEIVE_DIAGNOSTIC_OPC  0x1c
+#define SCSI_MAINT_IN_OPC  0xa3
+#define SCSI_REP_SUP_OPCS_OPC  0xc
+#define SCSI_REP_SUP_TMFS_OPC  0xd
+
+/* 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 LOW_POWER_COND_ON_ASC  0x5e     /* ASCQ=0 */
+#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
+
+
+static inline bool is_aligned(const void * 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;
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5)
+ * to the name of its associated char device (e.g. /dev/nvme0). If this
+ * occurs true is returned and the char device name is placed in 'b' (as
+ * long as b_len is sufficient). Otherwise false is returned. */
+bool
+sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len,
+                         char * b)
+{
+    uint32_t n, tlen;
+    const char * cp;
+    char buff[8];
+
+    if ((NULL == b) || (b_len < 5))
+        return false;   /* degenerate cases */
+    cp = strstr(nvme_block_devname, "nvme");
+    if (NULL == cp)
+        return false;   /* expected to find "nvme" in given name */
+    if (1 != sscanf(cp, "nvme%u", &n))
+        return false;   /* didn't find valid "nvme<number>" */
+    snprintf(buff, sizeof(buff), "%u", n);
+    tlen = (cp - nvme_block_devname) + 4 + strlen(buff);
+    if ((tlen + 1) > b_len)
+        return false;           /* b isn't long enough to fit output */
+    memcpy(b, nvme_block_devname, tlen);
+    b[tlen] = '\0';
+    return true;
+}
+
+static void
+build_sense_buffer(bool desc, uint8_t *buf, uint8_t skey, uint8_t asc,
+                   uint8_t ascq)
+{
+    if (desc) {
+        buf[0] = 0x72;  /* descriptor, current */
+        buf[1] = skey;
+        buf[2] = asc;
+        buf[3] = ascq;
+        buf[7] = 0;
+    } else {
+        buf[0] = 0x70;  /* fixed, current */
+        buf[2] = skey;
+        buf[7] = 0xa;   /* Assumes length is 18 bytes */
+        buf[12] = asc;
+        buf[13] = ascq;
+    }
+}
+
+/* Set in_bit to -1 to indicate no bit position of invalid field */
+static void
+mk_sense_asc_ascq(struct sg_pt_linux_scsi * ptp, int sk, int asc, int ascq,
+                  int vb)
+{
+    bool dsense = ptp->scsi_dsense;
+    int n;
+    uint8_t * sbp = (uint8_t *)ptp->io_hdr.response;
+
+    ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION;
+    n = ptp->io_hdr.max_response_len;
+    if ((n < 8) || ((! dsense) && (n < 14))) {
+        if (vb)
+            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, sk, asc, ascq);
+    if (vb > 3)
+        pr2ws("%s:  [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, sk,
+              asc, ascq);
+}
+
+static void
+mk_sense_from_nvme_status(struct sg_pt_linux_scsi * ptp, int vb)
+{
+    bool ok;
+    bool dsense = ptp->scsi_dsense;
+    int n;
+    uint8_t sstatus, sk, asc, ascq;
+    uint8_t * sbp = (uint8_t *)ptp->io_hdr.response;
+
+    ok = sg_nvme_status2scsi(ptp->nvme_status, &sstatus, &sk, &asc, &ascq);
+    if (! ok) { /* can't find a mapping to a SCSI error, so ... */
+        sstatus = SAM_STAT_CHECK_CONDITION;
+        sk = SPC_SK_ILLEGAL_REQUEST;
+        asc = 0xb;
+        ascq = 0x0;     /* asc: "WARNING" purposely vague */
+    }
+
+    ptp->io_hdr.device_status = sstatus;
+    n = ptp->io_hdr.max_response_len;
+    if ((n < 8) || ((! dsense) && (n < 14))) {
+        pr2ws("%s: sense_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, sk, asc, ascq);
+    if (vb > 3)
+        pr2ws("%s: [status, sense_key,asc,ascq]: [0x%x, 0x%x,0x%x,0x%x]\n",
+              __func__, sstatus, sk, asc, 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))) {
+        if (vb)
+            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 > 3)
+        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);
+}
+
+/* Returns 0 for success. Returns SG_LIB_NVME_STATUS if there is non-zero
+ * NVMe status (from the completion queue) with the value placed in
+ * ptp->nvme_status. If Unix error from ioctl then return negated value
+ * (equivalent -errno from basic Unix system functions like open()).
+ * CDW0 from the completion queue is placed in ptp->nvme_result in the
+ * absence of a Unix error. If time_secs is negative it is treated as
+ * a timeout in milliseconds (of abs(time_secs) ). */
+static int
+do_nvme_admin_cmd(struct sg_pt_linux_scsi * ptp,
+                  struct sg_nvme_passthru_cmd *cmdp, void * dp, bool is_read,
+                  int time_secs, int vb)
+{
+    const uint32_t cmd_len = sizeof(struct sg_nvme_passthru_cmd);
+    int res;
+    uint32_t n;
+    uint16_t sct_sc;
+    const uint8_t * up = ((const uint8_t *)cmdp) + SG_NVME_PT_OPCODE;
+
+    cmdp->timeout_ms = (time_secs < 0) ? (-time_secs) : (1000 * time_secs);
+    ptp->os_err = 0;
+    if (vb > 2) {
+        pr2ws("NVMe command:\n");
+        hex2stderr((const uint8_t *)cmdp, cmd_len, 1);
+        if ((vb > 3) && (! is_read) && dp) {
+            uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN);
+
+            if (len > 0) {
+                n = len;
+                if ((len < 512) || (vb > 5))
+                    pr2ws("\nData-out buffer (%u bytes):\n", n);
+                else {
+                    pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n);
+                    n = 512;
+                }
+                hex2stderr((const uint8_t *)dp, n, 0);
+            }
+        }
+    }
+    res = ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, cmdp);
+    if (res < 0) {  /* OS error (errno negated) */
+        ptp->os_err = -res;
+        if (vb > 1) {
+            pr2ws("%s: ioctl opcode=0x%x failed: %s "
+                  "(errno=%d)\n", __func__, *up, strerror(-res), -res);
+        }
+        return res;
+    }
+
+    /* Now res contains NVMe completion queue CDW3 31:17 (15 bits) */
+    ptp->nvme_result = cmdp->result;
+    if (ptp->nvme_direct && ptp->io_hdr.response &&
+        (ptp->io_hdr.max_response_len > 3)) {
+        /* build 16 byte "sense" buffer */
+        uint8_t * sbp = (uint8_t *)ptp->io_hdr.response;
+        uint16_t st = (uint16_t)res;
+
+        n = ptp->io_hdr.max_response_len;
+        n = (n < 16) ? n : 16;
+        memset(sbp, 0 , n);
+        ptp->io_hdr.response_len = n;
+        sg_put_unaligned_le32(cmdp->result,
+                              sbp + SG_NVME_PT_CQ_RESULT);
+        if (n > 15) /* LSBit will be 0 (Phase bit) after (st << 1) */
+            sg_put_unaligned_le16(st << 1, sbp + SG_NVME_PT_CQ_STATUS_P);
+    }
+    /* clear upper bits (DNR and More) leaving ((SCT << 8) | SC) */
+    sct_sc = 0x3ff & res;
+    ptp->nvme_status = sct_sc;
+    if (sct_sc) {  /* when non-zero, treat as command error */
+        if (vb > 1) {
+            char b[80];
+
+            pr2ws("%s: ioctl opcode=0x%x failed: NVMe status: %s [0x%x]\n",
+                   __func__, *up,
+                  sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b), sct_sc);
+        }
+        return SG_LIB_NVME_STATUS;      /* == SCSI_PT_DO_NVME_STATUS */
+    }
+    if ((vb > 3) && is_read && dp) {
+        uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN);
+
+        if (len > 0) {
+            n = len;
+            if ((len < 1024) || (vb > 5))
+                pr2ws("\nData-in buffer (%u bytes):\n", n);
+            else {
+                pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n);
+                n = 1024;
+            }
+            hex2stderr((const uint8_t *)dp, n, 0);
+        }
+    }
+    return 0;
+}
+
+/* Returns 0 on success; otherwise a positive value is returned */
+static int
+sntl_cache_identity(struct sg_pt_linux_scsi * ptp, int time_secs, int vb)
+{
+    struct sg_nvme_passthru_cmd cmd;
+    uint32_t pg_sz = sg_get_page_size();
+    uint8_t * up;
+
+    up = sg_memalign(pg_sz, pg_sz, &ptp->free_nvme_id_ctlp, vb > 3);
+    ptp->nvme_id_ctlp = up;
+    if (NULL == up) {
+        pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+        return -ENOMEM;
+    }
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0x6;   /* Identify */
+    cmd.cdw10 = 0x1;    /* CNS=0x1 Identify controller */
+    cmd.addr = (uint64_t)(sg_uintptr_t)ptp->nvme_id_ctlp;
+    cmd.data_len = pg_sz;
+    return do_nvme_admin_cmd(ptp, &cmd, up, true, time_secs, vb);
+}
+
+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 time_secs,
+         int vb)
+{
+    bool evpd;
+    bool cp_id_ctl = false;
+    int res;
+    uint16_t n, alloc_len, pg_cd;
+    uint32_t pg_sz = sg_get_page_size();
+    uint8_t * nvme_id_ns = NULL;
+    uint8_t * free_nvme_id_ns = NULL;
+    uint8_t inq_dout[256];
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+
+    if (0x2 & cdbp[1]) {        /* Reject CmdDt=1 */
+        mk_sense_invalid_fld(ptp, true, 1, 1, vb);
+        return 0;
+    }
+    if (NULL == ptp->nvme_id_ctlp) {
+        res = sntl_cache_identity(ptp, time_secs, vb);
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else if (res) /* should be negative errno */
+            return res;
+    }
+    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 */
+        switch (pg_cd) {
+        case 0:
+            /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+            inq_dout[1] = pg_cd;
+            n = 8;
+            sg_put_unaligned_be16(n - 4, inq_dout + 2);
+            inq_dout[4] = 0x0;
+            inq_dout[5] = 0x80;
+            inq_dout[6] = 0x83;
+            inq_dout[n - 1] = 0xde;     /* last VPD number */
+            break;
+        case 0x80:
+            /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+            inq_dout[1] = pg_cd;
+            sg_put_unaligned_be16(20, inq_dout + 2);
+            memcpy(inq_dout + 4, ptp->nvme_id_ctlp + 4, 20);    /* SN */
+            n = 24;
+            break;
+        case 0x83:
+            if ((ptp->nvme_nsid > 0) &&
+                (ptp->nvme_nsid < SG_NVME_BROADCAST_NSID)) {
+                nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns,
+                                         vb > 3);
+                if (nvme_id_ns) {
+                    struct sg_nvme_passthru_cmd cmd;
+
+                    memset(&cmd, 0, sizeof(cmd));
+                    cmd.opcode = 0x6;   /* Identify */
+                    cmd.nsid = ptp->nvme_nsid;
+                    cmd.cdw10 = 0x0;    /* CNS=0x0 Identify namespace */
+                    cmd.addr = (uint64_t)(sg_uintptr_t)nvme_id_ns;
+                    cmd.data_len = pg_sz;
+                    res = do_nvme_admin_cmd(ptp, &cmd, nvme_id_ns, true,
+                                            time_secs, vb > 3);
+                    if (res) {
+                        free(free_nvme_id_ns);
+                        free_nvme_id_ns = NULL;
+                        nvme_id_ns = NULL;
+                    }
+                }
+            }
+            n = sg_make_vpd_devid_for_nvme(ptp->nvme_id_ctlp, nvme_id_ns,
+                                           0 /* pdt */, -1 /*tproto */,
+                                           inq_dout, sizeof(inq_dout));
+            if (n > 3)
+                sg_put_unaligned_be16(n - 4, inq_dout + 2);
+            if (free_nvme_id_ns) {
+                free(free_nvme_id_ns);
+                free_nvme_id_ns = NULL;
+                nvme_id_ns = NULL;
+            }
+            break;
+        case 0xde:
+            inq_dout[1] = pg_cd;
+            sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2);
+            n = 16 + 4096;
+            cp_id_ctl = true;
+            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;
+            ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+            if (n > 0) {
+                if (cp_id_ctl) {
+                    memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout,
+                           (n < 16 ? n : 16));
+                    if (n > 16)
+                        memcpy((uint8_t *)ptp->io_hdr.din_xferp + 16,
+                               ptp->nvme_id_ctlp, n - 16);
+                } else
+                    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;
+            ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+            if (n > 0)
+                memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, n);
+        }
+    }
+    return 0;
+}
+
+static int
+sntl_rluns(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs,
+           int vb)
+{
+    int res;
+    uint16_t sel_report;
+    uint32_t alloc_len, k, n, num, max_nsid;
+    uint8_t * rl_doutp;
+    uint8_t * up;
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+
+    sel_report = cdbp[2];
+    alloc_len = sg_get_unaligned_be32(cdbp + 6);
+    if (NULL == ptp->nvme_id_ctlp) {
+        res = sntl_cache_identity(ptp, time_secs, vb);
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else if (res)
+            return res;
+    }
+    max_nsid = sg_get_unaligned_le32(ptp->nvme_id_ctlp + 516);
+    switch (sel_report) {
+    case 0:
+    case 2:
+        num = max_nsid;
+        break;
+    case 1:
+    case 0x10:
+    case 0x12:
+        num = 0;
+        break;
+    case 0x11:
+        num = (1 == ptp->nvme_nsid) ? max_nsid :  0;
+        break;
+    default:
+        if (vb > 1)
+            pr2ws("%s: bad select_report value: 0x%x\n", __func__,
+                  sel_report);
+        mk_sense_invalid_fld(ptp, true, 2, 7, vb);
+        return 0;
+    }
+    rl_doutp = (uint8_t *)calloc(num + 1, 8);
+    if (NULL == rl_doutp) {
+        pr2ws("%s: calloc() failed to get memory\n", __func__);
+        return -ENOMEM;
+    }
+    for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8)
+        sg_put_unaligned_be16(k, up);
+    n = num * 8;
+    sg_put_unaligned_be32(n, rl_doutp);
+    n+= 8;
+    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;
+        ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+        if (n > 0)
+            memcpy((uint8_t *)ptp->io_hdr.din_xferp, rl_doutp, n);
+    }
+    res = 0;
+    free(rl_doutp);
+    return res;
+}
+
+static int
+sntl_tur(struct sg_pt_linux_scsi * ptp, int time_secs, int vb)
+{
+    int res;
+    uint32_t pow_state;
+    struct sg_nvme_passthru_cmd cmd;
+
+    if (vb > 4)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+    if (NULL == ptp->nvme_id_ctlp) {
+        res = sntl_cache_identity(ptp, time_secs, vb);
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else if (res)
+            return res;
+    }
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0xa;   /* Get feature */
+    cmd.nsid = SG_NVME_BROADCAST_NSID;
+    cmd.cdw10 = 0x2;    /* SEL=0 (current), Feature=2 Power Management */
+    cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs);
+    res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb);
+    if (0 != res) {
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else
+            return res;
+    } else {
+        ptp->os_err = 0;
+        ptp->nvme_status = 0;
+    }
+    pow_state = (0x1f & ptp->nvme_result);
+    if (vb > 3)
+        pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+#if 0   /* pow_state bounces around too much on laptop */
+    if (pow_state)
+        mk_sense_asc_ascq(ptp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0,
+                          vb);
+#endif
+    return 0;
+}
+
+static int
+sntl_req_sense(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+               int time_secs, int vb)
+{
+    bool desc;
+    int res;
+    uint32_t pow_state, alloc_len, n;
+    struct sg_nvme_passthru_cmd cmd;
+    uint8_t rs_dout[64];
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+    if (NULL == ptp->nvme_id_ctlp) {
+        res = sntl_cache_identity(ptp, time_secs, vb);
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else if (res)
+            return res;
+    }
+    desc = !!(0x1 & cdbp[1]);
+    alloc_len = cdbp[4];
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0xa;   /* Get feature */
+    cmd.nsid = SG_NVME_BROADCAST_NSID;
+    cmd.cdw10 = 0x2;    /* SEL=0 (current), Feature=2 Power Management */
+    cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs);
+    res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb);
+    if (0 != res) {
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else
+            return res;
+    } else {
+        ptp->os_err = 0;
+        ptp->nvme_status = 0;
+    }
+    ptp->io_hdr.response_len = 0;
+    pow_state = (0x1f & ptp->nvme_result);
+    if (vb > 3)
+        pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+    memset(rs_dout, 0, sizeof(rs_dout));
+    if (pow_state)
+        build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+                           LOW_POWER_COND_ON_ASC, 0);
+    else
+        build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+                           NO_ADDITIONAL_SENSE, 0);
+    n = desc ? 8 : 18;
+    n = (n < alloc_len) ? n : alloc_len;
+    n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len;
+    ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+    if (n > 0)
+        memcpy((uint8_t *)ptp->io_hdr.din_xferp, rs_dout, n);
+    return 0;
+}
+
+/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI
+ * has a special command (SES Send) to tunnel through pages to an
+ * enclosure. The NVMe enclosure is meant to understand the SES
+ * (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_senddiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+              int time_secs, int vb)
+{
+    bool pf, self_test;
+    int res;
+    uint8_t st_cd, dpg_cd;
+    uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst;
+    uint32_t pg_sz = sg_get_page_size();
+    uint8_t * dop;
+    struct sg_nvme_passthru_cmd cmd;
+    uint8_t * cmd_up = (uint8_t *)&cmd;
+
+    st_cd = 0x7 & (cdbp[1] >> 5);
+    self_test = !! (0x4 & cdbp[1]);
+    pf = !! (0x10 & cdbp[1]);
+    if (vb > 3)
+        pr2ws("%s: pf=%d, self_test=%d (st_code=%d)\n", __func__, (int)pf,
+              (int)self_test, (int)st_cd);
+    if (self_test || st_cd) {
+        memset(cmd_up, 0, sizeof(cmd));
+        cmd_up[SG_NVME_PT_OPCODE] = 0x14;   /* Device self-test */
+        /* just this namespace (if there is one) and controller */
+        sg_put_unaligned_le32(ptp->nvme_nsid, cmd_up + SG_NVME_PT_NSID);
+        switch (st_cd) {
+        case 0: /* Here if self_test is set, do short self-test */
+        case 1: /* Background short */
+        case 5: /* Foreground short */
+            nvme_dst = 1;
+            break;
+        case 2: /* Background extended */
+        case 6: /* Foreground extended */
+            nvme_dst = 2;
+            break;
+        case 4: /* Abort self-test */
+            nvme_dst = 0xf;
+            break;
+        default:
+            pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd);
+            mk_sense_invalid_fld(ptp, true, 1, 7, vb);
+            return 0;
+        }
+        sg_put_unaligned_le32(nvme_dst, cmd_up + SG_NVME_PT_CDW10);
+        res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb);
+        if (0 != res) {
+            if (SG_LIB_NVME_STATUS == res) {
+                mk_sense_from_nvme_status(ptp, vb);
+                return 0;
+            } else
+                return res;
+        }
+    }
+    alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+    dout_len = ptp->io_hdr.dout_xfer_len;
+    if (pf) {
+        if (0 == alloc_len) {
+            mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+            if (vb)
+                pr2ws("%s: PF bit set bit param_list_len=0\n", __func__);
+            return 0;
+        }
+    } else {    /* PF bit clear */
+        if (alloc_len) {
+            mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+            if (vb)
+                pr2ws("%s: param_list_len>0 but PF clear\n", __func__);
+            return 0;
+        } else
+            return 0;     /* nothing to do */
+        if (dout_len > 0) {
+            if (vb)
+                pr2ws("%s: dout given but PF clear\n", __func__);
+            return SCSI_PT_DO_BAD_PARAMS;
+        }
+    }
+    if (dout_len < 4) {
+        if (vb)
+            pr2ws("%s: dout length (%u bytes) too short\n", __func__,
+                  dout_len);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    n = dout_len;
+    n = (n < alloc_len) ? n : alloc_len;
+    dop = (uint8_t *)ptp->io_hdr.dout_xferp;
+    if (! is_aligned(dop, pg_sz)) {  /* caller best use sg_memalign(,pg_sz) */
+        if (vb)
+            pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__,
+                  (uint64_t)ptp->io_hdr.dout_xferp);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    dpg_cd = dop[0];
+    dpg_len = sg_get_unaligned_be16(dop + 2) + 4;
+    /* should we allow for more than one D_PG is dout ?? */
+    n = (n < dpg_len) ? n : dpg_len;    /* not yet ... */
+
+    if (vb)
+        pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n",
+              __func__, dpg_cd, dpg_len);
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0x1d;  /* MI send; hmmm same opcode as SEND DIAG */
+    cmd.addr = (uint64_t)(sg_uintptr_t)dop;
+    cmd.data_len = 0x1000;   /* NVMe 4k page size. Maybe determine this? */
+                             /* dout_len > 0x1000, is this a problem?? */
+    cmd.cdw10 = 0x0804;      /* NVMe Message Header */
+    cmd.cdw11 = 0x9;         /* nvme_mi_ses_send; (0x8 -> mi_ses_recv) */
+    cmd.cdw13 = n;
+    res = do_nvme_admin_cmd(ptp, &cmd, dop, false, time_secs, vb);
+    if (0 != res) {
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        }
+    }
+    return res;
+}
+
+/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1)
+ * NVMe-MI has a special command (SES Receive) to read pages through a
+ * tunnel from an enclosure. The NVMe enclosure is meant to understand the
+ * SES (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_recvdiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+              int time_secs, int vb)
+{
+    bool pcv;
+    int res;
+    uint8_t dpg_cd;
+    uint32_t alloc_len, n, din_len;
+    uint32_t pg_sz = sg_get_page_size();
+    uint8_t * dip;
+    struct sg_nvme_passthru_cmd cmd;
+
+    pcv = !! (0x1 & cdbp[1]);
+    dpg_cd = cdbp[2];
+    alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+    if (vb > 3)
+        pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__,
+              dpg_cd, (int)pcv, alloc_len);
+    din_len = ptp->io_hdr.din_xfer_len;
+    n = din_len;
+    n = (n < alloc_len) ? n : alloc_len;
+    dip = (uint8_t *)ptp->io_hdr.din_xferp;
+    if (! is_aligned(dip, pg_sz)) {  /* caller best use sg_memalign(,pg_sz) */
+        if (vb)
+            pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__,
+                  (uint64_t)ptp->io_hdr.din_xferp);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+
+    if (vb)
+        pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__,
+              dpg_cd);
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.opcode = 0x1e;  /* MI receive */
+    cmd.addr = (uint64_t)(sg_uintptr_t)dip;
+    cmd.data_len = 0x1000;   /* NVMe 4k page size. Maybe determine this? */
+                             /* din_len > 0x1000, is this a problem?? */
+    cmd.cdw10 = 0x0804;      /* NVMe Message Header */
+    cmd.cdw11 = 0x8;         /* nvme_mi_ses_receive */
+    cmd.cdw12 = dpg_cd;
+    cmd.cdw13 = n;
+    res = do_nvme_admin_cmd(ptp, &cmd, dip, true, time_secs, vb);
+    if (0 != res) {
+        if (SG_LIB_NVME_STATUS == res) {
+            mk_sense_from_nvme_status(ptp, vb);
+            return 0;
+        } else
+            return res;
+    }
+    ptp->io_hdr.din_resid = din_len - n;
+    return res;
+}
+
+#define F_SA_LOW                0x80    /* cdb byte 1, bits 4 to 0 */
+#define F_SA_HIGH               0x100   /* as used by variable length cdbs */
+#define FF_SA (F_SA_HIGH | F_SA_LOW)
+#define F_INV_OP                0x200
+
+static struct opcode_info_t {
+        uint8_t opcode;
+        uint16_t sa;            /* service action, 0 for none */
+        uint32_t flags;         /* OR-ed set of F_* flags */
+        uint8_t len_mask[16];   /* len=len_mask[0], then mask for cdb[1]... */
+                                /* ignore cdb bytes after position 15 */
+    } opcode_info_arr[] = {
+    {0x0, 0, 0, {6,              /* TEST UNIT READY */
+      0, 0, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0x3, 0, 0, {6,             /* REQUEST SENSE */
+      0xe1, 0, 0, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0x12, 0, 0, {6,            /* INQUIRY */
+      0xe3, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0x1c, 0, 0, {6,            /* RECEIVE DIAGNOSTIC RESULTS */
+      0x1, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0x1d, 0, 0, {6,            /* SEND DIAGNOSTIC */
+      0xf7, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+    {0xa0, 0, 0, {12,           /* REPORT LUNS */
+      0xe3, 0xff, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} },
+    {0xa3, 0xc, F_SA_LOW, {12,  /* REPORT SUPPORTED OPERATION CODES */
+      0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0,
+      0} },
+    {0xa3, 0xd, F_SA_LOW, {12,  /* REPORT SUPPORTED TASK MAN. FUNCTIONS */
+      0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} },
+
+    {0xff, 0xffff, 0xffff, {0,  /* Sentinel, keep as last element */
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+};
+
+static int
+sntl_rep_opcodes(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+                 int time_secs, int vb)
+{
+    bool rctd;
+    uint8_t reporting_opts, req_opcode, supp;
+    uint16_t req_sa, u;
+    uint32_t alloc_len, offset, a_len;
+    uint32_t pg_sz = sg_get_page_size();
+    int k, len, count, bump;
+    const struct opcode_info_t *oip;
+    uint8_t *arr;
+    uint8_t *free_arr;
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+    rctd = !!(cdbp[2] & 0x80);      /* report command timeout desc. */
+    reporting_opts = cdbp[2] & 0x7;
+    req_opcode = cdbp[3];
+    req_sa = sg_get_unaligned_be16(cdbp + 4);
+    alloc_len = sg_get_unaligned_be32(cdbp + 6);
+    if (alloc_len < 4 || alloc_len > 0xffff) {
+        mk_sense_invalid_fld(ptp, true, 6, -1, vb);
+        return 0;
+    }
+    a_len = pg_sz - 72;
+    arr = sg_memalign(pg_sz, pg_sz, &free_arr, vb > 3);
+    if (NULL == arr) {
+        pr2ws("%s: calloc() failed to get memory\n", __func__);
+        return -ENOMEM;
+    }
+    switch (reporting_opts) {
+    case 0: /* all commands */
+        count = 0;
+        bump = rctd ? 20 : 8;
+        for (offset = 4, oip = opcode_info_arr;
+             (oip->flags != 0xffff) && (offset < a_len); ++oip) {
+            if (F_INV_OP & oip->flags)
+                continue;
+            ++count;
+            arr[offset] = oip->opcode;
+            sg_put_unaligned_be16(oip->sa, arr + offset + 2);
+            if (rctd)
+                arr[offset + 5] |= 0x2;
+            if (FF_SA & oip->flags)
+                arr[offset + 5] |= 0x1;
+            sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6);
+            if (rctd)
+                sg_put_unaligned_be16(0xa, arr + offset + 8);
+            offset += bump;
+        }
+        sg_put_unaligned_be32(count * bump, arr + 0);
+        break;
+    case 1: /* one command: opcode only */
+    case 2: /* one command: opcode plus service action */
+    case 3: /* one command: if sa==0 then opcode only else opcode+sa */
+        for (oip = opcode_info_arr; oip->flags != 0xffff; ++oip) {
+            if ((req_opcode == oip->opcode) && (req_sa == oip->sa))
+                break;
+        }
+        if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) {
+            supp = 1;
+            offset = 4;
+        } else {
+            if (1 == reporting_opts) {
+                if (FF_SA & oip->flags) {
+                    mk_sense_invalid_fld(ptp, true, 2, 2, vb);
+                    free(free_arr);
+                    return 0;
+                }
+                req_sa = 0;
+            } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) {
+                mk_sense_invalid_fld(ptp, true, 4, -1, vb);
+                free(free_arr);
+                return 0;
+            }
+            if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode))
+                supp = 3;
+            else if (0 == (FF_SA & oip->flags))
+                supp = 1;
+            else if (req_sa != oip->sa)
+                supp = 1;
+            else
+                supp = 3;
+            if (3 == supp) {
+                u = oip->len_mask[0];
+                sg_put_unaligned_be16(u, arr + 2);
+                arr[4] = oip->opcode;
+                for (k = 1; k < u; ++k)
+                    arr[4 + k] = (k < 16) ?
+                oip->len_mask[k] : 0xff;
+                offset = 4 + u;
+            } else
+                offset = 4;
+        }
+        arr[1] = (rctd ? 0x80 : 0) | supp;
+        if (rctd) {
+            sg_put_unaligned_be16(0xa, arr + offset);
+            offset += 12;
+        }
+        break;
+    default:
+        mk_sense_invalid_fld(ptp, true, 2, 2, vb);
+        free(free_arr);
+        return 0;
+    }
+    offset = (offset < a_len) ? offset : a_len;
+    len = (offset < alloc_len) ? offset : alloc_len;
+    ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len;
+    if (len > 0)
+        memcpy((uint8_t *)ptp->io_hdr.din_xferp, arr, len);
+    free(free_arr);
+    return 0;
+}
+
+static int
+sntl_rep_tmfs(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+              int time_secs, int vb)
+{
+    bool repd;
+    uint32_t alloc_len, len;
+    uint8_t arr[16];
+
+    if (vb > 3)
+        pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+    memset(arr, 0, sizeof(arr));
+    repd = !!(cdbp[2] & 0x80);
+    alloc_len = sg_get_unaligned_be32(cdbp + 6);
+    if (alloc_len < 4) {
+        mk_sense_invalid_fld(ptp, true, 6, -1, vb);
+        return 0;
+    }
+    arr[0] = 0xc8;          /* ATS | ATSS | LURS */
+    arr[1] = 0x1;           /* ITNRS */
+    if (repd) {
+        arr[3] = 0xc;
+        len = 16;
+    } else
+        len = 4;
+
+    len = (len < alloc_len) ? len : alloc_len;
+    ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len;
+    if (len > 0)
+        memcpy((uint8_t *)ptp->io_hdr.din_xferp, arr, len);
+    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_cdb;
+    bool is_read = false;
+    int n, len;
+    uint16_t sa;
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+    struct sg_nvme_passthru_cmd cmd;
+    const uint8_t * cdbp;
+    void * dp = NULL;
+
+    if (! ptp->io_hdr.request) {
+        if (vb)
+            pr2ws("No NVMe command given (set_scsi_pt_cdb())\n");
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    if (fd >= 0) {
+        if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) {
+            if (vb)
+                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 (vb)
+            pr2ws("%s: invalid file descriptors\n", __func__);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    n = ptp->io_hdr.request_len;
+    cdbp = (const uint8_t *)ptp->io_hdr.request;
+    if (vb > 3)
+        pr2ws("%s: opcode=0x%x, fd=%d, time_secs=%d\n", __func__, cdbp[0],
+              fd, time_secs);
+    scsi_cdb = sg_is_scsi_cdb(cdbp, n);
+    /* direct NVMe command (i.e. 64 bytes long) or SNTL */
+    ptp->nvme_direct = ! scsi_cdb;
+    if (scsi_cdb) {
+        switch (cdbp[0]) {
+        case SCSI_INQUIRY_OPC:
+            return sntl_inq(ptp, cdbp, time_secs, vb);
+        case SCSI_REPORT_LUNS_OPC:
+            return sntl_rluns(ptp, cdbp, time_secs, vb);
+        case SCSI_TEST_UNIT_READY_OPC:
+            return sntl_tur(ptp, time_secs, vb);
+        case SCSI_REQUEST_SENSE_OPC:
+            return sntl_req_sense(ptp, cdbp, time_secs, vb);
+        case SCSI_SEND_DIAGNOSTIC_OPC:
+            return sntl_senddiag(ptp, cdbp, time_secs, vb);
+        case SCSI_RECEIVE_DIAGNOSTIC_OPC:
+            return sntl_recvdiag(ptp, cdbp, time_secs, vb);
+        case SCSI_MAINT_IN_OPC:
+            sa = 0x1f & cdbp[1];        /* service action */
+            if (SCSI_REP_SUP_OPCS_OPC == sa)
+                return sntl_rep_opcodes(ptp, cdbp, time_secs, vb);
+            else if (SCSI_REP_SUP_TMFS_OPC == sa)
+                return sntl_rep_tmfs(ptp, cdbp, time_secs, vb);
+            /* fall through */
+        default:
+            if (vb > 2) {
+                char b[64];
+
+                sg_get_command_name(cdbp, -1, sizeof(b), b);
+                pr2ws("%s: no translation to NVMe for SCSI %s command\n",
+                      __func__, b);
+            }
+            mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE,
+                              0, vb);
+            return 0;
+        }
+    }
+    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, (const uint8_t *)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;
+        dp = (void *)ptp->io_hdr.din_xferp;
+        cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+        is_read = true;
+    } else if (ptp->io_hdr.dout_xfer_len > 0) {
+        cmd.data_len = ptp->io_hdr.dout_xfer_len;
+        dp = (void *)ptp->io_hdr.dout_xferp;
+        cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+        is_read = false;
+    }
+    return do_nvme_admin_cmd(ptp, &cmd, dp, is_read, time_secs, vb);
+}
+
+#else           /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+int
+sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb)
+{
+    if (vb)
+        pr2ws("%s: not supported\n", __func__);
+    if (vp) { ; }               /* suppress warning */
+    if (fd) { ; }               /* suppress warning */
+    if (time_secs) { ; }        /* suppress warning */
+    return -ENOTTY;             /* inappropriate ioctl error */
+}
+
+#endif          /* (HAVE_NVME && (! IGNORE_NVME)) */
diff --git a/tools/sg_write_buffer/sg_write_buffer.c b/tools/sg_write_buffer/sg_write_buffer.c
new file mode 100644
index 0000000..18d8f6f
--- /dev/null
+++ b/tools/sg_write_buffer/sg_write_buffer.c
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2006-2018 Luben Tuikov and 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 <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <string.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+#include "sg_pt.h"      /* needed for scsi_pt_win32_direct() */
+#endif
+#endif
+
+/*
+ * This utility issues the SCSI WRITE BUFFER command to the given device.
+ */
+
+static const char * version_str = "1.24 20180111";    /* spc5r18 */
+
+#define ME "sg_write_buffer: "
+#define DEF_XFER_LEN (8 * 1024 * 1024)
+#define EBUFF_SZ 256
+
+#define WRITE_BUFFER_CMD 0x3b
+#define WRITE_BUFFER_CMDLEN 10
+#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 300      /* 300 seconds, 5 minutes */
+
+static struct option long_options[] = {
+        {"bpw", required_argument, 0, 'b'},
+        {"dry-run", no_argument, 0, 'd'},
+        {"dry_run", no_argument, 0, 'd'},
+        {"help", no_argument, 0, 'h'},
+        {"id", required_argument, 0, 'i'},
+        {"in", required_argument, 0, 'I'},
+        {"length", required_argument, 0, 'l'},
+        {"mode", required_argument, 0, 'm'},
+        {"offset", required_argument, 0, 'o'},
+        {"read-stdin", no_argument, 0, 'r'},
+        {"read_stdin", no_argument, 0, 'r'},
+        {"raw", no_argument, 0, 'r'},
+        {"skip", required_argument, 0, 's'},
+        {"specific", required_argument, 0, 'S'},
+        {"timeout", required_argument, 0, 't' },
+        {"verbose", no_argument, 0, 'v'},
+        {"version", no_argument, 0, 'V'},
+        {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+    pr2serr("Usage: "
+            "sg_write_buffer [--bpw=CS] [--dry-run] [--help] [--id=ID] "
+            "[--in=FILE]\n"
+            "                       [--length=LEN] [--mode=MO] "
+            "[--offset=OFF]\n"
+            "                       [--read-stdin] [--skip=SKIP] "
+            "[--specific=MS]\n"
+            "                       [--timeout=TO] [--verbose] [--version] "
+            "DEVICE\n"
+            "  where:\n"
+            "    --bpw=CS|-b CS         CS is chunk size: bytes per write "
+            "buffer\n"
+            "                           command (def: 0 -> as many as "
+            "possible)\n"
+            "    --dry-run|-d           skip WRITE BUFFER commands, do "
+            "everything else\n"
+            "    --help|-h              print out usage message then exit\n"
+            "    --id=ID|-i ID          buffer identifier (0 (default) to "
+            "255)\n"
+            "    --in=FILE|-I FILE      read from FILE ('-I -' read "
+            "from stdin)\n"
+            "    --length=LEN|-l LEN    length in bytes to write; may be "
+            "deduced from\n"
+            "                           FILE\n"
+            "    --mode=MO|-m MO        write buffer mode, MO is number or "
+            "acronym\n"
+            "                           (def: 0 -> 'combined header and "
+            "data' (obs))\n"
+            "    --offset=OFF|-o OFF    buffer offset (unit: bytes, def: 0)\n"
+            "    --read-stdin|-r        read from stdin (same as '-I -')\n"
+            "    --skip=SKIP|-s SKIP    bytes in file FILE to skip before "
+            "reading\n"
+            "    --specific=MS|-S MS    mode specific value; 3 bit field "
+            "(0 to 7)\n"
+            "    --timeout=TO|-t TO     command timeout in seconds (def: "
+            "300)\n"
+            "    --verbose|-v           increase verbosity\n"
+            "    --version|-V           print version string and exit\n\n"
+            "Performs one or more SCSI WRITE BUFFER commands. Use '-m xxx' "
+            "to list\navailable modes. A chunk size of 4 KB ('--bpw=4k') "
+            "seems to work well.\nExample: sg_write_buffer -b 4k -I xxx.lod "
+            "-m 7 /dev/sg3\n"
+          );
+
+}
+
+#define MODE_HEADER_DATA        0
+#define MODE_VENDOR             1
+#define MODE_DATA               2
+#define MODE_DNLD_MC            4
+#define MODE_DNLD_MC_SAVE       5
+#define MODE_DNLD_MC_OFFS       6
+#define MODE_DNLD_MC_OFFS_SAVE  7
+#define MODE_ECHO_BUFFER        0x0A
+#define MODE_DNLD_MC_EV_OFFS_DEFER 0x0D
+#define MODE_DNLD_MC_OFFS_DEFER 0x0E
+#define MODE_ACTIVATE_MC        0x0F
+#define MODE_EN_EX_ECHO         0x1A
+#define MODE_DIS_EX             0x1B
+#define MODE_DNLD_ERR_HISTORY   0x1C
+
+
+struct mode_s {
+        const char *mode_string;
+        int   mode;
+        const char *comment;
+};
+
+static struct mode_s mode_arr[] = {
+        {"hd",         MODE_HEADER_DATA, "combined header and data "
+                "(obsolete)"},
+        {"vendor",     MODE_VENDOR,    "vendor specific"},
+        {"data",       MODE_DATA,      "data"},
+        {"dmc",        MODE_DNLD_MC,   "download microcode and activate"},
+        {"dmc_save",   MODE_DNLD_MC_SAVE, "download microcode, save and "
+                "activate"},
+        {"dmc_offs",   MODE_DNLD_MC_OFFS, "download microcode with offsets "
+                "and activate"},
+        {"dmc_offs_save", MODE_DNLD_MC_OFFS_SAVE, "download microcode with "
+                "offsets, save and\n\t\t\t\tactivate"},
+        {"echo",       MODE_ECHO_BUFFER, "write data to echo buffer"},
+        {"dmc_offs_ev_defer", MODE_DNLD_MC_EV_OFFS_DEFER, "download "
+                "microcode with offsets, select\n\t\t\t\tactivation event, "
+                "save and defer activation"},
+        {"dmc_offs_defer", MODE_DNLD_MC_OFFS_DEFER, "download microcode "
+                "with offsets, save and\n\t\t\t\tdefer activation"},
+        {"activate_mc", MODE_ACTIVATE_MC, "activate deferred microcode"},
+        {"en_ex",      MODE_EN_EX_ECHO, "enable expander communications "
+                "protocol and\n\t\t\t\techo buffer (obsolete)"},
+        {"dis_ex",     MODE_DIS_EX, "disable expander communications "
+                "protocol\n\t\t\t\t(obsolete)"},
+        {"deh",        MODE_DNLD_ERR_HISTORY, "download application client "
+                "error history "},
+        {NULL, 0, NULL},
+};
+
+static void
+print_modes(void)
+{
+    const struct mode_s * mp;
+
+    pr2serr("The modes parameter argument can be numeric (hex or decimal)\n"
+            "or symbolic:\n");
+    for (mp = mode_arr; mp->mode_string; ++mp) {
+        pr2serr(" %2d (0x%02x)  %-18s%s\n", mp->mode, mp->mode,
+                mp->mode_string, mp->comment);
+    }
+    pr2serr("\nAdditionally '--bpw=<val>,act' does a activate deferred "
+            "microcode after\nsuccessful dmc_offs_defer and "
+            "dmc_offs_ev_defer mode downloads.\n");
+}
+
+
+int
+main(int argc, char * argv[])
+{
+    bool bpw_then_activate = false;
+    bool dry_run = false;
+    bool got_stdin = false;
+    bool wb_len_given = false;
+    int sg_fd, infd, res, c, len, k, n;
+    int bpw = 0;
+    int do_help = 0;
+    int ret = 0;
+    int verbose = 0;
+    int wb_id = 0;
+    int wb_len = 0;
+    int wb_mode = 0;
+    int wb_offset = 0;
+    int wb_skip = 0;
+    int wb_timeout = DEF_PT_TIMEOUT;
+    int wb_mspec = 0;
+    const char * device_name = NULL;
+    const char * file_name = NULL;
+    unsigned char * dop = NULL;
+    char * cp;
+    const struct mode_s * mp;
+    char ebuff[EBUFF_SZ];
+
+    while (1) {
+        int option_index = 0;
+
+        c = getopt_long(argc, argv, "b:dhi:I:l:m:o:rs:S:t:vV", long_options,
+                        &option_index);
+        if (c == -1)
+            break;
+
+        switch (c) {
+        case 'b':
+            bpw = sg_get_num(optarg);
+            if (bpw < 0) {
+                pr2serr("argument to '--bpw' should be in a positive "
+                        "number\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            if ((cp = strchr(optarg, ','))) {
+                if (0 == strncmp("act", cp + 1, 3))
+                    bpw_then_activate = true;
+            }
+            break;
+        case 'd':
+            dry_run = true;
+            break;
+        case 'h':
+        case '?':
+            ++do_help;
+            break;
+        case 'i':
+            wb_id = sg_get_num(optarg);
+            if ((wb_id < 0) || (wb_id > 255)) {
+                pr2serr("argument to '--id' should be in the range 0 to "
+                        "255\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'I':
+            file_name = optarg;
+            break;
+        case 'l':
+            wb_len = sg_get_num(optarg);
+            if (wb_len < 0) {
+                pr2serr("bad argument to '--length'\n");
+                return SG_LIB_SYNTAX_ERROR;
+             }
+             wb_len_given = true;
+             break;
+        case 'm':
+            if (isdigit(*optarg)) {
+                wb_mode = sg_get_num(optarg);
+                if ((wb_mode < 0) || (wb_mode > 31)) {
+                    pr2serr("argument to '--mode' should be in the range 0 "
+                            "to 31\n");
+                    return SG_LIB_SYNTAX_ERROR;
+                }
+            } else {
+                len = strlen(optarg);
+                for (mp = mode_arr; mp->mode_string; ++mp) {
+                    if (0 == strncmp(mp->mode_string, optarg, len)) {
+                        wb_mode = mp->mode;
+                        break;
+                    }
+                }
+                if (! mp->mode_string) {
+                    print_modes();
+                    return SG_LIB_SYNTAX_ERROR;
+                }
+            }
+            break;
+        case 'o':
+           wb_offset = sg_get_num(optarg);
+           if (wb_offset < 0) {
+                pr2serr("bad argument to '--offset'\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'r':       /* --read-stdin and --raw (previous name) */
+            file_name = "-";
+            break;
+        case 's':
+           wb_skip = sg_get_num(optarg);
+           if (wb_skip < 0) {
+                pr2serr("bad argument to '--skip'\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'S':
+            wb_mspec = sg_get_num(optarg);
+            if ((wb_mspec < 0) || (wb_mspec > 7)) {
+                pr2serr("expected argument to '--specific' to be 0 to 7\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 't':
+            wb_timeout = sg_get_num(optarg);
+            if (wb_timeout < 0) {
+                pr2serr("Invalid argument to '--timeout'\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'v':
+            ++verbose;
+            break;
+        case 'V':
+            pr2serr(ME "version: %s\n", version_str);
+            return 0;
+        default:
+            pr2serr("unrecognised option code 0x%x ??\n", c);
+            usage();
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+    if (do_help) {
+        if (do_help > 1) {
+            usage();
+            pr2serr("\n");
+            print_modes();
+        } else
+            usage();
+        return 0;
+    }
+    if (optind < argc) {
+        if (NULL == device_name) {
+            device_name = argv[optind];
+            ++optind;
+        }
+        if (optind < argc) {
+            for (; optind < argc; ++optind)
+                pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+            usage();
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+
+    if (NULL == device_name) {
+        pr2serr("missing device name!\n");
+        usage();
+        return SG_LIB_SYNTAX_ERROR;
+    }
+
+    if ((wb_len > 0) && (bpw > wb_len)) {
+        pr2serr("trim chunk size (CS) to be the same as LEN\n");
+        bpw = wb_len;
+    }
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+    if (verbose > 4)
+        pr2serr("Initial win32 SPT interface state: %s\n",
+                scsi_pt_win32_spt_state() ? "direct" : "indirect");
+    scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
+#endif
+#endif
+
+    sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+    if (sg_fd < 0) {
+        pr2serr(ME "open error: %s: %s\n", device_name,
+                safe_strerror(-sg_fd));
+        return SG_LIB_FILE_ERROR;
+    }
+    if (file_name || (wb_len > 0)) {
+        if (0 == wb_len)
+            wb_len = DEF_XFER_LEN;
+        if (NULL == (dop = (unsigned char *)malloc(wb_len))) {
+            pr2serr(ME "out of memory\n");
+            ret = SG_LIB_SYNTAX_ERROR;
+            goto err_out;
+        }
+        memset(dop, 0xff, wb_len);
+        if (file_name) {
+            got_stdin = (0 == strcmp(file_name, "-"));
+            if (got_stdin) {
+                if (wb_skip > 0) {
+                    pr2serr("Can't skip on stdin\n");
+                    ret = SG_LIB_FILE_ERROR;
+                    goto err_out;
+                }
+                infd = STDIN_FILENO;
+            } else {
+                if ((infd = open(file_name, O_RDONLY)) < 0) {
+                    snprintf(ebuff, EBUFF_SZ,
+                             ME "could not open %s for reading", file_name);
+                    perror(ebuff);
+                    ret = SG_LIB_FILE_ERROR;
+                    goto err_out;
+                } else if (sg_set_binary_mode(infd) < 0)
+                    perror("sg_set_binary_mode");
+                if (wb_skip > 0) {
+                    if (lseek(infd, wb_skip, SEEK_SET) < 0) {
+                        snprintf(ebuff,  EBUFF_SZ, ME "couldn't skip to "
+                                 "required position on %s", file_name);
+                        perror(ebuff);
+                        close(infd);
+                        ret = SG_LIB_FILE_ERROR;
+                        goto err_out;
+                    }
+                }
+            }
+            res = read(infd, dop, wb_len);
+            if (res < 0) {
+                snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
+                         file_name);
+                perror(ebuff);
+                if (! got_stdin)
+                    close(infd);
+                ret = SG_LIB_FILE_ERROR;
+                goto err_out;
+            }
+            if (res < wb_len) {
+                if (wb_len_given) {
+                    pr2serr("tried to read %d bytes from %s, got %d bytes\n",
+                            wb_len, file_name, res);
+                    pr2serr("pad with 0xff bytes and continue\n");
+                } else {
+                    if (verbose) {
+                        pr2serr("tried to read %d bytes from %s, got %d "
+                                "bytes\n", wb_len, file_name, res);
+                        pr2serr("will write %d bytes", res);
+                        if ((bpw > 0) && (bpw < wb_len))
+                            pr2serr(", %d bytes per WRITE BUFFER command\n",
+                                    bpw);
+                        else
+                            pr2serr("\n");
+                    }
+                    wb_len = res;
+                }
+            }
+            if (! got_stdin)
+                close(infd);
+        }
+    }
+
+    res = 0;
+    if (bpw > 0) {
+        for (k = 0; k < wb_len; k += n) {
+            n = wb_len - k;
+            if (n > bpw)
+                n = bpw;
+            if (verbose)
+                pr2serr("sending write buffer, mode=0x%x, mspec=%d, id=%d, "
+                        " offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
+                        wb_offset + k, n);
+            if (dry_run) {
+                if (verbose)
+                    pr2serr("skipping WRITE BUFFER command due to "
+                            "--dry-run\n");
+                res = 0;
+            } else
+                res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
+                                            wb_offset + k, dop + k, n,
+                                            wb_timeout, true, verbose);
+            if (res)
+                break;
+        }
+        if (bpw_then_activate) {
+            if (verbose)
+                pr2serr("sending Activate deferred microcode [0xf]\n");
+            if (dry_run) {
+                if (verbose)
+                    pr2serr("skipping WRITE BUFFER(ACTIVATE) command due to "
+                            "--dry-run\n");
+                res = 0;
+            } else
+                res = sg_ll_write_buffer_v2(sg_fd, MODE_ACTIVATE_MC,
+                                            0 /* buffer_id */,
+                                            0 /* buffer_offset */, 0,
+                                            NULL, 0, wb_timeout, true,
+                                            verbose);
+        }
+    } else {
+        if (verbose)
+            pr2serr("sending single write buffer, mode=0x%x, mpsec=%d, "
+                    "id=%d, offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
+                    wb_offset, wb_len);
+        if (dry_run) {
+            if (verbose)
+                pr2serr("skipping WRITE BUFFER(all in one) command due to "
+                        "--dry-run\n");
+            res = 0;
+        } else
+            res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
+                                        wb_offset, dop, wb_len, wb_timeout,
+                                        true, verbose);
+    }
+    if (0 != res) {
+        char b[80];
+
+        ret = res;
+        sg_get_category_sense_str(res, sizeof(b), b, verbose);
+        pr2serr("Write buffer failed: %s\n", b);
+    }
+
+err_out:
+    if (dop)
+        free(dop);
+    res = sg_cmds_close_device(sg_fd);
+    if (res < 0) {
+        pr2serr("close error: %s\n", safe_strerror(-res));
+        if (0 == ret)
+            return SG_LIB_FILE_ERROR;
+    }
+    return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
-- 
2.15.0.531.g2ccb3012c9-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1)
  2018-03-14  0:08 [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Jaegeuk Kim
  2018-03-14  0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim
@ 2018-03-16  8:26 ` Chao Yu
  1 sibling, 0 replies; 8+ messages in thread
From: Chao Yu @ 2018-03-16  8:26 UTC (permalink / raw)
  To: Jaegeuk Kim, linux-f2fs-devel; +Cc: Hyojun Kim

On 2018/3/14 8:08, Jaegeuk Kim wrote:
> From: Hyojun Kim <hyojun@google.com>
> 
> It was reported that #pragma pack(1) could create unwanted
> influences. pack(push, 1) and pack(pop) are used instead.
> 
> Signed-off-by: Hyojun Kim <hyojun@google.com>
> Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>

Reviewed-by: Chao Yu <yuchao0@huawei.com>

Thanks,


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU
  2018-03-14  0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim
  2018-03-14  2:10   ` Junling Zheng
@ 2018-03-16  8:29   ` Chao Yu
  2018-03-17  4:59     ` Jaegeuk Kim
  1 sibling, 1 reply; 8+ messages in thread
From: Chao Yu @ 2018-03-16  8:29 UTC (permalink / raw)
  To: Jaegeuk Kim, linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim

Hi Jaegeuk,

On 2018/3/14 8:08, Jaegeuk Kim wrote:
> From: Hyojun Kim <hyojun@google.com>
> 
> sg_write_buffer sources are added for FFU.

Could you please explain more details about why we need to add those source
codes? ;)

Thanks,


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU
  2018-03-16  8:29   ` [PATCH 2/2] " Chao Yu
@ 2018-03-17  4:59     ` Jaegeuk Kim
  2018-03-19  2:40       ` Chao Yu
  0 siblings, 1 reply; 8+ messages in thread
From: Jaegeuk Kim @ 2018-03-17  4:59 UTC (permalink / raw)
  To: Chao Yu; +Cc: Hyojun Kim, Jaegeuk Kim, linux-f2fs-devel

On 03/16, Chao Yu wrote:
> Hi Jaegeuk,
> 
> On 2018/3/14 8:08, Jaegeuk Kim wrote:
> > From: Hyojun Kim <hyojun@google.com>
> > 
> > sg_write_buffer sources are added for FFU.
> 
> Could you please explain more details about why we need to add those source
> codes? ;)

We need this for FFU. What else do we need?

> 
> Thanks,

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU
  2018-03-17  4:59     ` Jaegeuk Kim
@ 2018-03-19  2:40       ` Chao Yu
  0 siblings, 0 replies; 8+ messages in thread
From: Chao Yu @ 2018-03-19  2:40 UTC (permalink / raw)
  To: Jaegeuk Kim; +Cc: Hyojun Kim, Jaegeuk Kim, linux-f2fs-devel

On 2018/3/17 12:59, Jaegeuk Kim wrote:
> On 03/16, Chao Yu wrote:
>> Hi Jaegeuk,
>>
>> On 2018/3/14 8:08, Jaegeuk Kim wrote:
>>> From: Hyojun Kim <hyojun@google.com>
>>>
>>> sg_write_buffer sources are added for FFU.
>>
>> Could you please explain more details about why we need to add those source
>> codes? ;)
> 
> We need this for FFU. What else do we need?

You mean Field Firmware Update?

Oh, I don't know, just be curious to know how f2fs using these drive to do FFU.

Thanks,

> 
>>
>> Thanks,
> 
> .
> 


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2018-03-19  2:40 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-03-14  0:08 [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Jaegeuk Kim
2018-03-14  0:08 ` [PATCH 2/2] tools: sg_write_buffer: add sg_write_buffer for FFU Jaegeuk Kim
2018-03-14  2:10   ` Junling Zheng
2018-03-14  4:27     ` [PATCH 2/2 v2] " Jaegeuk Kim
2018-03-16  8:29   ` [PATCH 2/2] " Chao Yu
2018-03-17  4:59     ` Jaegeuk Kim
2018-03-19  2:40       ` Chao Yu
2018-03-16  8:26 ` [PATCH 1/2] f2fs-tools: change to use #pragma pack(push, 1) Chao Yu

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.