[automerger skipped] Merge changes from topic "discard" into oc-dev am: d16862a25e  -s ours am: 7077074bd0  -s ours
am: b254bf3d8c  -s ours

Change-Id: I4c5c1cb36e7a203e9ee21903ce36efc7bb57b463
diff --git a/.gitignore b/.gitignore
index abe1336..d5ca55d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,7 @@
 /tools/fibmap.f2fs
 /tools/parse.f2fs
 /tools/f2fscrypt
+
+# cscope files
+cscope.*
+ncscope.*
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..b0019ac
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,191 @@
+// Copyright 2017 The Android Open Source Project
+
+cc_defaults {
+    name: "f2fs-tools-defaults",
+    cflags: [
+        "-DF2FS_MAJOR_VERSION=1",
+        "-DF2FS_MINOR_VERSION=10",
+        "-DF2FS_TOOLS_VERSION=\"1.10.0\"",
+        "-DF2FS_TOOLS_DATE=\"2018-01-30\"",
+        "-DWITH_ANDROID",
+        "-Wall",
+        "-Werror",
+        "-Wno-format",
+        "-Wno-macro-redefined",
+        "-Wno-missing-field-initializers",
+        "-Wno-pointer-arith",
+        "-Wno-sign-compare",
+        "-Wno-unused-function",
+    ],
+    local_include_dirs: [
+        "include",
+        "mkfs",
+        "fsck",
+    ],
+    include_dirs: [
+        "external/e2fsprogs/lib/",
+        "system/core/libsparse/include",
+    ],
+    target: {
+        windows: {
+            enabled: false,
+        },
+    },
+}
+
+cc_defaults {
+    name: "libf2fs_src_files",
+    cflags: ["-DWITH_BLKDISCARD"],
+    srcs: [
+        "lib/libf2fs.c",
+        "mkfs/f2fs_format.c",
+        "mkfs/f2fs_format_utils.c",
+        "lib/libf2fs_zoned.c",
+    ],
+}
+
+cc_defaults {
+    name: "make_f2fs_src_files",
+    srcs: [
+        "lib/libf2fs_io.c",
+        "mkfs/f2fs_format_main.c",
+    ],
+}
+
+cc_defaults {
+    name: "fsck_main_src_files",
+    srcs: [
+        "fsck/dir.c",
+        "fsck/dict.c",
+        "fsck/mkquota.c",
+        "fsck/quotaio.c",
+        "fsck/quotaio_tree.c",
+        "fsck/quotaio_v2.c",
+        "fsck/node.c",
+        "fsck/segment.c",
+        "fsck/xattr.c",
+        "fsck/main.c",
+        "fsck/mount.c",
+        "lib/libf2fs.c",
+        "lib/libf2fs_io.c",
+    ],
+}
+
+cc_library_static {
+    name: "libf2fs_fmt",
+    defaults: [
+        "f2fs-tools-defaults",
+        "libf2fs_src_files"
+    ],
+}
+
+cc_library_host_static {
+    name: "libf2fs_fmt_host",
+    defaults: [
+        "f2fs-tools-defaults",
+        "libf2fs_src_files"
+    ],
+    target: {
+        windows: {
+            include_dirs: [ "external/e2fsprogs/include/mingw" ],
+            cflags: ["-DANDROID_WINDOWS_HOST"],
+            enabled: true
+	},
+    },
+}
+
+cc_binary {
+    name: "make_f2fs",
+    defaults: [
+        "f2fs-tools-defaults",
+        "make_f2fs_src_files",
+    ],
+    host_supported: true,
+    target: {
+        android: {
+            static_libs: [
+                "libf2fs_fmt",
+            ],
+            shared_libs: [
+                "libext2_uuid",
+                "libsparse",
+                "libbase",
+            ],
+        },
+        host: {
+            static_libs: [
+	        "libf2fs_fmt_host",
+                "libext2_uuid",
+                "libsparse",
+                "libbase",
+                "libz",
+            ],
+        },
+        windows: {
+            include_dirs: [ "external/e2fsprogs/include/mingw" ],
+            cflags: ["-DANDROID_WINDOWS_HOST"],
+            ldflags: ["-static"],
+            host_ldlibs: ["-lws2_32"],
+            enabled: true
+	},
+    },
+}
+
+cc_binary {
+    name: "fsck.f2fs",
+    defaults: [
+        "f2fs-tools-defaults",
+        "fsck_main_src_files",
+    ],
+    host_supported: true,
+    srcs: ["fsck/fsck.c"],
+    shared_libs: [
+        "libext2_uuid",
+        "libsparse",
+        "libbase",
+    ],
+}
+
+cc_binary {
+    name: "sload_f2fs",
+    defaults: [
+        "f2fs-tools-defaults",
+        "fsck_main_src_files",
+    ],
+    host_supported: true,
+    cflags: ["-DWITH_SLOAD"],
+    srcs: ["fsck/fsck.c", "fsck/sload.c"],
+    target: {
+        android: {
+            shared_libs: [
+                "libext2_uuid",
+                "libsparse",
+                "libbase",
+                "libcrypto",
+                "libselinux",
+                "libcutils",
+                "liblog",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libext2_uuid",
+                "libsparse",
+                "libbase",
+                "libcrypto",
+                "libselinux",
+                "libcutils",
+                "liblog",
+                "libz",
+            ],
+        },
+    },
+    stl: "libc++_static",
+}
+
+cc_binary {
+    name: "check_f2fs",
+    host_supported: false,
+    cflags: ["--static"],
+    srcs: ["tools/check_f2fs.c"],
+}
diff --git a/Android.mk b/Android.mk
index d4f19cc..67e3e93 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,66 +1,23 @@
 LOCAL_PATH:= $(call my-dir)
 
 # f2fs-tools depends on Linux kernel headers being in the system include path.
-ifeq ($(HOST_OS),linux)
+ifneq (,$filter linux darwin,$(HOST_OS))
 
 # The versions depend on $(LOCAL_PATH)/VERSION
-version_CFLAGS := -DF2FS_MAJOR_VERSION=1 -DF2FS_MINOR_VERSION=8 -DF2FS_TOOLS_VERSION=\"1.8.0\" -DF2FS_TOOLS_DATE=\"2017-02-03\"
-common_CFLAGS := -DWITH_ANDROID -DWITH_BLKDISCARD $(version_CFLAGS)
-# Workaround for the <sys/types.h>/<sys/sysmacros.h> split, here now for
-# bionic and coming later for glibc.
-target_CFLAGS := $(common_CFLAGS) -include sys/sysmacros.h
+version_CFLAGS := -DF2FS_MAJOR_VERSION=1 -DF2FS_MINOR_VERSION=10 -DF2FS_TOOLS_VERSION=\"1.10.0\" -DF2FS_TOOLS_DATE=\"2018-01-30\"
+common_CFLAGS := -DWITH_ANDROID $(version_CFLAGS) \
+    -Wall -Werror \
+    -Wno-format \
+    -Wno-macro-redefined \
+    -Wno-missing-field-initializers \
+    -Wno-pointer-arith \
+    -Wno-sign-compare \
+    -Wno-unused-function \
 
 # external/e2fsprogs/lib is needed for uuid/uuid.h
-common_C_INCLUDES := $(LOCAL_PATH)/include external/e2fsprogs/lib/ system/core/libsparse/include
-
-#----------------------------------------------------------
-include $(CLEAR_VARS)
-LOCAL_MODULE := libf2fs_fmt
-LOCAL_SRC_FILES := \
-	lib/libf2fs.c \
-	lib/libf2fs_zoned.c \
-	mkfs/f2fs_format.c \
-	mkfs/f2fs_format_utils.c \
-
-LOCAL_C_INCLUDES := $(common_C_INCLUDES)
-LOCAL_CFLAGS := $(target_CFLAGS)
-LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/mkfs
-include $(BUILD_STATIC_LIBRARY)
-
-#----------------------------------------------------------
-include $(CLEAR_VARS)
-LOCAL_MODULE := libf2fs_fmt_host
-LOCAL_SRC_FILES := \
-	lib/libf2fs.c \
-	lib/libf2fs_zoned.c \
-	mkfs/f2fs_format.c \
-	mkfs/f2fs_format_utils.c \
-
-LOCAL_C_INCLUDES := $(common_C_INCLUDES)
-LOCAL_CFLAGS := $(common_CFLAGS)
-LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/mkfs
-include $(BUILD_HOST_STATIC_LIBRARY)
-
-#----------------------------------------------------------
-include $(CLEAR_VARS)
-LOCAL_MODULE := libf2fs_fmt_host_dyn
-LOCAL_SRC_FILES := \
-	lib/libf2fs.c \
-	lib/libf2fs_zoned.c \
-	lib/libf2fs_io.c \
-	mkfs/f2fs_format.c \
-	mkfs/f2fs_format_utils.c \
-
-LOCAL_C_INCLUDES := $(common_C_INCLUDES)
-LOCAL_CFLAGS := $(common_CFLAGS)
-LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/mkfs
-LOCAL_STATIC_LIBRARIES := \
-	libf2fs_ioutils_host \
-	libext2_uuid \
-	libsparse \
-	libz
-# LOCAL_LDLIBS := -ldl
-include $(BUILD_HOST_SHARED_LIBRARY)
+common_C_INCLUDES := $(LOCAL_PATH)/include \
+    external/e2fsprogs/lib/ \
+    system/core/libsparse/include \
 
 #----------------------------------------------------------
 include $(CLEAR_VARS)
@@ -73,81 +30,54 @@
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
 
 LOCAL_SRC_FILES := \
-	lib/libf2fs_io.c \
-	mkfs/f2fs_format_main.c
-LOCAL_C_INCLUDES := $(common_C_INCLUDES)
-LOCAL_CFLAGS := $(target_CFLAGS)
-LOCAL_STATIC_LIBRARIES := \
-	libf2fs_fmt \
-	libext2_uuid \
-	libsparse \
-	libz
-LOCAL_MODULE_TAGS := optional
-include $(BUILD_EXECUTABLE)
-
-#----------------------------------------------------------
-include $(CLEAR_VARS)
-LOCAL_MODULE := make_f2fs
-
-LOCAL_SRC_FILES := \
-	lib/libf2fs_io.c \
-	mkfs/f2fs_format_main.c
-LOCAL_C_INCLUDES := $(common_C_INCLUDES)
-LOCAL_CFLAGS := $(target_CFLAGS)
-LOCAL_STATIC_LIBRARIES := libf2fs_fmt
-LOCAL_SHARED_LIBRARIES := libext2_uuid libsparse
-LOCAL_SYSTEM_SHARED_LIBRARIES := libc
-LOCAL_MODULE_TAGS := optional
-include $(BUILD_EXECUTABLE)
-
-#----------------------------------------------------------
-include $(CLEAR_VARS)
-LOCAL_MODULE := make_f2fs
-
-LOCAL_SRC_FILES := \
-	mkfs/f2fs_format_main.c \
-	lib/libf2fs_io.c \
-
+    lib/libf2fs_io.c \
+    mkfs/f2fs_format_main.c
 LOCAL_C_INCLUDES := $(common_C_INCLUDES)
 LOCAL_CFLAGS := $(common_CFLAGS)
-LOCAL_STATIC_LIBRARIES := libf2fs_fmt_host
-LOCAL_SHARED_LIBRARIES := libext2_uuid libsparse
-include $(BUILD_HOST_EXECUTABLE)
+LOCAL_STATIC_LIBRARIES := \
+    libf2fs_fmt \
+    libext2_uuid \
+    libsparse \
+    libz
+LOCAL_WHOLE_STATIC_LIBRARIES := libbase
+include $(BUILD_EXECUTABLE)
 
 #----------------------------------------------------------
 include $(CLEAR_VARS)
 # The LOCAL_MODULE name is referenced by the code. Don't change it.
-LOCAL_MODULE := fsck.f2fs
-LOCAL_SRC_FILES := \
-	fsck/dump.c \
-	fsck/fsck.c \
-	fsck/main.c \
-	fsck/mount.c \
-	lib/libf2fs.c \
-	lib/libf2fs_io.c \
+LOCAL_MODULE := sload.f2fs
 
+# mkfs.f2fs is used in recovery: must be static.
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+
+LOCAL_SRC_FILES := \
+    fsck/fsck.c \
+    fsck/sload.c \
+    fsck/dir.c \
+    fsck/dict.c \
+    fsck/mkquota.c \
+    fsck/quotaio.c \
+    fsck/quotaio_tree.c \
+    fsck/quotaio_v2.c \
+    fsck/node.c \
+    fsck/segment.c \
+    fsck/xattr.c \
+    fsck/main.c \
+    fsck/mount.c \
+    lib/libf2fs.c \
+    lib/libf2fs_io.c
 LOCAL_C_INCLUDES := $(common_C_INCLUDES)
-LOCAL_CFLAGS := $(target_CFLAGS)
-LOCAL_SHARED_LIBRARIES := libext2_uuid libsparse
-LOCAL_SYSTEM_SHARED_LIBRARIES := libc
-LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := $(common_CFLAGS) -DWITH_SLOAD
+LOCAL_STATIC_LIBRARIES := \
+    libcutils \
+    libselinux \
+    libcrypto \
+    libsparse \
+    liblog \
+    libz
+LOCAL_WHOLE_STATIC_LIBRARIES := libbase
 include $(BUILD_EXECUTABLE)
 
-#----------------------------------------------------------
-include $(CLEAR_VARS)
-LOCAL_MODULE := fsck.f2fs
-LOCAL_SRC_FILES := \
-	fsck/dump.c \
-	fsck/fsck.c \
-	fsck/main.c \
-	fsck/mount.c \
-	lib/libf2fs.c \
-	lib/libf2fs_io.c \
-
-LOCAL_C_INCLUDES := $(common_C_INCLUDES)
-LOCAL_CFLAGS := $(common_CFLAGS)
-LOCAL_SHARED_LIBRARIES := libsparse
-LOCAL_HOST_SHARED_LIBRARIES :=  libext2_uuid
-include $(BUILD_HOST_EXECUTABLE)
-
 endif
diff --git a/VERSION b/VERSION
index c520af0..80d0337 100644
--- a/VERSION
+++ b/VERSION
@@ -1,2 +1,2 @@
-1.8.0
-2017-02-03
+1.10.0
+2018-01-30
diff --git a/configure.ac b/configure.ac
index d6de43b..a3ff12b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -35,16 +35,6 @@
 AC_CONFIG_AUX_DIR([build-aux])
 AM_INIT_AUTOMAKE([foreign tar-pax dist-xz])
 
-AC_CHECK_HEADERS_ONCE([
-	fcntl.h
-	mntent.h
-	stdlib.h
-	string.h
-	unistd.h
-	sys/ioctl.h
-	sys/mount.h
-])
-
 # Test configure options.
 AC_ARG_WITH([selinux],
 	AS_HELP_STRING([--without-selinux],
@@ -91,8 +81,31 @@
 )
 
 # Checks for header files.
-AC_CHECK_HEADERS([linux/fs.h linux/blkzoned.h fcntl.h mntent.h stdlib.h string.h \
-		sys/ioctl.h sys/mount.h unistd.h linux/falloc.h byteswap.h])
+AC_CHECK_HEADERS(m4_flatten([
+	attr/xattr.h
+	byteswap.h
+	fcntl.h
+	linux/blkzoned.h
+	linux/falloc.h
+	linux/fs.h
+	linux/hdreg.h
+	linux/limits.h
+	linux/posix_acl.h
+	linux/types.h
+	linux/xattr.h
+	mntent.h
+	scsi/sg.h
+	stdlib.h
+	string.h
+	sys/acl.h
+	sys/ioctl.h
+	sys/syscall.h
+	sys/mount.h
+	sys/sysmacros.h
+	sys/utsname.h
+	sys/xattr.h
+	unistd.h
+]))
 
 # Checks for typedefs, structures, and compiler characteristics.
 AC_C_INLINE
@@ -103,14 +116,75 @@
 # Checks for library functions.
 AC_FUNC_GETMNTENT
 AC_CHECK_FUNCS_ONCE([
+	add_key
 	fallocate
+	fsetxattr
+	fstat
+	fstat64
 	getmntent
+	keyctl
+	llseek
+	lseek64
 	memset
+	setmntent
 ])
 
 AS_IF([test "$ac_cv_header_byteswap_h" = "yes"],
       [AC_CHECK_DECLS([bswap_64],,,[#include <byteswap.h>])])
 
+dnl
+dnl Check to see if llseek() is declared in unistd.h.  On some libc's
+dnl it is, and on others it isn't..... Thank you glibc developers....
+dnl
+AC_CHECK_DECL(llseek,[AC_DEFINE(HAVE_LLSEEK_PROTOTYPE, 1,
+			[Define to 1 if llseek declared in unistd.h])],,
+			[#include <unistd.h>])
+dnl
+dnl Check to see if lseek64() is declared in unistd.h.  Glibc's header files
+dnl are so convoluted that I can't tell whether it will always be defined,
+dnl and if it isn't defined while lseek64 is defined in the library,
+dnl disaster will strike.
+dnl
+dnl Warning!  Use of --enable-gcc-wall may throw off this test.
+dnl
+dnl
+AC_CHECK_DECL(lseek64,[AC_DEFINE(HAVE_LSEEK64_PROTOTYPE, 1,
+		[Define to 1 if lseek64 declared in unistd.h])],,
+		[#define _LARGEFILE_SOURCE
+		#define _LARGEFILE64_SOURCE
+		#include <unistd.h>])
+dnl
+dnl Word sizes...
+dnl
+
+# AC_CANONICAL_HOST is needed to access the 'host_os' variable
+AC_CANONICAL_HOST
+
+build_linux=no
+build_windows=no
+build_mac=no
+
+# Detect the target system
+case "${host_os}" in
+linux*)
+	build_linux=yes
+	;;
+cygwin*|mingw*)
+	build_windows=yes
+	;;
+darwin*)
+	build_mac=yes
+	;;
+*)
+	AC_MSG_ERROR(["OS $host_os is not supported"])
+	;;
+esac
+
+# Pass the conditionals to automake
+AM_CONDITIONAL([LINUX], [test "$build_linux" = "yes"])
+AM_CONDITIONAL([WINDOWS], [test "$build_windows" = "yes"])
+AM_CONDITIONAL([OSX], [test "$build_mac" = "yes"])
+
 # Install directories
 #AC_PREFIX_DEFAULT([/usr])
 #AC_SUBST([sbindir], [/sbin])
@@ -123,15 +197,16 @@
 	mkfs/Makefile
 	fsck/Makefile
 	tools/Makefile
+	tools/sg_write_buffer/Makefile
 ])
 
 # export library version info for mkfs/libf2fs_format_la
-AC_SUBST(FMT_CURRENT, 1)
+AC_SUBST(FMT_CURRENT, 3)
 AC_SUBST(FMT_REVISION, 0)
 AC_SUBST(FMT_AGE, 0)
 
 # export library version info for lib/libf2fs_la
-AC_SUBST(LIBF2FS_CURRENT, 2)
+AC_SUBST(LIBF2FS_CURRENT, 4)
 AC_SUBST(LIBF2FS_REVISION, 0)
 AC_SUBST(LIBF2FS_AGE, 0)
 
diff --git a/fsck/Makefile.am b/fsck/Makefile.am
index 7abcd00..1fc7310 100644
--- a/fsck/Makefile.am
+++ b/fsck/Makefile.am
@@ -3,9 +3,11 @@
 AM_CPPFLAGS = ${libuuid_CFLAGS} -I$(top_srcdir)/include
 AM_CFLAGS = -Wall
 sbin_PROGRAMS = fsck.f2fs
-fsck_f2fs_SOURCES = main.c fsck.c dump.c mount.c defrag.c f2fs.h fsck.h $(top_srcdir)/include/f2fs_fs.h	\
-		resize.c										\
-		node.c segment.c dir.c sload.c xattr.c
+noinst_HEADERS = common.h dict.h dqblk_v2.h f2fs.h fsck.h node.h quotaio.h quotaio_tree.h quotaio_v2.h xattr.h
+include_HEADERS = $(top_srcdir)/include/quota.h
+fsck_f2fs_SOURCES = main.c fsck.c dump.c mount.c defrag.c resize.c \
+		node.c segment.c dir.c sload.c xattr.c \
+		dict.c mkquota.c quotaio.c quotaio_tree.c quotaio_v2.c
 fsck_f2fs_LDADD = ${libselinux_LIBS} ${libuuid_LIBS} $(top_builddir)/lib/libf2fs.la
 
 install-data-hook:
diff --git a/fsck/common.h b/fsck/common.h
new file mode 100644
index 0000000..19a6ecc
--- /dev/null
+++ b/fsck/common.h
@@ -0,0 +1,30 @@
+/**
+ *
+ * Various things common for all utilities
+ *
+ */
+
+#ifndef __QUOTA_COMMON_H__
+#define __QUOTA_COMMON_H__
+
+#undef DEBUG_QUOTA
+
+#ifndef __attribute__
+# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
+#  define __attribute__(x)
+# endif
+#endif
+
+#define log_err(format, arg ...)					\
+	fprintf(stderr, "[ERROR] %s:%d:%s:: " format "\n",		\
+		__FILE__, __LINE__, __func__, ## arg)
+
+#ifdef DEBUG_QUOTA
+# define log_debug(format, arg ...)					\
+	fprintf(stderr, "[DEBUG] %s:%d:%s:: " format "\n",		\
+		__FILE__, __LINE__, __func__, ## arg)
+#else
+# define log_debug(...)
+#endif
+
+#endif /* __QUOTA_COMMON_H__ */
diff --git a/fsck/dict.c b/fsck/dict.c
new file mode 100644
index 0000000..bb7600c
--- /dev/null
+++ b/fsck/dict.c
@@ -0,0 +1,1501 @@
+/*
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ * $Id: dict.c,v 1.40.2.7 2000/11/13 01:36:44 kaz Exp $
+ * $Name: kazlib_1_20 $
+ */
+
+#define DICT_NODEBUG
+
+#include "config.h"
+#include <stdlib.h>
+#include <stddef.h>
+#ifdef DICT_NODEBUG
+#define dict_assert(x)
+#else
+#include <assert.h>
+#define dict_assert(x) assert(x)
+#endif
+#define DICT_IMPLEMENTATION
+#include "dict.h"
+#include <f2fs_fs.h>
+
+#ifdef KAZLIB_RCSID
+static const char rcsid[] = "$Id: dict.c,v 1.40.2.7 2000/11/13 01:36:44 kaz Exp $";
+#endif
+
+/*
+ * These macros provide short convenient names for structure members,
+ * which are embellished with dict_ prefixes so that they are
+ * properly confined to the documented namespace. It's legal for a
+ * program which uses dict to define, for instance, a macro called ``parent''.
+ * Such a macro would interfere with the dnode_t struct definition.
+ * In general, highly portable and reusable C modules which expose their
+ * structures need to confine structure member names to well-defined spaces.
+ * The resulting identifiers aren't necessarily convenient to use, nor
+ * readable, in the implementation, however!
+ */
+
+#define left dict_left
+#define right dict_right
+#define parent dict_parent
+#define color dict_color
+#define key dict_key
+#define data dict_data
+
+#define nilnode dict_nilnode
+#define nodecount dict_nodecount
+#define maxcount dict_maxcount
+#define compare dict_compare
+#define allocnode dict_allocnode
+#define freenode dict_freenode
+#define context dict_context
+#define dupes dict_dupes
+
+#define dictptr dict_dictptr
+
+#define dict_root(D) ((D)->nilnode.left)
+#define dict_nil(D) (&(D)->nilnode)
+#define DICT_DEPTH_MAX 64
+
+static dnode_t *dnode_alloc(void *context);
+static void dnode_free(dnode_t *node, void *context);
+
+/*
+ * Perform a ``left rotation'' adjustment on the tree.  The given node P and
+ * its right child C are rearranged so that the P instead becomes the left
+ * child of C.   The left subtree of C is inherited as the new right subtree
+ * for P.  The ordering of the keys within the tree is thus preserved.
+ */
+static void rotate_left(dnode_t *upper)
+{
+	dnode_t *lower, *lowleft, *upparent;
+
+	lower = upper->right;
+	upper->right = lowleft = lower->left;
+	lowleft->parent = upper;
+
+	lower->parent = upparent = upper->parent;
+
+	/* don't need to check for root node here because root->parent is
+	   the sentinel nil node, and root->parent->left points back to root */
+
+	if (upper == upparent->left) {
+		upparent->left = lower;
+	} else {
+		dict_assert(upper == upparent->right);
+		upparent->right = lower;
+	}
+
+	lower->left = upper;
+	upper->parent = lower;
+}
+
+/*
+ * This operation is the ``mirror'' image of rotate_left. It is
+ * the same procedure, but with left and right interchanged.
+ */
+static void rotate_right(dnode_t *upper)
+{
+	dnode_t *lower, *lowright, *upparent;
+
+	lower = upper->left;
+	upper->left = lowright = lower->right;
+	lowright->parent = upper;
+
+	lower->parent = upparent = upper->parent;
+
+	if (upper == upparent->right) {
+		upparent->right = lower;
+	} else {
+		dict_assert(upper == upparent->left);
+		upparent->left = lower;
+	}
+
+	lower->right = upper;
+	upper->parent = lower;
+}
+
+/*
+ * Do a postorder traversal of the tree rooted at the specified
+ * node and free everything under it.  Used by dict_free().
+ */
+static void free_nodes(dict_t *dict, dnode_t *node, dnode_t *nil)
+{
+	if (node == nil)
+		return;
+	free_nodes(dict, node->left, nil);
+	free_nodes(dict, node->right, nil);
+	dict->freenode(node, dict->context);
+}
+
+/*
+ * This procedure performs a verification that the given subtree is a binary
+ * search tree. It performs an inorder traversal of the tree using the
+ * dict_next() successor function, verifying that the key of each node is
+ * strictly lower than that of its successor, if duplicates are not allowed,
+ * or lower or equal if duplicates are allowed.  This function is used for
+ * debugging purposes.
+ */
+#ifndef DICT_NODEBUG
+static int verify_bintree(dict_t *dict)
+{
+	dnode_t *first, *next;
+
+	first = dict_first(dict);
+
+	if (dict->dupes) {
+		while (first && (next = dict_next(dict, first))) {
+			if (dict->compare(first->key, next->key) > 0)
+				return 0;
+			first = next;
+		}
+	} else {
+		while (first && (next = dict_next(dict, first))) {
+			if (dict->compare(first->key, next->key) >= 0)
+				return 0;
+			first = next;
+		}
+	}
+	return 1;
+}
+
+/*
+ * This function recursively verifies that the given binary subtree satisfies
+ * three of the red black properties. It checks that every red node has only
+ * black children. It makes sure that each node is either red or black. And it
+ * checks that every path has the same count of black nodes from root to leaf.
+ * It returns the blackheight of the given subtree; this allows blackheights to
+ * be computed recursively and compared for left and right siblings for
+ * mismatches. It does not check for every nil node being black, because there
+ * is only one sentinel nil node. The return value of this function is the
+ * black height of the subtree rooted at the node ``root'', or zero if the
+ * subtree is not red-black.
+ */
+static unsigned int verify_redblack(dnode_t *nil, dnode_t *root)
+{
+	unsigned height_left, height_right;
+
+	if (root != nil) {
+		height_left = verify_redblack(nil, root->left);
+		height_right = verify_redblack(nil, root->right);
+		if (height_left == 0 || height_right == 0)
+			return 0;
+		if (height_left != height_right)
+			return 0;
+		if (root->color == dnode_red) {
+			if (root->left->color != dnode_black)
+				return 0;
+			if (root->right->color != dnode_black)
+				return 0;
+			return height_left;
+		}
+		if (root->color != dnode_black)
+			return 0;
+		return height_left + 1;
+	}
+	return 1;
+}
+
+/*
+ * Compute the actual count of nodes by traversing the tree and
+ * return it. This could be compared against the stored count to
+ * detect a mismatch.
+ */
+static dictcount_t verify_node_count(dnode_t *nil, dnode_t *root)
+{
+	if (root == nil)
+		return 0;
+	else
+		return 1 + verify_node_count(nil, root->left)
+			+ verify_node_count(nil, root->right);
+}
+#endif
+
+/*
+ * Verify that the tree contains the given node. This is done by
+ * traversing all of the nodes and comparing their pointers to the
+ * given pointer. Returns 1 if the node is found, otherwise
+ * returns zero. It is intended for debugging purposes.
+ */
+static int verify_dict_has_node(dnode_t *nil, dnode_t *root, dnode_t *node)
+{
+	if (root != nil) {
+		return root == node
+			|| verify_dict_has_node(nil, root->left, node)
+			|| verify_dict_has_node(nil, root->right, node);
+	}
+	return 0;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Dynamically allocate and initialize a dictionary object.
+ */
+dict_t *dict_create(dictcount_t maxcount, dict_comp_t comp)
+{
+	dict_t *new = malloc(sizeof *new);
+
+	if (new) {
+		new->compare = comp;
+		new->allocnode = dnode_alloc;
+		new->freenode = dnode_free;
+		new->context = NULL;
+		new->nodecount = 0;
+		new->maxcount = maxcount;
+		new->nilnode.left = &new->nilnode;
+		new->nilnode.right = &new->nilnode;
+		new->nilnode.parent = &new->nilnode;
+		new->nilnode.color = dnode_black;
+		new->dupes = 0;
+	}
+	return new;
+}
+#endif /* FSCK_NOTUSED */
+
+/*
+ * Select a different set of node allocator routines.
+ */
+void dict_set_allocator(dict_t *dict, dnode_alloc_t al,
+		dnode_free_t fr, void *context)
+{
+	dict_assert(dict_count(dict) == 0);
+	dict_assert((al == NULL && fr == NULL) || (al != NULL && fr != NULL));
+
+	dict->allocnode = al ? al : dnode_alloc;
+	dict->freenode = fr ? fr : dnode_free;
+	dict->context = context;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Free a dynamically allocated dictionary object. Removing the nodes
+ * from the tree before deleting it is required.
+ */
+void dict_destroy(dict_t *dict)
+{
+	dict_assert(dict_isempty(dict));
+	free(dict);
+}
+#endif
+
+/*
+ * Free all the nodes in the dictionary by using the dictionary's
+ * installed free routine. The dictionary is emptied.
+ */
+void dict_free_nodes(dict_t *dict)
+{
+	dnode_t *nil = dict_nil(dict), *root = dict_root(dict);
+	free_nodes(dict, root, nil);
+	dict->nodecount = 0;
+	dict->nilnode.left = &dict->nilnode;
+	dict->nilnode.right = &dict->nilnode;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Obsolescent function, equivalent to dict_free_nodes
+ */
+void dict_free(dict_t *dict)
+{
+#ifdef KAZLIB_OBSOLESCENT_DEBUG
+	dict_assert("call to obsolescent function dict_free()" && 0);
+#endif
+	dict_free_nodes(dict);
+}
+#endif
+
+/*
+ * Initialize a user-supplied dictionary object.
+ */
+dict_t *dict_init(dict_t *dict, dictcount_t maxcount, dict_comp_t comp)
+{
+	dict->compare = comp;
+	dict->allocnode = dnode_alloc;
+	dict->freenode = dnode_free;
+	dict->context = NULL;
+	dict->nodecount = 0;
+	dict->maxcount = maxcount;
+	dict->nilnode.left = &dict->nilnode;
+	dict->nilnode.right = &dict->nilnode;
+	dict->nilnode.parent = &dict->nilnode;
+	dict->nilnode.color = dnode_black;
+	dict->dupes = 0;
+	return dict;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Initialize a dictionary in the likeness of another dictionary
+ */
+void dict_init_like(dict_t *dict, const dict_t *template)
+{
+	dict->compare = template->compare;
+	dict->allocnode = template->allocnode;
+	dict->freenode = template->freenode;
+	dict->context = template->context;
+	dict->nodecount = 0;
+	dict->maxcount = template->maxcount;
+	dict->nilnode.left = &dict->nilnode;
+	dict->nilnode.right = &dict->nilnode;
+	dict->nilnode.parent = &dict->nilnode;
+	dict->nilnode.color = dnode_black;
+	dict->dupes = template->dupes;
+
+	dict_assert(dict_similar(dict, template));
+}
+
+/*
+ * Remove all nodes from the dictionary (without freeing them in any way).
+ */
+static void dict_clear(dict_t *dict)
+{
+	dict->nodecount = 0;
+	dict->nilnode.left = &dict->nilnode;
+	dict->nilnode.right = &dict->nilnode;
+	dict->nilnode.parent = &dict->nilnode;
+	dict_assert(dict->nilnode.color == dnode_black);
+}
+#endif /* FSCK_NOTUSED */
+
+/*
+ * Verify the integrity of the dictionary structure.  This is provided for
+ * debugging purposes, and should be placed in assert statements.   Just because
+ * this function succeeds doesn't mean that the tree is not corrupt. Certain
+ * corruptions in the tree may simply cause undefined behavior.
+ */
+#ifndef DICT_NODEBUG
+int dict_verify(dict_t *dict)
+{
+	dnode_t *nil = dict_nil(dict), *root = dict_root(dict);
+
+	/* check that the sentinel node and root node are black */
+	if (root->color != dnode_black)
+		return 0;
+	if (nil->color != dnode_black)
+		return 0;
+	if (nil->right != nil)
+		return 0;
+	/* nil->left is the root node; check that its parent pointer is nil */
+	if (nil->left->parent != nil)
+		return 0;
+	/* perform a weak test that the tree is a binary search tree */
+	if (!verify_bintree(dict))
+		return 0;
+	/* verify that the tree is a red-black tree */
+	if (!verify_redblack(nil, root))
+		return 0;
+	if (verify_node_count(nil, root) != dict_count(dict))
+		return 0;
+	return 1;
+}
+#endif /* DICT_NODEBUG */
+
+#ifdef FSCK_NOTUSED
+/*
+ * Determine whether two dictionaries are similar: have the same comparison and
+ * allocator functions, and same status as to whether duplicates are allowed.
+ */
+int dict_similar(const dict_t *left, const dict_t *right)
+{
+	if (left->compare != right->compare)
+		return 0;
+
+	if (left->allocnode != right->allocnode)
+		return 0;
+
+	if (left->freenode != right->freenode)
+		return 0;
+
+	if (left->context != right->context)
+		return 0;
+
+	if (left->dupes != right->dupes)
+		return 0;
+
+	return 1;
+}
+#endif /* FSCK_NOTUSED */
+
+/*
+ * Locate a node in the dictionary having the given key.
+ * If the node is not found, a null a pointer is returned (rather than
+ * a pointer that dictionary's nil sentinel node), otherwise a pointer to the
+ * located node is returned.
+ */
+dnode_t *dict_lookup(dict_t *dict, const void *key)
+{
+	dnode_t *root = dict_root(dict);
+	dnode_t *nil = dict_nil(dict);
+	dnode_t *saved;
+	int result;
+
+	/* simple binary search adapted for trees that contain duplicate keys */
+
+	while (root != nil) {
+		result = dict->compare(key, root->key);
+		if (result < 0)
+			root = root->left;
+		else if (result > 0)
+			root = root->right;
+		else {
+			if (!dict->dupes) {	/* no duplicates, return match		*/
+				return root;
+			} else {		/* could be dupes, find leftmost one	*/
+				do {
+					saved = root;
+					root = root->left;
+					while (root != nil && dict->compare(key, root->key))
+						root = root->right;
+				} while (root != nil);
+				return saved;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Look for the node corresponding to the lowest key that is equal to or
+ * greater than the given key.  If there is no such node, return null.
+ */
+dnode_t *dict_lower_bound(dict_t *dict, const void *key)
+{
+	dnode_t *root = dict_root(dict);
+	dnode_t *nil = dict_nil(dict);
+	dnode_t *tentative = 0;
+
+	while (root != nil) {
+		int result = dict->compare(key, root->key);
+
+		if (result > 0) {
+			root = root->right;
+		} else if (result < 0) {
+			tentative = root;
+			root = root->left;
+		} else {
+			if (!dict->dupes) {
+				return root;
+			} else {
+				tentative = root;
+				root = root->left;
+			}
+		}
+	}
+
+	return tentative;
+}
+
+/*
+ * Look for the node corresponding to the greatest key that is equal to or
+ * lower than the given key.  If there is no such node, return null.
+ */
+dnode_t *dict_upper_bound(dict_t *dict, const void *key)
+{
+	dnode_t *root = dict_root(dict);
+	dnode_t *nil = dict_nil(dict);
+	dnode_t *tentative = 0;
+
+	while (root != nil) {
+		int result = dict->compare(key, root->key);
+
+		if (result < 0) {
+			root = root->left;
+		} else if (result > 0) {
+			tentative = root;
+			root = root->right;
+		} else {
+			if (!dict->dupes) {
+				return root;
+			} else {
+				tentative = root;
+				root = root->right;
+			}
+		}
+	}
+
+	return tentative;
+}
+#endif
+
+/*
+ * Insert a node into the dictionary. The node should have been
+ * initialized with a data field. All other fields are ignored.
+ * The behavior is undefined if the user attempts to insert into
+ * a dictionary that is already full (for which the dict_isfull()
+ * function returns true).
+ */
+void dict_insert(dict_t *dict, dnode_t *node, const void *key)
+{
+	dnode_t *where = dict_root(dict), *nil = dict_nil(dict);
+	dnode_t *parent = nil, *uncle, *grandpa;
+	int result = -1;
+
+	node->key = key;
+
+	dict_assert(!dict_isfull(dict));
+	dict_assert(!dict_contains(dict, node));
+	dict_assert(!dnode_is_in_a_dict(node));
+
+	/* basic binary tree insert */
+
+	while (where != nil) {
+		parent = where;
+		result = dict->compare(key, where->key);
+		/* trap attempts at duplicate key insertion unless it's explicitly allowed */
+		dict_assert(dict->dupes || result != 0);
+		if (result < 0)
+			where = where->left;
+		else
+			where = where->right;
+	}
+
+	dict_assert(where == nil);
+
+	if (result < 0)
+		parent->left = node;
+	else
+		parent->right = node;
+
+	node->parent = parent;
+	node->left = nil;
+	node->right = nil;
+
+	dict->nodecount++;
+
+	/* red black adjustments */
+
+	node->color = dnode_red;
+
+	while (parent->color == dnode_red) {
+		grandpa = parent->parent;
+		if (parent == grandpa->left) {
+			uncle = grandpa->right;
+			if (uncle->color == dnode_red) {	/* red parent, red uncle */
+				parent->color = dnode_black;
+				uncle->color = dnode_black;
+				grandpa->color = dnode_red;
+				node = grandpa;
+				parent = grandpa->parent;
+			} else {				/* red parent, black uncle */
+				if (node == parent->right) {
+					rotate_left(parent);
+					parent = node;
+					dict_assert(grandpa == parent->parent);
+					/* rotation between parent and child preserves grandpa */
+				}
+				parent->color = dnode_black;
+				grandpa->color = dnode_red;
+				rotate_right(grandpa);
+				break;
+			}
+		} else { 	/* symmetric cases: parent == parent->parent->right */
+			uncle = grandpa->left;
+			if (uncle->color == dnode_red) {
+				parent->color = dnode_black;
+				uncle->color = dnode_black;
+				grandpa->color = dnode_red;
+				node = grandpa;
+				parent = grandpa->parent;
+			} else {
+				if (node == parent->left) {
+					rotate_right(parent);
+					parent = node;
+					dict_assert(grandpa == parent->parent);
+				}
+				parent->color = dnode_black;
+				grandpa->color = dnode_red;
+				rotate_left(grandpa);
+				break;
+			}
+		}
+	}
+
+	dict_root(dict)->color = dnode_black;
+
+	dict_assert(dict_verify(dict));
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Delete the given node from the dictionary. If the given node does not belong
+ * to the given dictionary, undefined behavior results.  A pointer to the
+ * deleted node is returned.
+ */
+dnode_t *dict_delete(dict_t *dict, dnode_t *delete)
+{
+	dnode_t *nil = dict_nil(dict), *child, *delparent = delete->parent;
+
+	/* basic deletion */
+
+	dict_assert(!dict_isempty(dict));
+	dict_assert(dict_contains(dict, delete));
+
+	/*
+	 * If the node being deleted has two children, then we replace it with its
+	 * successor (i.e. the leftmost node in the right subtree.) By doing this,
+	 * we avoid the traditional algorithm under which the successor's key and
+	 * value *only* move to the deleted node and the successor is spliced out
+	 * from the tree. We cannot use this approach because the user may hold
+	 * pointers to the successor, or nodes may be inextricably tied to some
+	 * other structures by way of embedding, etc. So we must splice out the
+	 * node we are given, not some other node, and must not move contents from
+	 * one node to another behind the user's back.
+	 */
+
+	if (delete->left != nil && delete->right != nil) {
+		dnode_t *next = dict_next(dict, delete);
+		dnode_t *nextparent = next->parent;
+		dnode_color_t nextcolor = next->color;
+
+		dict_assert(next != nil);
+		dict_assert(next->parent != nil);
+		dict_assert(next->left == nil);
+
+		/*
+		 * First, splice out the successor from the tree completely, by
+		 * moving up its right child into its place.
+		 */
+
+		child = next->right;
+		child->parent = nextparent;
+
+		if (nextparent->left == next) {
+			nextparent->left = child;
+		} else {
+			dict_assert(nextparent->right == next);
+			nextparent->right = child;
+		}
+
+		/*
+		 * Now that the successor has been extricated from the tree, install it
+		 * in place of the node that we want deleted.
+		 */
+
+		next->parent = delparent;
+		next->left = delete->left;
+		next->right = delete->right;
+		next->left->parent = next;
+		next->right->parent = next;
+		next->color = delete->color;
+		delete->color = nextcolor;
+
+		if (delparent->left == delete) {
+			delparent->left = next;
+		} else {
+			dict_assert(delparent->right == delete);
+			delparent->right = next;
+		}
+
+	} else {
+		dict_assert(delete != nil);
+		dict_assert(delete->left == nil || delete->right == nil);
+
+		child = (delete->left != nil) ? delete->left : delete->right;
+
+		child->parent = delparent = delete->parent;
+
+		if (delete == delparent->left) {
+			delparent->left = child;
+		} else {
+			dict_assert(delete == delparent->right);
+			delparent->right = child;
+		}
+	}
+
+	delete->parent = NULL;
+	delete->right = NULL;
+	delete->left = NULL;
+
+	dict->nodecount--;
+
+	dict_assert(verify_bintree(dict));
+
+	/* red-black adjustments */
+
+	if (delete->color == dnode_black) {
+		dnode_t *parent, *sister;
+
+		dict_root(dict)->color = dnode_red;
+
+		while (child->color == dnode_black) {
+			parent = child->parent;
+			if (child == parent->left) {
+				sister = parent->right;
+				dict_assert(sister != nil);
+				if (sister->color == dnode_red) {
+					sister->color = dnode_black;
+					parent->color = dnode_red;
+					rotate_left(parent);
+					sister = parent->right;
+					dict_assert(sister != nil);
+				}
+				if (sister->left->color == dnode_black
+						&& sister->right->color == dnode_black) {
+					sister->color = dnode_red;
+					child = parent;
+				} else {
+					if (sister->right->color == dnode_black) {
+						dict_assert(sister->left->color == dnode_red);
+						sister->left->color = dnode_black;
+						sister->color = dnode_red;
+						rotate_right(sister);
+						sister = parent->right;
+						dict_assert(sister != nil);
+					}
+					sister->color = parent->color;
+					sister->right->color = dnode_black;
+					parent->color = dnode_black;
+					rotate_left(parent);
+					break;
+				}
+			} else {	/* symmetric case: child == child->parent->right */
+				dict_assert(child == parent->right);
+				sister = parent->left;
+				dict_assert(sister != nil);
+				if (sister->color == dnode_red) {
+					sister->color = dnode_black;
+					parent->color = dnode_red;
+					rotate_right(parent);
+					sister = parent->left;
+					dict_assert(sister != nil);
+				}
+				if (sister->right->color == dnode_black
+						&& sister->left->color == dnode_black) {
+					sister->color = dnode_red;
+					child = parent;
+				} else {
+					if (sister->left->color == dnode_black) {
+						dict_assert(sister->right->color == dnode_red);
+						sister->right->color = dnode_black;
+						sister->color = dnode_red;
+						rotate_left(sister);
+						sister = parent->left;
+						dict_assert(sister != nil);
+					}
+					sister->color = parent->color;
+					sister->left->color = dnode_black;
+					parent->color = dnode_black;
+					rotate_right(parent);
+					break;
+				}
+			}
+		}
+
+		child->color = dnode_black;
+		dict_root(dict)->color = dnode_black;
+	}
+
+	dict_assert(dict_verify(dict));
+
+	return delete;
+}
+#endif /* FSCK_NOTUSED */
+
+/*
+ * Allocate a node using the dictionary's allocator routine, give it
+ * the data item.
+ */
+int dict_alloc_insert(dict_t *dict, const void *key, void *data)
+{
+	dnode_t *node = dict->allocnode(dict->context);
+
+	if (node) {
+		dnode_init(node, data);
+		dict_insert(dict, node, key);
+		return 1;
+	}
+	return 0;
+}
+
+#ifdef FSCK_NOTUSED
+void dict_delete_free(dict_t *dict, dnode_t *node)
+{
+	dict_delete(dict, node);
+	dict->freenode(node, dict->context);
+}
+#endif
+
+/*
+ * Return the node with the lowest (leftmost) key. If the dictionary is empty
+ * (that is, dict_isempty(dict) returns 1) a null pointer is returned.
+ */
+dnode_t *dict_first(dict_t *dict)
+{
+	dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *left;
+
+	if (root != nil)
+		while ((left = root->left) != nil)
+			root = left;
+
+	return (root == nil) ? NULL : root;
+}
+
+/*
+ * Return the node with the highest (rightmost) key. If the dictionary is empty
+ * (that is, dict_isempty(dict) returns 1) a null pointer is returned.
+ */
+dnode_t *dict_last(dict_t *dict)
+{
+	dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *right;
+
+	if (root != nil)
+		while ((right = root->right) != nil)
+			root = right;
+
+	return (root == nil) ? NULL : root;
+}
+
+/*
+ * Return the given node's successor node---the node which has the
+ * next key in the the left to right ordering. If the node has
+ * no successor, a null pointer is returned rather than a pointer to
+ * the nil node.
+ */
+dnode_t *dict_next(dict_t *dict, dnode_t *curr)
+{
+	dnode_t *nil = dict_nil(dict), *parent, *left;
+
+	if (curr->right != nil) {
+		curr = curr->right;
+		while ((left = curr->left) != nil)
+			curr = left;
+		return curr;
+	}
+
+	parent = curr->parent;
+
+	while (parent != nil && curr == parent->right) {
+		curr = parent;
+		parent = curr->parent;
+	}
+
+	return (parent == nil) ? NULL : parent;
+}
+
+/*
+ * Return the given node's predecessor, in the key order.
+ * The nil sentinel node is returned if there is no predecessor.
+ */
+dnode_t *dict_prev(dict_t *dict, dnode_t *curr)
+{
+	dnode_t *nil = dict_nil(dict), *parent, *right;
+
+	if (curr->left != nil) {
+		curr = curr->left;
+		while ((right = curr->right) != nil)
+			curr = right;
+		return curr;
+	}
+
+	parent = curr->parent;
+
+	while (parent != nil && curr == parent->left) {
+		curr = parent;
+		parent = curr->parent;
+	}
+
+	return (parent == nil) ? NULL : parent;
+}
+
+void dict_allow_dupes(dict_t *dict)
+{
+	dict->dupes = 1;
+}
+
+#undef dict_count
+#undef dict_isempty
+#undef dict_isfull
+#undef dnode_get
+#undef dnode_put
+#undef dnode_getkey
+
+dictcount_t dict_count(dict_t *dict)
+{
+	return dict->nodecount;
+}
+
+int dict_isempty(dict_t *dict)
+{
+	return dict->nodecount == 0;
+}
+
+int dict_isfull(dict_t *dict)
+{
+	return dict->nodecount == dict->maxcount;
+}
+
+int dict_contains(dict_t *dict, dnode_t *node)
+{
+	return verify_dict_has_node(dict_nil(dict), dict_root(dict), node);
+}
+
+static dnode_t *dnode_alloc(void *UNUSED(context))
+{
+	return malloc(sizeof *dnode_alloc(NULL));
+}
+
+static void dnode_free(dnode_t *node, void *UNUSED(context))
+{
+	free(node);
+}
+
+dnode_t *dnode_create(void *data)
+{
+	dnode_t *new = malloc(sizeof *new);
+	if (new) {
+		new->data = data;
+		new->parent = NULL;
+		new->left = NULL;
+		new->right = NULL;
+	}
+	return new;
+}
+
+dnode_t *dnode_init(dnode_t *dnode, void *data)
+{
+	dnode->data = data;
+	dnode->parent = NULL;
+	dnode->left = NULL;
+	dnode->right = NULL;
+	return dnode;
+}
+
+void dnode_destroy(dnode_t *dnode)
+{
+	dict_assert(!dnode_is_in_a_dict(dnode));
+	free(dnode);
+}
+
+void *dnode_get(dnode_t *dnode)
+{
+	return dnode->data;
+}
+
+const void *dnode_getkey(dnode_t *dnode)
+{
+	return dnode->key;
+}
+
+#ifdef FSCK_NOTUSED
+void dnode_put(dnode_t *dnode, void *data)
+{
+	dnode->data = data;
+}
+#endif
+
+#ifndef DICT_NODEBUG
+int dnode_is_in_a_dict(dnode_t *dnode)
+{
+	return (dnode->parent && dnode->left && dnode->right);
+}
+#endif
+
+#ifdef FSCK_NOTUSED
+void dict_process(dict_t *dict, void *context, dnode_process_t function)
+{
+	dnode_t *node = dict_first(dict), *next;
+
+	while (node != NULL) {
+		/* check for callback function deleting	*/
+		/* the next node from under us		*/
+		dict_assert(dict_contains(dict, node));
+		next = dict_next(dict, node);
+		function(dict, node, context);
+		node = next;
+	}
+}
+
+static void load_begin_internal(dict_load_t *load, dict_t *dict)
+{
+	load->dictptr = dict;
+	load->nilnode.left = &load->nilnode;
+	load->nilnode.right = &load->nilnode;
+}
+
+void dict_load_begin(dict_load_t *load, dict_t *dict)
+{
+	dict_assert(dict_isempty(dict));
+	load_begin_internal(load, dict);
+}
+
+void dict_load_next(dict_load_t *load, dnode_t *newnode, const void *key)
+{
+	dict_t *dict = load->dictptr;
+	dnode_t *nil = &load->nilnode;
+
+	dict_assert(!dnode_is_in_a_dict(newnode));
+	dict_assert(dict->nodecount < dict->maxcount);
+
+#ifndef DICT_NODEBUG
+	if (dict->nodecount > 0) {
+		if (dict->dupes)
+			dict_assert(dict->compare(nil->left->key, key) <= 0);
+		else
+			dict_assert(dict->compare(nil->left->key, key) < 0);
+	}
+#endif
+
+	newnode->key = key;
+	nil->right->left = newnode;
+	nil->right = newnode;
+	newnode->left = nil;
+	dict->nodecount++;
+}
+
+void dict_load_end(dict_load_t *load)
+{
+	dict_t *dict = load->dictptr;
+	dnode_t *tree[DICT_DEPTH_MAX] = { 0 };
+	dnode_t *curr, *dictnil = dict_nil(dict), *loadnil = &load->nilnode, *next;
+	dnode_t *complete = 0;
+	dictcount_t fullcount = DICTCOUNT_T_MAX, nodecount = dict->nodecount;
+	dictcount_t botrowcount;
+	unsigned baselevel = 0, level = 0, i;
+
+	dict_assert(dnode_red == 0 && dnode_black == 1);
+
+	while (fullcount >= nodecount && fullcount)
+		fullcount >>= 1;
+
+	botrowcount = nodecount - fullcount;
+
+	for (curr = loadnil->left; curr != loadnil; curr = next) {
+		next = curr->left;
+
+		if (complete == NULL && botrowcount-- == 0) {
+			dict_assert(baselevel == 0);
+			dict_assert(level == 0);
+			baselevel = level = 1;
+			complete = tree[0];
+
+			if (complete != 0) {
+				tree[0] = 0;
+				complete->right = dictnil;
+				while (tree[level] != 0) {
+					tree[level]->right = complete;
+					complete->parent = tree[level];
+					complete = tree[level];
+					tree[level++] = 0;
+				}
+			}
+		}
+
+		if (complete == NULL) {
+			curr->left = dictnil;
+			curr->right = dictnil;
+			curr->color = level % 2;
+			complete = curr;
+
+			dict_assert(level == baselevel);
+			while (tree[level] != 0) {
+				tree[level]->right = complete;
+				complete->parent = tree[level];
+				complete = tree[level];
+				tree[level++] = 0;
+			}
+		} else {
+			curr->left = complete;
+			curr->color = (level + 1) % 2;
+			complete->parent = curr;
+			tree[level] = curr;
+			complete = 0;
+			level = baselevel;
+		}
+	}
+
+	if (complete == NULL)
+		complete = dictnil;
+
+	for (i = 0; i < DICT_DEPTH_MAX; i++) {
+		if (tree[i] != 0) {
+			tree[i]->right = complete;
+			complete->parent = tree[i];
+			complete = tree[i];
+		}
+	}
+
+	dictnil->color = dnode_black;
+	dictnil->right = dictnil;
+	complete->parent = dictnil;
+	complete->color = dnode_black;
+	dict_root(dict) = complete;
+
+	dict_assert(dict_verify(dict));
+}
+
+void dict_merge(dict_t *dest, dict_t *source)
+{
+	dict_load_t load;
+	dnode_t *leftnode = dict_first(dest), *rightnode = dict_first(source);
+
+	dict_assert(dict_similar(dest, source));
+
+	if (source == dest)
+		return;
+
+	dest->nodecount = 0;
+	load_begin_internal(&load, dest);
+
+	for (;;) {
+		if (leftnode != NULL && rightnode != NULL) {
+			if (dest->compare(leftnode->key, rightnode->key) < 0)
+				goto copyleft;
+			else
+				goto copyright;
+		} else if (leftnode != NULL) {
+			goto copyleft;
+		} else if (rightnode != NULL) {
+			goto copyright;
+		} else {
+			dict_assert(leftnode == NULL && rightnode == NULL);
+			break;
+		}
+
+copyleft:
+		{
+			dnode_t *next = dict_next(dest, leftnode);
+#ifndef DICT_NODEBUG
+			leftnode->left = NULL;	/* suppress assertion in dict_load_next */
+#endif
+			dict_load_next(&load, leftnode, leftnode->key);
+			leftnode = next;
+			continue;
+		}
+
+copyright:
+		{
+			dnode_t *next = dict_next(source, rightnode);
+#ifndef DICT_NODEBUG
+			rightnode->left = NULL;
+#endif
+			dict_load_next(&load, rightnode, rightnode->key);
+			rightnode = next;
+			continue;
+		}
+	}
+
+	dict_clear(source);
+	dict_load_end(&load);
+}
+#endif /* FSCK_NOTUSED */
+
+#ifdef KAZLIB_TEST_MAIN
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+typedef char input_t[256];
+
+static int tokenize(char *string, ...)
+{
+	char **tokptr;
+	va_list arglist;
+	int tokcount = 0;
+
+	va_start(arglist, string);
+	tokptr = va_arg(arglist, char **);
+	while (tokptr) {
+		while (*string && isspace((unsigned char) *string))
+			string++;
+		if (!*string)
+			break;
+		*tokptr = string;
+		while (*string && !isspace((unsigned char) *string))
+			string++;
+		tokptr = va_arg(arglist, char **);
+		tokcount++;
+		if (!*string)
+			break;
+		*string++ = 0;
+	}
+	va_end(arglist);
+
+	return tokcount;
+}
+
+static int comparef(const void *key1, const void *key2)
+{
+	return strcmp(key1, key2);
+}
+
+static char *dupstring(char *str)
+{
+	int sz = strlen(str) + 1;
+	char *new = malloc(sz);
+	if (new)
+		memcpy(new, str, sz);
+	return new;
+}
+
+static dnode_t *new_node(void *c)
+{
+	static dnode_t few[5];
+	static int count;
+
+	if (count < 5)
+		return few + count++;
+
+	return NULL;
+}
+
+static void del_node(dnode_t *n, void *c)
+{
+}
+
+static int prompt = 0;
+
+static void construct(dict_t *d)
+{
+	input_t in;
+	int done = 0;
+	dict_load_t dl;
+	dnode_t *dn;
+	char *tok1, *tok2, *val;
+	const char *key;
+	char *help =
+		"p                      turn prompt on\n"
+		"q                      finish construction\n"
+		"a <key> <val>          add new entry\n";
+
+	if (!dict_isempty(d))
+		puts("warning: dictionary not empty!");
+
+	dict_load_begin(&dl, d);
+
+	while (!done) {
+		if (prompt)
+			putchar('>');
+		fflush(stdout);
+
+		if (!fgets(in, sizeof(input_t), stdin))
+			break;
+
+		switch (in[0]) {
+			case '?':
+				puts(help);
+				break;
+			case 'p':
+				prompt = 1;
+				break;
+			case 'q':
+				done = 1;
+				break;
+			case 'a':
+				if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+					puts("what?");
+					break;
+				}
+				key = dupstring(tok1);
+				val = dupstring(tok2);
+				dn = dnode_create(val);
+
+				if (!key || !val || !dn) {
+					puts("out of memory");
+					free((void *) key);
+					free(val);
+					if (dn)
+						dnode_destroy(dn);
+				}
+
+				dict_load_next(&dl, dn, key);
+				break;
+			default:
+				putchar('?');
+				putchar('\n');
+				break;
+		}
+	}
+
+	dict_load_end(&dl);
+}
+
+int main(void)
+{
+	input_t in;
+	dict_t darray[10];
+	dict_t *d = &darray[0];
+	dnode_t *dn;
+	int i;
+	char *tok1, *tok2, *val;
+	const char *key;
+
+	char *help =
+		"a <key> <val>          add value to dictionary\n"
+		"d <key>                delete value from dictionary\n"
+		"l <key>                lookup value in dictionary\n"
+		"( <key>                lookup lower bound\n"
+		") <key>                lookup upper bound\n"
+		"# <num>                switch to alternate dictionary (0-9)\n"
+		"j <num> <num>          merge two dictionaries\n"
+		"f                      free the whole dictionary\n"
+		"k                      allow duplicate keys\n"
+		"c                      show number of entries\n"
+		"t                      dump whole dictionary in sort order\n"
+		"m                      make dictionary out of sorted items\n"
+		"p                      turn prompt on\n"
+		"s                      switch to non-functioning allocator\n"
+		"q                      quit";
+
+	for (i = 0; i < sizeof darray / sizeof *darray; i++)
+		dict_init(&darray[i], DICTCOUNT_T_MAX, comparef);
+
+	for (;;) {
+		if (prompt)
+			putchar('>');
+		fflush(stdout);
+
+		if (!fgets(in, sizeof(input_t), stdin))
+			break;
+
+		switch(in[0]) {
+			case '?':
+				puts(help);
+				break;
+			case 'a':
+				if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+					puts("what?");
+					break;
+				}
+				key = dupstring(tok1);
+				val = dupstring(tok2);
+
+				if (!key || !val) {
+					puts("out of memory");
+					free((void *) key);
+					free(val);
+				}
+
+				if (!dict_alloc_insert(d, key, val)) {
+					puts("dict_alloc_insert failed");
+					free((void *) key);
+					free(val);
+					break;
+				}
+				break;
+			case 'd':
+				if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+					puts("what?");
+					break;
+				}
+				dn = dict_lookup(d, tok1);
+				if (!dn) {
+					puts("dict_lookup failed");
+					break;
+				}
+				val = dnode_get(dn);
+				key = dnode_getkey(dn);
+				dict_delete_free(d, dn);
+
+				free(val);
+				free((void *) key);
+				break;
+			case 'f':
+				dict_free(d);
+				break;
+			case 'l':
+			case '(':
+			case ')':
+				if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+					puts("what?");
+					break;
+				}
+				dn = 0;
+				switch (in[0]) {
+					case 'l':
+						dn = dict_lookup(d, tok1);
+						break;
+					case '(':
+						dn = dict_lower_bound(d, tok1);
+						break;
+					case ')':
+						dn = dict_upper_bound(d, tok1);
+						break;
+				}
+				if (!dn) {
+					puts("lookup failed");
+					break;
+				}
+				val = dnode_get(dn);
+				puts(val);
+				break;
+			case 'm':
+				construct(d);
+				break;
+			case 'k':
+				dict_allow_dupes(d);
+				break;
+			case 'c':
+				printf("%lu\n", (unsigned long) dict_count(d));
+				break;
+			case 't':
+				for (dn = dict_first(d); dn; dn = dict_next(d, dn)) {
+					printf("%s\t%s\n", (char *) dnode_getkey(dn),
+							(char *) dnode_get(dn));
+				}
+				break;
+			case 'q':
+				exit(0);
+				break;
+			case '\0':
+				break;
+			case 'p':
+				prompt = 1;
+				break;
+			case 's':
+				dict_set_allocator(d, new_node, del_node, NULL);
+				break;
+			case '#':
+				if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+					puts("what?");
+					break;
+				} else {
+					int dictnum = atoi(tok1);
+					if (dictnum < 0 || dictnum > 9) {
+						puts("invalid number");
+						break;
+					}
+					d = &darray[dictnum];
+				}
+				break;
+			case 'j':
+				if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+					puts("what?");
+					break;
+				} else {
+					int dict1 = atoi(tok1), dict2 = atoi(tok2);
+					if (dict1 < 0 || dict1 > 9 || dict2 < 0 || dict2 > 9) {
+						puts("invalid number");
+						break;
+					}
+					dict_merge(&darray[dict1], &darray[dict2]);
+				}
+				break;
+			default:
+				putchar('?');
+				putchar('\n');
+				break;
+		}
+	}
+
+	return 0;
+}
+
+#endif
diff --git a/fsck/dict.h b/fsck/dict.h
new file mode 100644
index 0000000..c59e1a2
--- /dev/null
+++ b/fsck/dict.h
@@ -0,0 +1,144 @@
+/*
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ * $Id: dict.h,v 1.22.2.6 2000/11/13 01:36:44 kaz Exp $
+ * $Name: kazlib_1_20 $
+ */
+
+#ifndef DICT_H
+#define DICT_H
+
+#include <limits.h>
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#include "sfx.h"
+#endif
+
+/*
+ * Blurb for inclusion into C++ translation units
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef unsigned long dictcount_t;
+#define DICTCOUNT_T_MAX ULONG_MAX
+
+/*
+ * The dictionary is implemented as a red-black tree
+ */
+
+typedef enum { dnode_red, dnode_black } dnode_color_t;
+
+typedef struct dnode_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+	struct dnode_t *dict_left;
+	struct dnode_t *dict_right;
+	struct dnode_t *dict_parent;
+	dnode_color_t dict_color;
+	const void *dict_key;
+	void *dict_data;
+#else
+	int dict_dummy;
+#endif
+} dnode_t;
+
+typedef int (*dict_comp_t)(const void *, const void *);
+typedef dnode_t *(*dnode_alloc_t)(void *);
+typedef void (*dnode_free_t)(dnode_t *, void *);
+
+typedef struct dict_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+	dnode_t dict_nilnode;
+	dictcount_t dict_nodecount;
+	dictcount_t dict_maxcount;
+	dict_comp_t dict_compare;
+	dnode_alloc_t dict_allocnode;
+	dnode_free_t dict_freenode;
+	void *dict_context;
+	int dict_dupes;
+#else
+	int dict_dummmy;
+#endif
+} dict_t;
+
+typedef void (*dnode_process_t)(dict_t *, dnode_t *, void *);
+
+typedef struct dict_load_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+	dict_t *dict_dictptr;
+	dnode_t dict_nilnode;
+#else
+	int dict_dummmy;
+#endif
+} dict_load_t;
+
+extern dict_t *dict_create(dictcount_t, dict_comp_t);
+extern void dict_set_allocator(dict_t *, dnode_alloc_t, dnode_free_t, void *);
+extern void dict_destroy(dict_t *);
+extern void dict_free_nodes(dict_t *);
+extern void dict_free(dict_t *);
+extern dict_t *dict_init(dict_t *, dictcount_t, dict_comp_t);
+extern void dict_init_like(dict_t *, const dict_t *);
+extern int dict_verify(dict_t *);
+extern int dict_similar(const dict_t *, const dict_t *);
+extern dnode_t *dict_lookup(dict_t *, const void *);
+extern dnode_t *dict_lower_bound(dict_t *, const void *);
+extern dnode_t *dict_upper_bound(dict_t *, const void *);
+extern void dict_insert(dict_t *, dnode_t *, const void *);
+extern dnode_t *dict_delete(dict_t *, dnode_t *);
+extern int dict_alloc_insert(dict_t *, const void *, void *);
+extern void dict_delete_free(dict_t *, dnode_t *);
+extern dnode_t *dict_first(dict_t *);
+extern dnode_t *dict_last(dict_t *);
+extern dnode_t *dict_next(dict_t *, dnode_t *);
+extern dnode_t *dict_prev(dict_t *, dnode_t *);
+extern dictcount_t dict_count(dict_t *);
+extern int dict_isempty(dict_t *);
+extern int dict_isfull(dict_t *);
+extern int dict_contains(dict_t *, dnode_t *);
+extern void dict_allow_dupes(dict_t *);
+extern int dnode_is_in_a_dict(dnode_t *);
+extern dnode_t *dnode_create(void *);
+extern dnode_t *dnode_init(dnode_t *, void *);
+extern void dnode_destroy(dnode_t *);
+extern void *dnode_get(dnode_t *);
+extern const void *dnode_getkey(dnode_t *);
+extern void dnode_put(dnode_t *, void *);
+extern void dict_process(dict_t *, void *, dnode_process_t);
+extern void dict_load_begin(dict_load_t *, dict_t *);
+extern void dict_load_next(dict_load_t *, dnode_t *, const void *);
+extern void dict_load_end(dict_load_t *);
+extern void dict_merge(dict_t *, dict_t *);
+
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#define dict_isfull(D) (SFX_CHECK(D)->dict_nodecount == (D)->dict_maxcount)
+#else
+#define dict_isfull(D) ((D)->dict_nodecount == (D)->dict_maxcount)
+#endif
+#define dict_count(D) ((D)->dict_nodecount)
+#define dict_isempty(D) ((D)->dict_nodecount == 0)
+#define dnode_get(N) ((N)->dict_data)
+#define dnode_getkey(N) ((N)->dict_key)
+#define dnode_put(N, X) ((N)->dict_data = (X))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/fsck/dir.c b/fsck/dir.c
index 57b7f9b..b2ea18f 100644
--- a/fsck/dir.c
+++ b/fsck/dir.c
@@ -34,20 +34,29 @@
 
 }
 
-static void make_dentry_ptr(struct f2fs_dentry_ptr *d, void *src, int type)
+void make_dentry_ptr(struct f2fs_dentry_ptr *d, struct f2fs_node *node_blk,
+							void *src, int type)
 {
 	if (type == 1) {
 		struct f2fs_dentry_block *t = (struct f2fs_dentry_block *)src;
 		d->max = NR_DENTRY_IN_BLOCK;
+		d->nr_bitmap = SIZE_OF_DENTRY_BITMAP;
 		d->bitmap = t->dentry_bitmap;
 		d->dentry = t->dentry;
 		d->filename = t->filename;
 	} else {
-		struct f2fs_inline_dentry *t = (struct f2fs_inline_dentry *)src;
-		d->max = NR_INLINE_DENTRY;
-		d->bitmap = t->dentry_bitmap;
-		d->dentry = t->dentry;
-		d->filename = t->filename;
+		int entry_cnt = NR_INLINE_DENTRY(node_blk);
+		int bitmap_size = INLINE_DENTRY_BITMAP_SIZE(node_blk);
+		int reserved_size = INLINE_RESERVED_SIZE(node_blk);
+
+		d->max = entry_cnt;
+		d->nr_bitmap = bitmap_size;
+		d->bitmap = (u8 *)src;
+		d->dentry = (struct f2fs_dir_entry *)
+				((char *)src + bitmap_size + reserved_size);
+		d->filename = (__u8 (*)[F2FS_SLOT_LEN])((char *)src +
+				bitmap_size + reserved_size +
+				SIZE_OF_DIR_ENTRY * entry_cnt);
 	}
 }
 
@@ -61,7 +70,7 @@
 
 	if (max_slots)
 		*max_slots = 0;
-	while (bit_pos < d->max) {
+	while (bit_pos < (unsigned long)d->max) {
 		if (!test_bit_le(bit_pos, d->bitmap)) {
 			bit_pos++;
 			max_len++;
@@ -93,7 +102,7 @@
 {
 	struct f2fs_dentry_ptr d;
 
-	make_dentry_ptr(&d, block, 1);
+	make_dentry_ptr(&d, NULL, block, 1);
 	return find_target_dentry(name, len, namehash, max_slots, &d);
 }
 
@@ -103,7 +112,7 @@
 	unsigned int nbucket, nblock;
 	unsigned int bidx, end_block;
 	struct f2fs_dir_entry *dentry = NULL;
-	struct dnode_of_data dn = {0};
+	struct dnode_of_data dn;
 	void *dentry_blk;
 	int max_slots = 214;
 	nid_t ino = le32_to_cpu(dir->footer.ino);
@@ -122,6 +131,7 @@
 	dentry_blk = calloc(BLOCK_SZ, 1);
 	ASSERT(dentry_blk);
 
+	memset(&dn, 0, sizeof(dn));
 	for (; bidx < end_block; bidx++) {
 
 		/* Firstly, we should know direct node of target data blk */
@@ -199,7 +209,7 @@
 	f2fs_hash_t dentry_hash = f2fs_dentry_hash(name, name_len);
 	struct f2fs_dentry_block *dentry_blk;
 	struct f2fs_dentry_ptr d;
-	struct dnode_of_data dn = {0};
+	struct dnode_of_data dn;
 	nid_t pino = le32_to_cpu(parent->footer.ino);
 	unsigned int dir_level = parent->i.i_dir_level;
 	int ret;
@@ -231,6 +241,7 @@
 	nblock = bucket_blocks(level);
 	bidx = dir_block_index(level, dir_level, le32_to_cpu(dentry_hash) % nbucket);
 
+	memset(&dn, 0, sizeof(dn));
 	for (block = bidx; block <= (bidx + nblock - 1); block++) {
 
 		/* Firstly, we should know the direct node of target data blk */
@@ -256,7 +267,7 @@
 	goto start;
 
 add_dentry:
-	make_dentry_ptr(&d, (void *)dentry_blk, 1);
+	make_dentry_ptr(&d, NULL, (void *)dentry_blk, 1);
 	f2fs_update_dentry(ino, file_type, &d, name, name_len, dentry_hash, bit_pos);
 
 	ret = dev_write_block(dentry_blk, dn.data_blkaddr);
@@ -278,7 +289,8 @@
 		dn.idirty = 1;
 	}
 
-	if ((block + 1) * F2FS_BLKSIZE > le64_to_cpu(parent->i.i_size)) {
+	if ((__u64)((block + 1) * F2FS_BLKSIZE) >
+					le64_to_cpu(parent->i.i_size)) {
 		parent->i.i_size = cpu_to_le64((block + 1) * F2FS_BLKSIZE);
 		dn.idirty = 1;
 	}
@@ -307,7 +319,7 @@
 	nid_t pino = le32_to_cpu(inode->i.i_pino);
 	struct f2fs_summary sum;
 	struct node_info ni;
-	block_t blkaddr;
+	block_t blkaddr = NULL_ADDR;
 	int ret;
 
 	get_node_info(sbi, ino, &ni);
@@ -336,7 +348,7 @@
 	ret = dev_write_block(dent_blk, blkaddr);
 	ASSERT(ret >= 0);
 
-	inode->i.i_addr[0] = cpu_to_le32(blkaddr);
+	inode->i.i_addr[get_extra_isize(inode)] = cpu_to_le32(blkaddr);
 	free(dent_blk);
 }
 
@@ -347,16 +359,16 @@
 	struct f2fs_summary sum;
 	struct node_info ni;
 	char *data_blk;
-	block_t blkaddr;
+	block_t blkaddr = NULL_ADDR;
 	int ret;
 
 	get_node_info(sbi, ino, &ni);
 
 	/* store into inline_data */
-	if (symlen + 1 <= MAX_INLINE_DATA) {
+	if ((unsigned long)(symlen + 1) <= MAX_INLINE_DATA(inode)) {
 		inode->i.i_inline |= F2FS_INLINE_DATA;
 		inode->i.i_inline |= F2FS_DATA_EXIST;
-		memcpy(&inode->i.i_addr[1], symname, symlen);
+		memcpy(inline_data_addr(inode), symname, symlen);
 		return;
 	}
 
@@ -371,7 +383,7 @@
 	ret = dev_write_block(data_blk, blkaddr);
 	ASSERT(ret >= 0);
 
-	inode->i.i_addr[0] = cpu_to_le32(blkaddr);
+	inode->i.i_addr[get_extra_isize(inode)] = cpu_to_le32(blkaddr);
 	free(data_blk);
 }
 
@@ -396,7 +408,7 @@
 		ASSERT(de->link);
 		mode |= S_IFLNK;
 		size = strlen(de->link);
-		if (size + 1 > MAX_INLINE_DATA)
+		if (size + 1 > MAX_INLINE_DATA(node_blk))
 			blocks++;
 	} else {
 		ASSERT(0);
@@ -425,6 +437,12 @@
 	memcpy(node_blk->i.i_name, de->name, de->len);
 	node_blk->i.i_name[de->len] = 0;
 
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
+		node_blk->i.i_inline |= F2FS_EXTRA_ATTR;
+		node_blk->i.i_extra_isize =
+				cpu_to_le16(F2FS_TOTAL_EXTRA_ATTR_SIZE);
+	}
+
 	node_blk->footer.ino = cpu_to_le32(de->ino);
 	node_blk->footer.nid = cpu_to_le32(de->ino);
 	node_blk->footer.flag = 0;
@@ -434,6 +452,10 @@
 		make_empty_dir(sbi, node_blk);
 	else if (S_ISLNK(mode))
 		page_symlink(sbi, node_blk, de->link, size);
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+		node_blk->i.i_inode_checksum =
+			cpu_to_le32(f2fs_inode_chksum(node_blk));
 }
 
 int convert_inline_dentry(struct f2fs_sb_info *sbi, struct f2fs_node *node,
@@ -442,8 +464,8 @@
 	struct f2fs_inode *inode = &(node->i);
 	unsigned int dir_level = node->i.i_dir_level;
 	nid_t ino = le32_to_cpu(node->footer.ino);
-	char inline_data[MAX_INLINE_DATA];
-	struct dnode_of_data dn = {0};
+	char inline_data[MAX_INLINE_DATA(node)];
+	struct dnode_of_data dn;
 	struct f2fs_dentry_ptr d;
 	unsigned long bit_pos = 0;
 	int ret = 0;
@@ -451,16 +473,17 @@
 	if (!(inode->i_inline & F2FS_INLINE_DENTRY))
 		return 0;
 
-	memcpy(inline_data, inline_data_addr(node), MAX_INLINE_DATA);
-	memset(inline_data_addr(node), 0, MAX_INLINE_DATA);
+	memcpy(inline_data, inline_data_addr(node), MAX_INLINE_DATA(node));
+	memset(inline_data_addr(node), 0, MAX_INLINE_DATA(node));
 	inode->i_inline &= ~F2FS_INLINE_DENTRY;
 
 	ret = dev_write_block(node, p_blkaddr);
 	ASSERT(ret >= 0);
 
+	memset(&dn, 0, sizeof(dn));
 	if (!dir_level) {
-		struct f2fs_inline_dentry *inline_dentry;
 		struct f2fs_dentry_block *dentry_blk;
+		struct f2fs_dentry_ptr src, dst;
 
 		dentry_blk = calloc(BLOCK_SZ, 1);
 		ASSERT(dentry_blk);
@@ -470,17 +493,16 @@
 		if (dn.data_blkaddr == NULL_ADDR)
 			new_data_block(sbi, dentry_blk, &dn, CURSEG_HOT_DATA);
 
-		inline_dentry = (struct f2fs_inline_dentry *)inline_data;
-		 /* copy data from inline dentry block to new dentry block */
-		memcpy(dentry_blk->dentry_bitmap, inline_dentry->dentry_bitmap,
-				INLINE_DENTRY_BITMAP_SIZE);
-		memset(dentry_blk->dentry_bitmap + INLINE_DENTRY_BITMAP_SIZE, 0,
-			SIZE_OF_DENTRY_BITMAP - INLINE_DENTRY_BITMAP_SIZE);
+		make_dentry_ptr(&src, node, (void *)inline_data, 2);
+		make_dentry_ptr(&dst, NULL, (void *)dentry_blk, 1);
 
-		memcpy(dentry_blk->dentry, inline_dentry->dentry,
-			sizeof(struct f2fs_dir_entry) * NR_INLINE_DENTRY);
-		memcpy(dentry_blk->filename, inline_dentry->filename,
-				NR_INLINE_DENTRY * F2FS_SLOT_LEN);
+		 /* copy data from inline dentry block to new dentry block */
+		memcpy(dst.bitmap, src.bitmap, src.nr_bitmap);
+		memset(dst.bitmap + src.nr_bitmap, 0,
+					dst.nr_bitmap - src.nr_bitmap);
+
+		memcpy(dst.dentry, src.dentry, SIZE_OF_DIR_ENTRY * src.max);
+		memcpy(dst.filename, src.filename, src.max * F2FS_SLOT_LEN);
 
 		ret = dev_write_block(dentry_blk, dn.data_blkaddr);
 		ASSERT(ret >= 0);
@@ -492,9 +514,9 @@
 	}
 
 	make_empty_dir(sbi, node);
-	make_dentry_ptr(&d, (void *)inline_data, 2);
+	make_dentry_ptr(&d, node, (void *)inline_data, 2);
 
-	while (bit_pos < d.max) {
+	while (bit_pos < (unsigned long)d.max) {
 		struct f2fs_dir_entry *de;
 		const unsigned char *filename;
 		int namelen;
@@ -537,7 +559,7 @@
 	struct f2fs_node *parent, *child;
 	struct node_info ni;
 	struct f2fs_summary sum;
-	block_t blkaddr;
+	block_t blkaddr = NULL_ADDR;
 	int ret;
 
 	/* Find if there is a */
@@ -598,9 +620,12 @@
 	ASSERT(ret >= 0);
 
 	update_free_segments(sbi);
-	MSG(1, "Info: Create \"%s\" type=%x, ino=%x / %x into \"%s\"\n",
-			de->full_path, de->file_type,
-			de->ino, de->pino, de->path);
+	MSG(1, "Info: Create %s -> %s\n"
+		"  -- ino=%x, type=%x, mode=%x, uid=%x, "
+		"gid=%x, cap=%"PRIx64", size=%lu, pino=%x\n",
+		de->full_path, de->path,
+		de->ino, de->file_type, de->mode,
+		de->uid, de->gid, de->capabilities, de->size, de->pino);
 free_child_dir:
 	free(child);
 free_parent_dir:
diff --git a/fsck/dqblk_v2.h b/fsck/dqblk_v2.h
new file mode 100644
index 0000000..d12512a
--- /dev/null
+++ b/fsck/dqblk_v2.h
@@ -0,0 +1,31 @@
+/*
+ * Header file for disk format of new quotafile format
+ *
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ */
+
+#ifndef __QUOTA_DQBLK_V2_H__
+#define __QUOTA_DQBLK_V2_H__
+
+#include "quotaio_tree.h"
+
+/* Structure for format specific information */
+struct v2_mem_dqinfo {
+	struct qtree_mem_dqinfo dqi_qtree;
+	unsigned int dqi_flags;		/* Flags set in quotafile */
+	unsigned int dqi_used_entries;	/* Number of entries in file -
+					   updated by scan_dquots */
+	unsigned int dqi_data_blocks;	/* Number of data blocks in file -
+					   updated by scan_dquots */
+};
+
+struct v2_mem_dqblk {
+	long long dqb_off;	/* Offset of dquot in file */
+};
+
+struct quotafile_ops;		/* Will be defined later in quotaio.h */
+
+/* Operations above this format */
+extern struct quotafile_ops quotafile_ops_2;
+
+#endif  /* __QUOTA_DQBLK_V2_H__ */
diff --git a/fsck/dump.c b/fsck/dump.c
index 22e2265..7ccb03f 100644
--- a/fsck/dump.c
+++ b/fsck/dump.c
@@ -11,6 +11,13 @@
 #include <inttypes.h>
 
 #include "fsck.h"
+#include "xattr.h"
+#ifdef HAVE_ATTR_XATTR_H
+#include <attr/xattr.h>
+#endif
+#ifdef HAVE_LINUX_XATTR_H
+#include <linux/xattr.h>
+#endif
 #include <locale.h>
 
 #define BUF_SZ	80
@@ -310,26 +317,92 @@
 	free(node_blk);
 }
 
+#ifdef HAVE_FSETXATTR
+static void dump_xattr(struct f2fs_sb_info *sbi, struct f2fs_node *node_blk)
+{
+	void *xattr;
+	struct f2fs_xattr_entry *ent;
+	char xattr_name[F2FS_NAME_LEN] = {0};
+	int ret;
+
+	xattr = read_all_xattrs(sbi, node_blk);
+
+	list_for_each_xattr(ent, xattr) {
+		char *name = strndup(ent->e_name, ent->e_name_len);
+		void *value = ent->e_name + ent->e_name_len;
+
+		if (!name)
+			continue;
+
+		switch (ent->e_name_index) {
+		case F2FS_XATTR_INDEX_USER:
+			ret = snprintf(xattr_name, F2FS_NAME_LEN, "%s%s",
+				       XATTR_USER_PREFIX, name);
+			break;
+
+		case F2FS_XATTR_INDEX_SECURITY:
+			ret = snprintf(xattr_name, F2FS_NAME_LEN, "%s%s",
+				       XATTR_SECURITY_PREFIX, name);
+			break;
+		case F2FS_XATTR_INDEX_TRUSTED:
+			ret = snprintf(xattr_name, F2FS_NAME_LEN, "%s%s",
+				       XATTR_TRUSTED_PREFIX, name);
+			break;
+		default:
+			MSG(0, "Unknown xattr index 0x%x\n", ent->e_name_index);
+			free(name);
+			continue;
+		}
+		if (ret >= F2FS_NAME_LEN) {
+			MSG(0, "XATTR index 0x%x name too long\n", ent->e_name_index);
+			free(name);
+			continue;
+		}
+
+		DBG(1, "fd %d xattr_name %s\n", c.dump_fd, xattr_name);
+#if defined(__linux__)
+		ret = fsetxattr(c.dump_fd, xattr_name, value,
+				le16_to_cpu(ent->e_value_size), 0);
+#elif defined(__APPLE__)
+		ret = fsetxattr(c.dump_fd, xattr_name, value,
+				le16_to_cpu(ent->e_value_size), 0,
+				XATTR_CREATE);
+#endif
+		if (ret)
+			MSG(0, "XATTR index 0x%x set xattr failed error %d\n",
+			    ent->e_name_index, errno);
+
+		free(name);
+	}
+
+	free(xattr);
+}
+#else
+static void dump_xattr(struct f2fs_sb_info *UNUSED(sbi),
+				struct f2fs_node *UNUSED(node_blk))
+{
+	MSG(0, "XATTR does not support\n");
+}
+#endif
+
 static void dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
 					struct f2fs_node *node_blk)
 {
 	u32 i = 0;
 	u64 ofs = 0;
 
-	/* TODO: need to dump xattr */
-
-	if((node_blk->i.i_inline & F2FS_INLINE_DATA)){
+	if((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
 		DBG(3, "ino[0x%x] has inline data!\n", nid);
 		/* recover from inline data */
 		dev_write_dump(((unsigned char *)node_blk) + INLINE_DATA_OFFSET,
-							0, MAX_INLINE_DATA);
+						0, MAX_INLINE_DATA(node_blk));
 		return;
 	}
 
 	/* check data blocks in inode */
 	for (i = 0; i < ADDRS_PER_INODE(&node_blk->i); i++, ofs++)
-		dump_data_blk(sbi, ofs * F2FS_BLKSIZE,
-				le32_to_cpu(node_blk->i.i_addr[i]));
+		dump_data_blk(sbi, ofs * F2FS_BLKSIZE, le32_to_cpu(
+			node_blk->i.i_addr[get_extra_isize(node_blk) + i]));
 
 	/* check node blocks in inode */
 	for (i = 0; i < 5; i++) {
@@ -345,6 +418,8 @@
 		else
 			ASSERT(0);
 	}
+
+	dump_xattr(sbi, node_blk);
 }
 
 static void dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
@@ -353,12 +428,17 @@
 	struct f2fs_inode *inode = &node_blk->i;
 	u32 imode = le32_to_cpu(inode->i_mode);
 	u32 namelen = le32_to_cpu(inode->i_namelen);
-	unsigned char name[F2FS_NAME_LEN + 1] = {0};
+	char name[F2FS_NAME_LEN + 1] = {0};
 	char path[1024] = {0};
 	char ans[255] = {0};
-	int enc_name = file_enc_name(inode);
+	int is_encrypted = file_is_encrypt(inode);
 	int ret;
 
+	if (is_encrypted) {
+		MSG(force, "File is encrypted\n");
+		return;
+	}
+
 	if (!S_ISREG(imode) || namelen == 0 || namelen > F2FS_NAME_LEN) {
 		MSG(force, "Not a regular file or wrong name info\n\n");
 		return;
@@ -376,8 +456,7 @@
 		ASSERT(ret >= 0);
 
 		/* make a file */
-		namelen = convert_encrypted_name(inode->i_name, namelen,
-							name, enc_name);
+		strncpy(name, (const char *)inode->i_name, namelen);
 		name[namelen] = 0;
 		sprintf(path, "./lost_found/%s", name);
 
@@ -419,17 +498,17 @@
 	if (le32_to_cpu(node_blk->footer.ino) == ni.ino &&
 			le32_to_cpu(node_blk->footer.nid) == ni.nid &&
 			ni.ino == ni.nid) {
-		print_node_info(node_blk, force);
+		print_node_info(sbi, node_blk, force);
 		dump_file(sbi, &ni, node_blk, force);
 	} else {
-		print_node_info(node_blk, force);
+		print_node_info(sbi, node_blk, force);
 		MSG(force, "Invalid (i)node block\n\n");
 	}
 
 	free(node_blk);
 }
 
-static void dump_node_from_blkaddr(u32 blk_addr)
+static void dump_node_from_blkaddr(struct f2fs_sb_info *sbi, u32 blk_addr)
 {
 	struct f2fs_node *node_blk;
 	int ret;
@@ -441,9 +520,9 @@
 	ASSERT(ret >= 0);
 
 	if (c.dbg_lv > 0)
-		print_node_info(node_blk, 0);
+		print_node_info(sbi, node_blk, 0);
 	else
-		print_inode_info(&node_blk->i, 1);
+		print_inode_info(sbi, node_blk, 1);
 
 	free(node_blk);
 }
@@ -507,6 +586,8 @@
 	int type;
 	struct f2fs_summary sum_entry;
 	struct node_info ni, ino_ni;
+	struct seg_entry *se;
+	u32 offset;
 	int ret = 0;
 
 	MSG(0, "\n== Dump data from block address ==\n\n");
@@ -538,6 +619,13 @@
 		return ret;
 	}
 
+	se = get_seg_entry(sbi, GET_SEGNO(sbi, blk_addr));
+	offset = OFFSET_IN_SEG(sbi, blk_addr);
+
+	if (f2fs_test_bit(offset, (const char *)se->cur_valid_map) == 0) {
+		MSG(0, "\nblkaddr is not valid\n");
+	}
+
 	type = get_sum_entry(sbi, blk_addr, &sum_entry);
 	nid = le32_to_cpu(sum_entry.nid);
 
@@ -567,7 +655,7 @@
 
 	/* print inode */
 	if (c.dbg_lv > 0)
-		dump_node_from_blkaddr(ino_ni.blk_addr);
+		dump_node_from_blkaddr(sbi, ino_ni.blk_addr);
 
 	if (type == SEG_TYPE_CUR_DATA || type == SEG_TYPE_DATA) {
 		MSG(0, "FS Userdata Area: Data block from 0x%x\n", blk_addr);
@@ -575,7 +663,7 @@
 					nid, ni.blk_addr);
 		MSG(0, " - Inode block       : id = 0x%x from 0x%x\n",
 					ni.ino, ino_ni.blk_addr);
-		dump_node_from_blkaddr(ino_ni.blk_addr);
+		dump_node_from_blkaddr(sbi, ino_ni.blk_addr);
 		dump_data_offset(ni.blk_addr,
 			le16_to_cpu(sum_entry.ofs_in_node));
 	} else {
@@ -583,13 +671,13 @@
 		if (ni.ino == ni.nid) {
 			MSG(0, " - Inode block       : id = 0x%x from 0x%x\n",
 					ni.ino, ino_ni.blk_addr);
-			dump_node_from_blkaddr(ino_ni.blk_addr);
+			dump_node_from_blkaddr(sbi, ino_ni.blk_addr);
 		} else {
 			MSG(0, " - Node block        : id = 0x%x from 0x%x\n",
 					nid, ni.blk_addr);
 			MSG(0, " - Inode block       : id = 0x%x from 0x%x\n",
 					ni.ino, ino_ni.blk_addr);
-			dump_node_from_blkaddr(ino_ni.blk_addr);
+			dump_node_from_blkaddr(sbi, ino_ni.blk_addr);
 			dump_node_offset(ni.blk_addr);
 		}
 	}
diff --git a/fsck/f2fs.h b/fsck/f2fs.h
index efc43f6..417ca0b 100644
--- a/fsck/f2fs.h
+++ b/fsck/f2fs.h
@@ -11,6 +11,7 @@
 #ifndef _F2FS_H_
 #define _F2FS_H_
 
+#include <f2fs_fs.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <stdio.h>
@@ -18,14 +19,14 @@
 #include <fcntl.h>
 #include <string.h>
 #include <errno.h>
+#ifdef HAVE_MNTENT_H
 #include <mntent.h>
+#endif
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/mount.h>
 #include <assert.h>
 
-#include <f2fs_fs.h>
-
 #define EXIT_ERR_CODE		(-1)
 #define ver_after(a, b) (typecheck(unsigned long long, a) &&            \
 		typecheck(unsigned long long, b) &&                     \
@@ -49,6 +50,7 @@
 
 struct f2fs_nm_info {
 	block_t nat_blkaddr;
+	block_t nat_blocks;
 	nid_t max_nid;
 	nid_t init_scan_nid;
 	nid_t next_scan_nid;
@@ -64,12 +66,6 @@
 struct seg_entry {
 	unsigned short valid_blocks;    /* # of valid blocks */
 	unsigned char *cur_valid_map;   /* validity bitmap of blocks */
-	/*
-	 * # of valid blocks and the validity bitmap stored in the the last
-	 * checkpoint pack. This information is used by the SSR mode.
-	 */
-	unsigned short ckpt_valid_blocks;
-	unsigned char *ckpt_valid_map;
 	unsigned char type;             /* segment type like CURSEG_XXX_TYPE */
 	unsigned char orig_type;        /* segment type like CURSEG_XXX_TYPE */
 	unsigned long long mtime;       /* modification time of the segment */
@@ -129,6 +125,7 @@
 	struct f2fs_dir_entry *dentry;
 	__u8 (*filename)[F2FS_SLOT_LEN];
 	int max;
+	int nr_bitmap;
 };
 
 struct dentry {
@@ -232,7 +229,9 @@
 
 static inline void *inline_data_addr(struct f2fs_node *node_blk)
 {
-	return (void *)&(node_blk->i.i_addr[1]);
+	int ofs = get_extra_isize(node_blk) + DEF_INLINE_RESERVED_SIZE;
+
+	return (void *)&(node_blk->i.i_addr[ofs]);
 }
 
 static inline unsigned int ofs_of_node(struct f2fs_node *node_blk)
@@ -273,7 +272,7 @@
 static inline bool is_set_ckpt_flags(struct f2fs_checkpoint *cp, unsigned int f)
 {
 	unsigned int ckpt_flags = le32_to_cpu(cp->ckpt_flags);
-	return ckpt_flags & f;
+	return ckpt_flags & f ? 1 : 0;
 }
 
 static inline block_t __start_cp_addr(struct f2fs_sb_info *sbi)
@@ -365,21 +364,12 @@
 
 static inline bool IS_VALID_BLK_ADDR(struct f2fs_sb_info *sbi, u32 addr)
 {
-	int i;
-
 	if (addr >= le64_to_cpu(F2FS_RAW_SUPER(sbi)->block_count) ||
 				addr < SM_I(sbi)->main_blkaddr) {
 		DBG(1, "block addr [0x%x]\n", addr);
 		return 0;
 	}
-
-	for (i = 0; i < NO_CHECK_TYPE; i++) {
-		struct curseg_info *curseg = CURSEG_I(sbi, i);
-
-		if (START_BLOCK(sbi, curseg->segno) +
-					curseg->next_blkoff == addr)
-			return 0;
-	}
+	/* next block offset will be checked at the end of fsck. */
 	return 1;
 }
 
@@ -451,14 +441,13 @@
 
 static inline void *inline_xattr_addr(struct f2fs_inode *inode)
 {
-	return (void *)&(inode->i_addr[DEF_ADDRS_PER_INODE_INLINE_XATTR]);
+	return (void *)&(inode->i_addr[DEF_ADDRS_PER_INODE -
+				get_inline_xattr_addrs(inode)]);
 }
 
 static inline int inline_xattr_size(struct f2fs_inode *inode)
 {
-	if (inode->i_inline & F2FS_INLINE_XATTR)
-		return F2FS_INLINE_XATTR_ADDRS << 2;
-	return 0;
+	return get_inline_xattr_addrs(inode) * sizeof(__le32);
 }
 
 extern int lookup_nat_in_journal(struct f2fs_sb_info *sbi, u32 nid, struct f2fs_nat_entry *ne);
diff --git a/fsck/fsck.c b/fsck/fsck.c
index 56336ad..668ecc1 100644
--- a/fsck/fsck.c
+++ b/fsck/fsck.c
@@ -9,12 +9,12 @@
  * published by the Free Software Foundation.
  */
 #include "fsck.h"
+#include "quotaio.h"
 
 char *tree_mark;
 uint32_t tree_mark_size = 256;
 
-static inline int f2fs_set_main_bitmap(struct f2fs_sb_info *sbi, u32 blk,
-								int type)
+int f2fs_set_main_bitmap(struct f2fs_sb_info *sbi, u32 blk, int type)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
 	struct seg_entry *se;
@@ -50,6 +50,13 @@
 	return f2fs_test_bit(BLKOFF_FROM_MAIN(sbi, blk), fsck->sit_area_bitmap);
 }
 
+int f2fs_set_sit_bitmap(struct f2fs_sb_info *sbi, u32 blk)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+
+	return f2fs_set_bit(BLKOFF_FROM_MAIN(sbi, blk), fsck->sit_area_bitmap);
+}
+
 static int add_into_hard_link_list(struct f2fs_sb_info *sbi,
 						u32 nid, u32 link_cnt)
 {
@@ -226,10 +233,13 @@
 		goto out;
 
 	/* check its block address */
-	if (node_blk->footer.nid == node_blk->footer.ino)
-		target_blk_addr = node_blk->i.i_addr[ofs_in_node];
-	else
+	if (node_blk->footer.nid == node_blk->footer.ino) {
+		int ofs = get_extra_isize(node_blk);
+
+		target_blk_addr = node_blk->i.i_addr[ofs + ofs_in_node];
+	} else {
 		target_blk_addr = node_blk->dn.addr[ofs_in_node];
+	}
 
 	if (blk_addr == le32_to_cpu(target_blk_addr))
 		ret = 1;
@@ -455,6 +465,25 @@
 	return 0;
 }
 
+static int sanity_check_inode(struct f2fs_sb_info *sbi, struct f2fs_node *node)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_inode *fi = &node->i;
+
+	if (!(le16_to_cpu(fi->i_mode) & S_IFMT)) {
+		ASSERT_MSG("i_mode is not valid. [0x%x]", le16_to_cpu(fi->i_mode));
+		goto remove_node;
+	}
+
+	return 0;
+
+remove_node:
+	f2fs_set_bit(le32_to_cpu(node->footer.ino), fsck->nat_area_bitmap);
+	fsck->chk.valid_blk_cnt--;
+	fsck->chk.valid_node_cnt--;
+	return -EINVAL;
+}
+
 static int fsck_chk_xattr_blk(struct f2fs_sb_info *sbi, u32 ino,
 					u32 x_nid, u32 *blk_cnt)
 {
@@ -497,7 +526,12 @@
 		goto err;
 
 	if (ntype == TYPE_INODE) {
-		fsck_chk_inode_blk(sbi, nid, ftype, node_blk, blk_cnt, &ni);
+		struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+
+		if (sanity_check_inode(sbi, node_blk))
+			goto err;
+		fsck_chk_inode_blk(sbi, nid, ftype, node_blk, blk_cnt, &ni, child);
+		quota_add_inode_usage(fsck->qctx, nid, &node_blk->i);
 	} else {
 		switch (ntype) {
 		case TYPE_DIRECT_NODE:
@@ -590,7 +624,7 @@
 /* start with valid nid and blkaddr */
 void fsck_chk_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
 		enum FILE_TYPE ftype, struct f2fs_node *node_blk,
-		u32 *blk_cnt, struct node_info *ni)
+		u32 *blk_cnt, struct node_info *ni, struct child_info *child_d)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
 	struct child_info child;
@@ -598,7 +632,8 @@
 	u32 i_links = le32_to_cpu(node_blk->i.i_links);
 	u64 i_size = le64_to_cpu(node_blk->i.i_size);
 	u64 i_blocks = le64_to_cpu(node_blk->i.i_blocks);
-	unsigned char en[F2FS_NAME_LEN + 1];
+	int ofs = get_extra_isize(node_blk);
+	unsigned char *en;
 	int namelen;
 	unsigned int idx = 0;
 	int need_fix = 0;
@@ -619,7 +654,8 @@
 		if (f2fs_test_main_bitmap(sbi, ni->blk_addr) == 0) {
 			f2fs_set_main_bitmap(sbi, ni->blk_addr,
 							CURSEG_WARM_NODE);
-			if (i_links > 1 && ftype != F2FS_FT_ORPHAN) {
+			if (i_links > 1 && ftype != F2FS_FT_ORPHAN &&
+					!is_qf_ino(F2FS_RAW_SUPER(sbi), nid)) {
 				/* First time. Create new hard link node */
 				add_into_hard_link_list(sbi, nid, i_links);
 				fsck->chk.multi_hard_link_files++;
@@ -657,21 +693,21 @@
 			ftype == F2FS_FT_FIFO || ftype == F2FS_FT_SOCK)
 		goto check;
 
-	if((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
-		if (le32_to_cpu(node_blk->i.i_addr[0]) != 0) {
+	if ((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
+		if (le32_to_cpu(node_blk->i.i_addr[ofs]) != 0) {
 			/* should fix this bug all the time */
 			FIX_MSG("inline_data has wrong 0'th block = %x",
-					le32_to_cpu(node_blk->i.i_addr[0]));
-			node_blk->i.i_addr[0] = 0;
+					le32_to_cpu(node_blk->i.i_addr[ofs]));
+			node_blk->i.i_addr[ofs] = 0;
 			node_blk->i.i_blocks = cpu_to_le64(*blk_cnt);
 			need_fix = 1;
 		}
 		if (!(node_blk->i.i_inline & F2FS_DATA_EXIST)) {
-			char buf[MAX_INLINE_DATA];
-			memset(buf, 0, MAX_INLINE_DATA);
+			char buf[MAX_INLINE_DATA(node_blk)];
+			memset(buf, 0, MAX_INLINE_DATA(node_blk));
 
-			if (memcmp(buf, &node_blk->i.i_addr[1],
-							MAX_INLINE_DATA)) {
+			if (memcmp(buf, inline_data_addr(node_blk),
+						MAX_INLINE_DATA(node_blk))) {
 				FIX_MSG("inline_data has DATA_EXIST");
 				node_blk->i.i_inline |= F2FS_DATA_EXIST;
 				need_fix = 1;
@@ -680,8 +716,18 @@
 		DBG(3, "ino[0x%x] has inline data!\n", nid);
 		goto check;
 	}
-	if((node_blk->i.i_inline & F2FS_INLINE_DENTRY)) {
+
+	if ((node_blk->i.i_inline & F2FS_INLINE_DENTRY)) {
 		DBG(3, "ino[0x%x] has inline dentry!\n", nid);
+		if (le32_to_cpu(node_blk->i.i_addr[ofs]) != 0) {
+			/* should fix this bug all the time */
+			FIX_MSG("inline_dentry has wrong 0'th block = %x",
+					le32_to_cpu(node_blk->i.i_addr[ofs]));
+			node_blk->i.i_addr[ofs] = 0;
+			node_blk->i.i_blocks = cpu_to_le64(*blk_cnt);
+			need_fix = 1;
+		}
+
 		ret = fsck_chk_inline_dentries(sbi, node_blk, &child);
 		if (ret < 0) {
 			/* should fix this bug all the time */
@@ -694,7 +740,7 @@
 	for (idx = 0; idx < 5; idx++) {
 		u32 nid = le32_to_cpu(node_blk->i.i_nid[idx]);
 
-		if (nid != 0) {
+		if (nid != 0 && IS_VALID_NID(sbi, nid)) {
 			struct node_info ni;
 
 			get_node_info(sbi, nid, &ni);
@@ -710,7 +756,7 @@
 	/* check data blocks in inode */
 	for (idx = 0; idx < ADDRS_PER_INODE(&node_blk->i);
 						idx++, child.pgofs++) {
-		block_t blkaddr = le32_to_cpu(node_blk->i.i_addr[idx]);
+		block_t blkaddr = le32_to_cpu(node_blk->i.i_addr[ofs + idx]);
 
 		/* check extent info */
 		check_extent_info(&child, blkaddr, 0);
@@ -720,13 +766,14 @@
 					blkaddr,
 					&child, (i_blocks == *blk_cnt),
 					ftype, nid, idx, ni->version,
-					file_enc_name(&node_blk->i));
+					file_is_encrypt(&node_blk->i));
 			if (!ret) {
 				*blk_cnt = *blk_cnt + 1;
 			} else if (c.fix_on) {
-				node_blk->i.i_addr[idx] = 0;
+				node_blk->i.i_addr[ofs + idx] = 0;
 				need_fix = 1;
-				FIX_MSG("[0x%x] i_addr[%d] = 0", nid, idx);
+				FIX_MSG("[0x%x] i_addr[%d] = 0",
+							nid, ofs + idx);
 			}
 		}
 	}
@@ -791,8 +838,26 @@
 		}
 	}
 skip_blkcnt_fix:
-	namelen = convert_encrypted_name(node_blk->i.i_name,
-					le32_to_cpu(node_blk->i.i_namelen),
+	en = malloc(F2FS_NAME_LEN + 1);
+	ASSERT(en);
+
+	namelen = le32_to_cpu(node_blk->i.i_namelen);
+	if (namelen > F2FS_NAME_LEN) {
+		if (child_d && child_d->i_namelen <= F2FS_NAME_LEN) {
+			ASSERT_MSG("ino: 0x%x has i_namelen: 0x%x, "
+					"but has %d characters for name",
+					nid, namelen, child_d->i_namelen);
+			if (c.fix_on) {
+				FIX_MSG("[0x%x] i_namelen=0x%x -> 0x%x", nid, namelen,
+					child_d->i_namelen);
+				node_blk->i.i_namelen = cpu_to_le32(child_d->i_namelen);
+				need_fix = 1;
+			}
+			namelen = child_d->i_namelen;
+		} else
+			namelen = F2FS_NAME_LEN;
+	}
+	namelen = convert_encrypted_name(node_blk->i.i_name, namelen,
 					en, file_enc_name(&node_blk->i));
 	en[namelen] = '\0';
 	if (ftype == F2FS_FT_ORPHAN)
@@ -800,6 +865,11 @@
 				le32_to_cpu(node_blk->footer.ino),
 				en, (u32)i_blocks);
 
+	if (is_qf_ino(F2FS_RAW_SUPER(sbi), nid))
+		DBG(1, "Quota Inode: 0x%x [%s] i_blocks: %u\n\n",
+				le32_to_cpu(node_blk->footer.ino),
+				en, (u32)i_blocks);
+
 	if (ftype == F2FS_FT_DIR) {
 		DBG(1, "Directory Inode: 0x%x [%s] depth: %d has %d files\n\n",
 				le32_to_cpu(node_blk->footer.ino), en,
@@ -827,16 +897,19 @@
 			}
 		}
 	}
+
+	free(en);
+
 	if (ftype == F2FS_FT_SYMLINK && i_blocks && i_size == 0) {
 		DBG(1, "ino: 0x%x i_blocks: %lu with zero i_size",
-							nid, i_blocks);
+						nid, (unsigned long)i_blocks);
 		if (c.fix_on) {
 			u64 i_size = i_blocks * F2FS_BLKSIZE;
 
 			node_blk->i.i_size = cpu_to_le64(i_size);
 			need_fix = 1;
 			FIX_MSG("Symlink: recover 0x%x with i_size=%lu",
-							nid, i_size);
+						nid, (unsigned long)i_size);
 		}
 	}
 
@@ -850,9 +923,32 @@
 					nid, i_links);
 		}
 	}
-	if (need_fix && !c.ro) {
-		/* drop extent information to avoid potential wrong access */
+
+	/* drop extent information to avoid potential wrong access */
+	if (need_fix && !c.ro)
 		node_blk->i.i_ext.len = 0;
+
+	if ((c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM)) &&
+				f2fs_has_extra_isize(&node_blk->i)) {
+		__u32 provided, calculated;
+
+		provided = le32_to_cpu(node_blk->i.i_inode_checksum);
+		calculated = f2fs_inode_chksum(node_blk);
+
+		if (provided != calculated) {
+			ASSERT_MSG("ino: 0x%x chksum:0x%x, but calculated one is: 0x%x",
+				nid, provided, calculated);
+			if (c.fix_on) {
+				node_blk->i.i_inode_checksum =
+							cpu_to_le32(calculated);
+				need_fix = 1;
+				FIX_MSG("ino: 0x%x recover, i_inode_checksum= 0x%x -> 0x%x",
+						nid, provided, calculated);
+			}
+		}
+	}
+
+	if (need_fix && !c.ro) {
 		ret = dev_write_block(node_blk, ni->blk_addr);
 		ASSERT(ret >= 0);
 	}
@@ -878,7 +974,7 @@
 			blkaddr, child,
 			le64_to_cpu(inode->i_blocks) == *blk_cnt, ftype,
 			nid, idx, ni->version,
-			file_enc_name(inode));
+			file_is_encrypt(inode));
 		if (!ret) {
 			*blk_cnt = *blk_cnt + 1;
 		} else if (c.fix_on) {
@@ -915,7 +1011,7 @@
 			else {
 				node_blk->in.nid[i] = 0;
 				need_fix = 1;
-				FIX_MSG("Set indirect node 0x%x -> 0\n", i);
+				FIX_MSG("Set indirect node 0x%x -> 0", i);
 			}
 skip:
 			child->pgofs += ADDRS_PER_BLOCK;
@@ -955,7 +1051,7 @@
 			else {
 				node_blk->in.nid[i] = 0;
 				need_fix = 1;
-				FIX_MSG("Set double indirect node 0x%x -> 0\n", i);
+				FIX_MSG("Set double indirect node 0x%x -> 0", i);
 			}
 skip:
 			child->pgofs += ADDRS_PER_BLOCK * NIDS_PER_BLOCK;
@@ -1008,6 +1104,8 @@
 				unsigned char *new, int enc_name)
 {
 	if (!enc_name) {
+		if (len > F2FS_NAME_LEN)
+			len = F2FS_NAME_LEN;
 		memcpy(new, name, len);
 		new[len] = 0;
 		return len;
@@ -1324,9 +1422,10 @@
 				dentry, max, i, last_blk, enc_name);
 
 		blk_cnt = 1;
+		child->i_namelen = name_len;
 		ret = fsck_chk_node_blk(sbi,
 				NULL, le32_to_cpu(dentry[i].ino),
-				ftype, TYPE_INODE, &blk_cnt, NULL);
+				ftype, TYPE_INODE, &blk_cnt, child);
 
 		if (ret && c.fix_on) {
 			int j;
@@ -1335,7 +1434,7 @@
 				test_and_clear_bit_le(i + j, bitmap);
 			FIX_MSG("Unlink [0x%x] - %s len[0x%x], type[0x%x]",
 					le32_to_cpu(dentry[i].ino),
-					name, name_len,
+					en, name_len,
 					dentry[i].file_type);
 			fixed = 1;
 		} else if (ret == 0) {
@@ -1355,18 +1454,19 @@
 		struct f2fs_node *node_blk, struct child_info *child)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
-	struct f2fs_inline_dentry *de_blk;
+	struct f2fs_dentry_ptr d;
+	void *inline_dentry;
 	int dentries;
 
-	de_blk = inline_data_addr(node_blk);
-	ASSERT(de_blk != NULL);
+	inline_dentry = inline_data_addr(node_blk);
+	ASSERT(inline_dentry != NULL);
+
+	make_dentry_ptr(&d, node_blk, inline_dentry, 2);
 
 	fsck->dentry_depth++;
 	dentries = __chk_dentries(sbi, child,
-			de_blk->dentry_bitmap,
-			de_blk->dentry, de_blk->filename,
-			NR_INLINE_DENTRY, 1,
-			file_enc_name(&node_blk->i));
+			d.bitmap, d.dentry, d.filename, d.max, 1,
+			file_is_encrypt(&node_blk->i));
 	if (dentries < 0) {
 		DBG(1, "[%3d] Inline Dentry Block Fixed hash_codes\n\n",
 			fsck->dentry_depth);
@@ -1374,7 +1474,7 @@
 		DBG(1, "[%3d] Inline Dentry Block Done : "
 				"dentries:%d in %d slots (len:%d)\n\n",
 			fsck->dentry_depth, dentries,
-			(int)NR_INLINE_DENTRY, F2FS_NAME_LEN);
+			d.max, F2FS_NAME_LEN);
 	}
 	fsck->dentry_depth--;
 	return dentries;
@@ -1527,6 +1627,83 @@
 	return 0;
 }
 
+int fsck_chk_quota_node(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	enum quota_type qtype;
+	int ret = 0;
+	u32 blk_cnt = 0;
+
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		nid_t ino = QUOTA_INO(sb, qtype);
+		struct node_info ni;
+
+		DBG(1, "[%3d] ino [0x%x]\n", qtype, ino);
+		blk_cnt = 1;
+
+		if (c.preen_mode == PREEN_MODE_1 && !c.fix_on) {
+			get_node_info(sbi, ino, &ni);
+			if (!IS_VALID_NID(sbi, ino) ||
+					!IS_VALID_BLK_ADDR(sbi, ni.blk_addr))
+				return -EINVAL;
+		}
+		ret = fsck_chk_node_blk(sbi, NULL, ino,
+				F2FS_FT_REG_FILE, TYPE_INODE, &blk_cnt, NULL);
+		if (ret)
+			ASSERT_MSG("[0x%x] wrong orphan inode", ino);
+	}
+	return ret;
+}
+
+int fsck_chk_quota_files(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	enum quota_type qtype;
+	f2fs_ino_t ino;
+	int ret = 0;
+	int needs_writeout;
+
+	/* Return if quota feature is disabled */
+	if (!fsck->qctx)
+		return 0;
+
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		ino = sb->qf_ino[qtype];
+		if (!ino)
+			continue;
+
+	        DBG(1, "Checking Quota file ([%3d] ino [0x%x])\n", qtype, ino);
+		needs_writeout = 0;
+		ret = quota_compare_and_update(sbi, qtype, &needs_writeout,
+						c.preserve_limits);
+		if (ret == 0 && needs_writeout == 0) {
+			DBG(1, "OK\n");
+			continue;
+		}
+
+		/* Something is wrong */
+		if (c.fix_on) {
+			DBG(0, "Fixing Quota file ([%3d] ino [0x%x])\n",
+							qtype, ino);
+			f2fs_filesize_update(sbi, ino, 0);
+			ret = quota_write_inode(sbi, qtype);
+			if (!ret) {
+				c.bug_on = 1;
+				DBG(1, "OK\n");
+			} else {
+				ASSERT_MSG("Unable to write quota file");
+			}
+		} else {
+			ASSERT_MSG("Quota file is missing or invalid"
+					" quota file content found.");
+		}
+	}
+	return ret;
+}
+
 int fsck_chk_meta(struct f2fs_sb_info *sbi)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
@@ -1587,15 +1764,7 @@
 	if (fsck_chk_orphan_node(sbi))
 		return -EINVAL;
 
-	if (fsck->nat_valid_inode_cnt != le32_to_cpu(cp->valid_inode_count)) {
-		ASSERT_MSG("valid inode does not match: nat_valid_inode_cnt %u,"
-				" valid_inode_count %u",
-				fsck->nat_valid_inode_cnt,
-				le32_to_cpu(cp->valid_inode_count));
-		return -EINVAL;
-	}
-
-	/*check nat entry with sit_area_bitmap*/
+	/* 5. check nat entry -- must be done before quota check */
 	for (i = 0; i < fsck->nr_nat_entries; i++) {
 		u32 blk = le32_to_cpu(fsck->entries[i].block_addr);
 		nid_t ino = le32_to_cpu(fsck->entries[i].ino);
@@ -1635,6 +1804,18 @@
 		}
 	}
 
+	/* 6. check quota inode simply */
+	if (fsck_chk_quota_node(sbi))
+		return -EINVAL;
+
+	if (fsck->nat_valid_inode_cnt != le32_to_cpu(cp->valid_inode_count)) {
+		ASSERT_MSG("valid inode does not match: nat_valid_inode_cnt %u,"
+				" valid_inode_count %u",
+				fsck->nat_valid_inode_cnt,
+				le32_to_cpu(cp->valid_inode_count));
+		return -EINVAL;
+	}
+
 	return 0;
 }
 
@@ -1755,6 +1936,7 @@
 	set_cp(cp_pack_total_block_count, 8 + orphan_blks + get_sb(cp_payload));
 
 	flags = update_nat_bits_flags(sb, cp, flags);
+	flags |= CP_NOCRC_RECOVERY_FLAG;
 	set_cp(ckpt_flags, flags);
 
 	set_cp(free_segment_count, get_free_segments(sbi));
@@ -1995,9 +2177,11 @@
 			fix_hard_links(sbi);
 			fix_nat_entries(sbi);
 			rewrite_sit_area_bitmap(sbi);
-			move_curseg_info(sbi, SM_I(sbi)->main_blkaddr);
-			write_curseg_info(sbi);
-			flush_curseg_sit_entries(sbi);
+			if (check_curseg_offset(sbi)) {
+				move_curseg_info(sbi, SM_I(sbi)->main_blkaddr);
+				write_curseg_info(sbi);
+				flush_curseg_sit_entries(sbi);
+			}
 			fix_checkpoint(sbi);
 		} else if (is_set_ckpt_flags(cp, CP_FSCK_FLAG)) {
 			write_checkpoint(sbi);
@@ -2009,6 +2193,10 @@
 void fsck_free(struct f2fs_sb_info *sbi)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+
+	if (fsck->qctx)
+		quota_release_context(&fsck->qctx);
+
 	if (fsck->main_area_bitmap)
 		free(fsck->main_area_bitmap);
 
diff --git a/fsck/fsck.h b/fsck/fsck.h
index c54eccb..d635c5a 100644
--- a/fsck/fsck.h
+++ b/fsck/fsck.h
@@ -13,6 +13,8 @@
 
 #include "f2fs.h"
 
+struct quota_ctx;
+
 #define FSCK_UNMATCHED_EXTENT		0x00000001
 
 enum {
@@ -52,6 +54,7 @@
 	u32 pp_ino;		/*parent parent ino*/
 	struct extent_info ei;
 	u32 last_blk;
+	u32 i_namelen;  /* dentry namelen */
 };
 
 struct f2fs_fsck {
@@ -85,6 +88,8 @@
 	u32 dentry_depth;
 	struct f2fs_nat_entry *entries;
 	u32 nat_valid_inode_cnt;
+
+	struct quota_ctx *qctx;
 };
 
 #define BLOCK_SZ		4096
@@ -118,11 +123,13 @@
 struct selabel_handle;
 
 extern int fsck_chk_orphan_node(struct f2fs_sb_info *);
+extern int fsck_chk_quota_node(struct f2fs_sb_info *);
+extern int fsck_chk_quota_files(struct f2fs_sb_info *);
 extern int fsck_chk_node_blk(struct f2fs_sb_info *, struct f2fs_inode *, u32,
 		enum FILE_TYPE, enum NODE_TYPE, u32 *,
 		struct child_info *);
 extern void fsck_chk_inode_blk(struct f2fs_sb_info *, u32, enum FILE_TYPE,
-		struct f2fs_node *, u32 *, struct node_info *);
+		struct f2fs_node *, u32 *, struct node_info *, struct child_info *);
 extern int fsck_chk_dnode_blk(struct f2fs_sb_info *, struct f2fs_inode *,
 		u32, enum FILE_TYPE, struct f2fs_node *, u32 *,
 		struct child_info *, struct node_info *);
@@ -141,8 +148,8 @@
 
 extern void update_free_segments(struct f2fs_sb_info *);
 void print_cp_state(u32);
-extern void print_node_info(struct f2fs_node *, int);
-extern void print_inode_info(struct f2fs_inode *, int);
+extern void print_node_info(struct f2fs_sb_info *, struct f2fs_node *, int);
+extern void print_inode_info(struct f2fs_sb_info *, struct f2fs_node *, int);
 extern struct seg_entry *get_seg_entry(struct f2fs_sb_info *, unsigned int);
 extern struct f2fs_summary_block *get_sum_block(struct f2fs_sb_info *,
 				unsigned int, int *);
@@ -154,6 +161,8 @@
 extern void rewrite_sit_area_bitmap(struct f2fs_sb_info *);
 extern void build_nat_area_bitmap(struct f2fs_sb_info *);
 extern void build_sit_area_bitmap(struct f2fs_sb_info *);
+extern int f2fs_set_main_bitmap(struct f2fs_sb_info *, u32, int);
+extern int f2fs_set_sit_bitmap(struct f2fs_sb_info *, u32);
 extern void fsck_init(struct f2fs_sb_info *);
 extern int fsck_verify(struct f2fs_sb_info *);
 extern void fsck_free(struct f2fs_sb_info *);
@@ -208,8 +217,9 @@
 int f2fs_resize(struct f2fs_sb_info *);
 
 /* sload.c */
-int f2fs_sload(struct f2fs_sb_info *, const char *, const char *,
-		const char *, struct selabel_handle *);
+int f2fs_sload(struct f2fs_sb_info *);
+
+/* segment.c */
 void reserve_new_block(struct f2fs_sb_info *, block_t *,
 					struct f2fs_summary *, int);
 void new_data_block(struct f2fs_sb_info *, void *,
@@ -219,12 +229,22 @@
 void set_data_blkaddr(struct dnode_of_data *);
 block_t new_node_block(struct f2fs_sb_info *,
 					struct dnode_of_data *, unsigned int);
+
+/* segment.c */
+u64 f2fs_read(struct f2fs_sb_info *, nid_t, u8 *, u64, pgoff_t);
+u64 f2fs_write(struct f2fs_sb_info *, nid_t, u8 *, u64, pgoff_t);
+void f2fs_filesize_update(struct f2fs_sb_info *, nid_t, u64);
+
 void get_dnode_of_data(struct f2fs_sb_info *, struct dnode_of_data *,
 					pgoff_t, int);
+void make_dentry_ptr(struct f2fs_dentry_ptr *, struct f2fs_node *, void *, int);
 int f2fs_create(struct f2fs_sb_info *, struct dentry *);
 int f2fs_mkdir(struct f2fs_sb_info *, struct dentry *);
 int f2fs_symlink(struct f2fs_sb_info *, struct dentry *);
 int inode_set_selinux(struct f2fs_sb_info *, u32, const char *);
 int f2fs_find_path(struct f2fs_sb_info *, char *, nid_t *);
 
+/* xattr.c */
+void *read_all_xattrs(struct f2fs_sb_info *, struct f2fs_node *);
+
 #endif /* _FSCK_H_ */
diff --git a/fsck/main.c b/fsck/main.c
index c9411eb..f35d01d 100644
--- a/fsck/main.c
+++ b/fsck/main.c
@@ -18,9 +18,34 @@
 #include "fsck.h"
 #include <libgen.h>
 #include <ctype.h>
+#include <getopt.h>
+#include "quotaio.h"
 
 struct f2fs_fsck gfsck;
 
+#ifdef WITH_ANDROID
+#include <sparse/sparse.h>
+extern struct sparse_file *f2fs_sparse_file;
+#endif
+
+static char *absolute_path(const char *file)
+{
+	char *ret;
+	char cwd[PATH_MAX];
+
+	if (file[0] != '/') {
+		if (getcwd(cwd, PATH_MAX) == NULL) {
+			fprintf(stderr, "Failed to getcwd\n");
+			exit(EXIT_FAILURE);
+		}
+		ret = malloc(strlen(cwd) + 1 + strlen(file) + 1);
+		if (ret)
+			sprintf(ret, "%s/%s", cwd, file);
+	} else
+		ret = strdup(file);
+	return ret;
+}
+
 void fsck_usage()
 {
 	MSG(0, "\nUsage: fsck.f2fs [options] device\n");
@@ -29,7 +54,11 @@
 	MSG(0, "  -d debug level [default:0]\n");
 	MSG(0, "  -f check/fix entire partition\n");
 	MSG(0, "  -p preen mode [default:0 the same as -a [0|1]]\n");
+	MSG(0, "  -S sparse_mode\n");
 	MSG(0, "  -t show directory tree\n");
+	MSG(0, "  -q preserve quota limits\n");
+	MSG(0, "  -y fix all the time\n");
+	MSG(0, "  --dry-run do not really fix corruptions\n");
 	exit(1);
 }
 
@@ -41,6 +70,7 @@
 	MSG(0, "  -i inode no (hex)\n");
 	MSG(0, "  -n [NAT dump segno from #1~#2 (decimal), for all 0~-1]\n");
 	MSG(0, "  -s [SIT dump segno from #1~#2 (decimal), for all 0~-1]\n");
+	MSG(0, "  -S sparse_mode\n");
 	MSG(0, "  -a [SSA dump segno from #1~#2 (decimal), for all 0~-1]\n");
 	MSG(0, "  -b blk_addr (in 4KB)\n");
 
@@ -53,6 +83,7 @@
 	MSG(0, "[options]:\n");
 	MSG(0, "  -d debug level [default:0]\n");
 	MSG(0, "  -s start block address [default: main_blkaddr]\n");
+	MSG(0, "  -S sparse_mode\n");
 	MSG(0, "  -l length [default:512 (2MB)]\n");
 	MSG(0, "  -t target block address [default: main_blkaddr + 2MB]\n");
 	MSG(0, "  -i set direction as shrink [default: expand]\n");
@@ -72,8 +103,13 @@
 {
 	MSG(0, "\nUsage: sload.f2fs [options] device\n");
 	MSG(0, "[options]:\n");
+	MSG(0, "  -C fs_config\n");
 	MSG(0, "  -f source directory [path of the source directory]\n");
+	MSG(0, "  -p product out directory\n");
+	MSG(0, "  -s file_contexts\n");
+	MSG(0, "  -S sparse_mode\n");
 	MSG(0, "  -t mount point [prefix of target fs path, default:/]\n");
+	MSG(0, "  -T timestamp\n");
 	MSG(0, "  -d debug level [default:0]\n");
 	exit(1);
 }
@@ -109,18 +145,36 @@
 	int option = 0;
 	char *prog = basename(argv[0]);
 	int err = NOERROR;
+#ifdef WITH_ANDROID
+	int i;
 
+	/* Allow prog names (e.g, sload_f2fs, fsck_f2fs, etc) */
+	for (i = 0; i < strlen(prog); i++) {
+		if (prog[i] == '_')
+			prog[i] = '.';
+	}
+#endif
 	if (argc < 2) {
 		MSG(0, "\tError: Device not specified\n");
 		error_out(prog);
 	}
 
 	if (!strcmp("fsck.f2fs", prog)) {
-		const char *option_string = ":ad:fp:t";
+		const char *option_string = ":ad:fp:q:Sty";
+		int opt = 0;
+		struct option long_opt[] = {
+			{"dry-run", no_argument, 0, 1},
+			{0, 0, 0, 0}
+		};
 
 		c.func = FSCK;
-		while ((option = getopt(argc, argv, option_string)) != EOF) {
+		while ((option = getopt_long(argc, argv, option_string,
+						long_opt, &opt)) != EOF) {
 			switch (option) {
+			case 1:
+				c.dry_run = 1;
+				MSG(0, "Info: Dry run\n");
+				break;
 			case 'a':
 				c.auto_fix = 1;
 				MSG(0, "Info: Fix the reported corruption.\n");
@@ -160,14 +214,21 @@
 				MSG(0, "Info: Debug level = %d\n", c.dbg_lv);
 				break;
 			case 'f':
+			case 'y':
 				c.fix_on = 1;
 				MSG(0, "Info: Force to fix corruption\n");
 				break;
+			case 'q':
+				c.preserve_limits = atoi(optarg);
+				MSG(0, "Info: Preserve quota limits = %d\n",
+					c.preserve_limits);
+				break;
+			case 'S':
+				c.sparse_mode = 1;
+				break;
 			case 't':
 				c.show_dentry = 1;
 				break;
-
-
 			case ':':
 				if (optopt == 'p') {
 					MSG(0, "Info: Use default preen mode\n");
@@ -189,7 +250,7 @@
 				break;
 		}
 	} else if (!strcmp("dump.f2fs", prog)) {
-		const char *option_string = "d:i:n:s:a:b:";
+		const char *option_string = "d:i:n:s:Sa:b:";
 		static struct dump_option dump_opt = {
 			.nid = 0,	/* default root ino */
 			.start_nat = -1,
@@ -233,6 +294,9 @@
 							&dump_opt.start_sit,
 							&dump_opt.end_sit);
 				break;
+			case 'S':
+				c.sparse_mode = 1;
+				break;
 			case 'a':
 				ret = sscanf(optarg, "%d~%d",
 							&dump_opt.start_ssa,
@@ -257,7 +321,7 @@
 
 		c.private = &dump_opt;
 	} else if (!strcmp("defrag.f2fs", prog)) {
-		const char *option_string = "d:s:l:t:i";
+		const char *option_string = "d:s:Sl:t:i";
 
 		c.func = DEFRAG;
 		while ((option = getopt(argc, argv, option_string)) != EOF) {
@@ -281,6 +345,9 @@
 					ret = sscanf(optarg, "%"PRIx64"",
 							&c.defrag_start);
 				break;
+			case 'S':
+				c.sparse_mode = 1;
+				break;
 			case 'l':
 				if (strncmp(optarg, "0x", 2))
 					ret = sscanf(optarg, "%"PRIu64"",
@@ -342,11 +409,20 @@
 				break;
 		}
 	} else if (!strcmp("sload.f2fs", prog)) {
-		const char *option_string = "d:f:t:";
+		const char *option_string = "C:d:f:p:s:St:T:";
+#ifdef HAVE_LIBSELINUX
+		int max_nr_opt = (int)sizeof(c.seopt_file) /
+			sizeof(c.seopt_file[0]);
+		char *token;
+#endif
+		char *p;
 
 		c.func = SLOAD;
 		while ((option = getopt(argc, argv, option_string)) != EOF) {
 			switch (option) {
+			case 'C':
+				c.fs_config_file = absolute_path(optarg);
+				break;
 			case 'd':
 				if (!is_digits(optarg)) {
 					err = EWRONG_OPT;
@@ -357,11 +433,40 @@
 						c.dbg_lv);
 				break;
 			case 'f':
-				c.from_dir = (char *)optarg;
+				c.from_dir = absolute_path(optarg);
+				break;
+			case 'p':
+				c.target_out_dir = absolute_path(optarg);
+				break;
+			case 's':
+#ifdef HAVE_LIBSELINUX
+				token = strtok(optarg, ",");
+				while (token) {
+					if (c.nr_opt == max_nr_opt) {
+						MSG(0, "\tError: Expected at most %d selinux opts\n",
+										max_nr_opt);
+						error_out(prog);
+					}
+					c.seopt_file[c.nr_opt].type =
+								SELABEL_OPT_PATH;
+					c.seopt_file[c.nr_opt].value =
+								absolute_path(token);
+					c.nr_opt++;
+					token = strtok(NULL, ",");
+				}
+#else
+				MSG(0, "Info: Not support selinux opts\n");
+#endif
+				break;
+			case 'S':
+				c.sparse_mode = 1;
 				break;
 			case 't':
 				c.mount_point = (char *)optarg;
 				break;
+			case 'T':
+				c.fixed_time = strtoul(optarg, &p, 0);
+				break;
 			default:
 				err = EUNKNOWN_OPT;
 				break;
@@ -407,6 +512,7 @@
 	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
 	u32 flag = le32_to_cpu(ckpt->ckpt_flags);
 	u32 blk_cnt;
+	errcode_t ret;
 
 	fsck_init(sbi);
 
@@ -429,7 +535,7 @@
 				c.fix_on = 1;
 			break;
 		}
-	} else {
+	} else if (c.preen_mode) {
 		/*
 		 * we can hit this in 3 situations:
 		 *  1. fsck -f, fix_on has already been set to 1 when
@@ -443,12 +549,23 @@
 		c.fix_on = 1;
 	}
 
-	fsck_chk_orphan_node(sbi);
+	fsck_chk_quota_node(sbi);
 
 	/* Traverse all block recursively from root inode */
 	blk_cnt = 1;
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_QUOTA_INO)) {
+		ret = quota_init_context(sbi);
+		if (ret) {
+			ASSERT_MSG("quota_init_context failure: %d", ret);
+			return;
+		}
+	}
+	fsck_chk_orphan_node(sbi);
 	fsck_chk_node_blk(sbi, NULL, sbi->root_ino_num,
 			F2FS_FT_DIR, TYPE_INODE, &blk_cnt, NULL);
+	fsck_chk_quota_files(sbi);
+
 	fsck_verify(sbi);
 	fsck_free(sbi);
 }
@@ -554,14 +671,13 @@
 static int do_sload(struct f2fs_sb_info *sbi)
 {
 	if (!c.from_dir) {
-		MSG(0, "\tError: Need source directory\n");
-		sload_usage();
-		return -1;
+		MSG(0, "Info: No source directory, but it's okay.\n");
+		return 0;
 	}
 	if (!c.mount_point)
 		c.mount_point = "/";
 
-	return f2fs_sload(sbi, c.from_dir, c.mount_point, NULL, NULL);
+	return f2fs_sload(sbi);
 }
 
 int main(int argc, char **argv)
@@ -590,6 +706,7 @@
 	/* Get device */
 	if (f2fs_get_device_info() < 0)
 		return -1;
+
 fsck_again:
 	memset(&gfsck, 0, sizeof(gfsck));
 	gfsck.sbi.fsck = &gfsck;
@@ -608,23 +725,39 @@
 	case FSCK:
 		do_fsck(sbi);
 		break;
+#ifdef WITH_DUMP
 	case DUMP:
 		do_dump(sbi);
 		break;
-#ifndef WITH_ANDROID
+#endif
+#ifdef WITH_DEFRAG
 	case DEFRAG:
 		ret = do_defrag(sbi);
 		if (ret)
 			goto out_err;
 		break;
+#endif
+#ifdef WITH_RESIZE
 	case RESIZE:
 		if (do_resize(sbi))
 			goto out_err;
 		break;
-	case SLOAD:
-		do_sload(sbi);
-		break;
 #endif
+#ifdef WITH_SLOAD
+	case SLOAD:
+		if (do_sload(sbi))
+			goto out_err;
+
+		f2fs_do_umount(sbi);
+
+		/* fsck to fix missing quota */
+		c.func = FSCK;
+		c.fix_on = 1;
+		goto fsck_again;
+#endif
+	default:
+		ERR_MSG("Wrong program name\n");
+		ASSERT(0);
 	}
 
 	f2fs_do_umount(sbi);
@@ -647,7 +780,9 @@
 				goto fsck_again;
 		}
 	}
-	f2fs_finalize_device();
+	ret = f2fs_finalize_device();
+	if (ret < 0)
+		return ret;
 
 	printf("\nDone.\n");
 	return 0;
diff --git a/fsck/mkquota.c b/fsck/mkquota.c
new file mode 100644
index 0000000..b54be08
--- /dev/null
+++ b/fsck/mkquota.c
@@ -0,0 +1,404 @@
+/*
+ * mkquota.c --- create quota files for a filesystem
+ *
+ * Aditya Kali <adityakali@google.com>
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "quotaio.h"
+#include "quotaio_v2.h"
+#include "quotaio_tree.h"
+#include "common.h"
+#include "dict.h"
+
+
+/* Needed for architectures where sizeof(int) != sizeof(void *) */
+#define UINT_TO_VOIDPTR(val)  ((void *)(intptr_t)(val))
+#define VOIDPTR_TO_UINT(ptr)  ((unsigned int)(intptr_t)(ptr))
+
+#if DEBUG_QUOTA
+static void print_dquot(const char *desc, struct dquot *dq)
+{
+	if (desc)
+		fprintf(stderr, "%s: ", desc);
+	fprintf(stderr, "%u %lld:%lld:%lld %lld:%lld:%lld\n",
+		dq->dq_id, (long long) dq->dq_dqb.dqb_curspace,
+		(long long) dq->dq_dqb.dqb_bsoftlimit,
+		(long long) dq->dq_dqb.dqb_bhardlimit,
+		(long long) dq->dq_dqb.dqb_curinodes,
+		(long long) dq->dq_dqb.dqb_isoftlimit,
+		(long long) dq->dq_dqb.dqb_ihardlimit);
+}
+#else
+#define print_dquot(...)
+#endif
+
+static void write_dquots(dict_t *dict, struct quota_handle *qh)
+{
+	dnode_t		*n;
+	struct dquot	*dq;
+
+	for (n = dict_first(dict); n; n = dict_next(dict, n)) {
+		dq = dnode_get(n);
+		if (dq) {
+			print_dquot("write", dq);
+			dq->dq_h = qh;
+			update_grace_times(dq);
+			qh->qh_ops->commit_dquot(dq);
+		}
+	}
+}
+
+errcode_t quota_write_inode(struct f2fs_sb_info *sbi, enum quota_type qtype)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	quota_ctx_t qctx = fsck->qctx;
+	struct quota_handle *h = NULL;
+	int retval = 0;
+	dict_t *dict;
+
+	if ((!qctx) || (!sb->qf_ino[qtype]))
+		return 0;
+
+	retval = quota_get_mem(sizeof(struct quota_handle), &h);
+	if (retval) {
+		log_debug("Unable to allocate quota handle");
+		goto out;
+	}
+
+	dict = qctx->quota_dict[qtype];
+	if (dict) {
+		retval = quota_file_create(sbi, h, qtype);
+		if (retval) {
+			log_debug("Cannot initialize io on quotafile");
+		} else {
+			write_dquots(dict, h);
+			quota_file_close(sbi, h, 1);
+		}
+	}
+out:
+	if (h)
+		quota_free_mem(&h);
+	return retval;
+}
+
+/******************************************************************/
+/* Helper functions for computing quota in memory.                */
+/******************************************************************/
+
+static int dict_uint_cmp(const void *a, const void *b)
+{
+	unsigned int	c, d;
+
+	c = VOIDPTR_TO_UINT(a);
+	d = VOIDPTR_TO_UINT(b);
+
+	if (c == d)
+		return 0;
+	else if (c > d)
+		return 1;
+	else
+		return -1;
+}
+
+static inline qid_t get_qid(struct f2fs_inode *inode, enum quota_type qtype)
+{
+	switch (qtype) {
+	case USRQUOTA:
+		return inode->i_uid;
+	case GRPQUOTA:
+		return inode->i_gid;
+	case PRJQUOTA:
+		return inode->i_projid;
+	default:
+		return 0;
+	}
+
+	return 0;
+}
+
+static void quota_dnode_free(dnode_t *node, void *UNUSED(context))
+{
+	void *ptr = node ? dnode_get(node) : 0;
+
+	quota_free_mem(&ptr);
+	free(node);
+}
+
+/*
+ * Set up the quota tracking data structures.
+ */
+errcode_t quota_init_context(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	errcode_t err;
+	dict_t	*dict;
+	quota_ctx_t ctx;
+	enum quota_type	qtype;
+
+	err = quota_get_mem(sizeof(struct quota_ctx), &ctx);
+	if (err) {
+		log_debug("Failed to allocate quota context");
+		return err;
+	}
+
+	memset(ctx, 0, sizeof(struct quota_ctx));
+	dict_init(&ctx->linked_inode_dict, DICTCOUNT_T_MAX, dict_uint_cmp);
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		ctx->quota_file[qtype] = NULL;
+		if (!sb->qf_ino[qtype])
+			continue;
+		err = quota_get_mem(sizeof(dict_t), &dict);
+		if (err) {
+			log_debug("Failed to allocate dictionary");
+			quota_release_context(&ctx);
+			return err;
+		}
+		ctx->quota_dict[qtype] = dict;
+		dict_init(dict, DICTCOUNT_T_MAX, dict_uint_cmp);
+		dict_set_allocator(dict, NULL, quota_dnode_free, NULL);
+	}
+	ctx->sbi = sbi;
+	fsck->qctx = ctx;
+	return 0;
+}
+
+void quota_release_context(quota_ctx_t *qctx)
+{
+	dict_t	*dict;
+	enum quota_type	qtype;
+	quota_ctx_t ctx;
+
+	if (!qctx)
+		return;
+
+	ctx = *qctx;
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		dict = ctx->quota_dict[qtype];
+		ctx->quota_dict[qtype] = 0;
+		if (dict) {
+			dict_free_nodes(dict);
+			free(dict);
+		}
+	}
+	dict_free_nodes(&ctx->linked_inode_dict);
+	*qctx = NULL;
+	free(ctx);
+}
+
+static struct dquot *get_dq(dict_t *dict, __u32 key)
+{
+	struct dquot	*dq;
+	dnode_t		*n;
+
+	n = dict_lookup(dict, UINT_TO_VOIDPTR(key));
+	if (n)
+		dq = dnode_get(n);
+	else {
+		if (quota_get_mem(sizeof(struct dquot), &dq)) {
+			log_err("Unable to allocate dquot");
+			return NULL;
+		}
+		memset(dq, 0, sizeof(struct dquot));
+		dict_alloc_insert(dict, UINT_TO_VOIDPTR(key), dq);
+		dq->dq_id = key;
+	}
+	return dq;
+}
+
+/*
+ * Called to update the blocks used by a particular inode
+ */
+void quota_data_add(quota_ctx_t qctx, struct f2fs_inode *inode, qsize_t space)
+{
+	struct dquot	*dq;
+	dict_t		*dict;
+	enum quota_type	qtype;
+
+	if (!qctx)
+		return;
+
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		dict = qctx->quota_dict[qtype];
+		if (dict) {
+			dq = get_dq(dict, get_qid(inode, qtype));
+			if (dq)
+				dq->dq_dqb.dqb_curspace += space;
+		}
+	}
+}
+
+/*
+ * Called to remove some blocks used by a particular inode
+ */
+void quota_data_sub(quota_ctx_t qctx, struct f2fs_inode *inode, qsize_t space)
+{
+	struct dquot	*dq;
+	dict_t		*dict;
+	enum quota_type	qtype;
+
+	if (!qctx)
+		return;
+
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		dict = qctx->quota_dict[qtype];
+		if (dict) {
+			dq = get_dq(dict, get_qid(inode, qtype));
+			dq->dq_dqb.dqb_curspace -= space;
+		}
+	}
+}
+
+/*
+ * Called to count the files used by an inode's user/group
+ */
+void quota_data_inodes(quota_ctx_t qctx, struct f2fs_inode *inode, int adjust)
+{
+	struct dquot	*dq;
+	dict_t		*dict; enum quota_type	qtype;
+
+	if (!qctx)
+		return;
+
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		dict = qctx->quota_dict[qtype];
+		if (dict) {
+			dq = get_dq(dict, get_qid(inode, qtype));
+			dq->dq_dqb.dqb_curinodes += adjust;
+		}
+	}
+}
+
+/*
+ * Called from fsck to count quota.
+ */
+void quota_add_inode_usage(quota_ctx_t qctx, f2fs_ino_t ino,
+		struct f2fs_inode* inode)
+{
+	if (qctx) {
+		/* Handle hard linked inodes */
+		if (inode->i_links > 1) {
+			if (dict_lookup(&qctx->linked_inode_dict,
+				UINT_TO_VOIDPTR(ino))) {
+				return;
+			}
+			dict_alloc_insert(&qctx->linked_inode_dict,
+					UINT_TO_VOIDPTR(ino), NULL);
+		}
+
+		qsize_t space = (inode->i_blocks - 1) * BLOCK_SZ;
+		quota_data_add(qctx, inode, space);
+		quota_data_inodes(qctx, inode, +1);
+	}
+}
+
+struct scan_dquots_data {
+	dict_t		*quota_dict;
+	int             update_limits; /* update limits from disk */
+	int		update_usage;
+	int		usage_is_inconsistent;
+};
+
+static int scan_dquots_callback(struct dquot *dquot, void *cb_data)
+{
+	struct scan_dquots_data *scan_data = cb_data;
+	dict_t *quota_dict = scan_data->quota_dict;
+	struct dquot *dq;
+
+	dq = get_dq(quota_dict, dquot->dq_id);
+	dq->dq_id = dquot->dq_id;
+	dq->dq_flags |= DQF_SEEN;
+
+	print_dquot("mem", dq);
+	print_dquot("dsk", dquot);
+	/* Check if there is inconsistency */
+	if (dq->dq_dqb.dqb_curspace != dquot->dq_dqb.dqb_curspace ||
+	    dq->dq_dqb.dqb_curinodes != dquot->dq_dqb.dqb_curinodes) {
+		scan_data->usage_is_inconsistent = 1;
+		log_debug("[QUOTA WARNING] Usage inconsistent for ID %u:"
+			"actual (%lld, %lld) != expected (%lld, %lld)\n",
+				dq->dq_id, (long long) dq->dq_dqb.dqb_curspace,
+				(long long) dq->dq_dqb.dqb_curinodes,
+				(long long) dquot->dq_dqb.dqb_curspace,
+				(long long) dquot->dq_dqb.dqb_curinodes);
+	}
+
+	if (scan_data->update_limits) {
+		dq->dq_dqb.dqb_ihardlimit = dquot->dq_dqb.dqb_ihardlimit;
+		dq->dq_dqb.dqb_isoftlimit = dquot->dq_dqb.dqb_isoftlimit;
+		dq->dq_dqb.dqb_bhardlimit = dquot->dq_dqb.dqb_bhardlimit;
+		dq->dq_dqb.dqb_bsoftlimit = dquot->dq_dqb.dqb_bsoftlimit;
+	}
+
+	if (scan_data->update_usage) {
+		dq->dq_dqb.dqb_curspace = dquot->dq_dqb.dqb_curspace;
+		dq->dq_dqb.dqb_curinodes = dquot->dq_dqb.dqb_curinodes;
+	}
+
+	return 0;
+}
+
+/*
+ * Compares the measured quota in qctx->quota_dict with that in the quota inode
+ * on disk and updates the limits in qctx->quota_dict. 'usage_inconsistent' is
+ * set to 1 if the supplied and on-disk quota usage values are not identical.
+ */
+errcode_t quota_compare_and_update(struct f2fs_sb_info *sbi,
+		enum quota_type qtype, int *usage_inconsistent,
+		int preserve_limits)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	quota_ctx_t qctx = fsck->qctx;
+	struct quota_handle qh;
+	struct scan_dquots_data scan_data;
+	struct dquot *dq;
+	dnode_t *n;
+	dict_t *dict = qctx->quota_dict[qtype];
+	errcode_t err = 0;
+
+	if (!dict)
+		goto out;
+
+	err = quota_file_open(sbi, &qh, qtype, 0);
+	if (err) {
+		log_debug("Open quota file failed");
+		goto out;
+	}
+
+	scan_data.quota_dict = qctx->quota_dict[qtype];
+	scan_data.update_limits = preserve_limits;
+	scan_data.update_usage = 0;
+	scan_data.usage_is_inconsistent = 0;
+	err = qh.qh_ops->scan_dquots(&qh, scan_dquots_callback, &scan_data);
+	if (err) {
+		log_debug("Error scanning dquots");
+		goto out;
+	}
+
+	for (n = dict_first(dict); n; n = dict_next(dict, n)) {
+		dq = dnode_get(n);
+		if (!dq)
+			continue;
+		if ((dq->dq_flags & DQF_SEEN) == 0) {
+			log_debug("[QUOTA WARNING] "
+				"Missing quota entry ID %d\n", dq->dq_id);
+			scan_data.usage_is_inconsistent = 1;
+		}
+	}
+	*usage_inconsistent = scan_data.usage_is_inconsistent;
+
+out:
+	return err;
+}
+
diff --git a/fsck/mount.c b/fsck/mount.c
index a0b0bea..61ea0ea 100644
--- a/fsck/mount.c
+++ b/fsck/mount.c
@@ -9,7 +9,24 @@
  * published by the Free Software Foundation.
  */
 #include "fsck.h"
+#include "xattr.h"
 #include <locale.h>
+#ifdef HAVE_LINUX_POSIX_ACL_H
+#include <linux/posix_acl.h>
+#endif
+#ifdef HAVE_SYS_ACL_H
+#include <sys/acl.h>
+#endif
+
+#ifndef ACL_UNDEFINED_TAG
+#define ACL_UNDEFINED_TAG	(0x00)
+#define ACL_USER_OBJ		(0x01)
+#define ACL_USER		(0x02)
+#define ACL_GROUP_OBJ		(0x04)
+#define ACL_GROUP		(0x08)
+#define ACL_MASK		(0x10)
+#define ACL_OTHER		(0x20)
+#endif
 
 u32 get_free_segments(struct f2fs_sb_info *sbi)
 {
@@ -30,17 +47,138 @@
 	char *progress = "-*|*-";
 	static int i = 0;
 
+	if (c.dbg_lv)
+		return;
+
 	MSG(0, "\r [ %c ] Free segments: 0x%x", progress[i % 5], get_free_segments(sbi));
 	fflush(stdout);
 	i++;
 }
 
-void print_inode_info(struct f2fs_inode *inode, int name)
+#if defined(HAVE_LINUX_POSIX_ACL_H) || defined(HAVE_SYS_ACL_H)
+void print_acl(char *value, int size)
 {
+	struct f2fs_acl_header *hdr = (struct f2fs_acl_header *)value;
+	struct f2fs_acl_entry *entry = (struct f2fs_acl_entry *)(hdr + 1);
+	const char *end = value + size;
+	int i, count;
+
+	if (hdr->a_version != cpu_to_le32(F2FS_ACL_VERSION)) {
+		MSG(0, "Invalid ACL version [0x%x : 0x%x]\n",
+				le32_to_cpu(hdr->a_version), F2FS_ACL_VERSION);
+		return;
+	}
+
+	count = f2fs_acl_count(size);
+	if (count <= 0) {
+		MSG(0, "Invalid ACL value size %d\n", size);
+		return;
+	}
+
+	for (i = 0; i < count; i++) {
+		if ((char *)entry > end) {
+			MSG(0, "Invalid ACL entries count %d\n", count);
+			return;
+		}
+
+		switch (le16_to_cpu(entry->e_tag)) {
+		case ACL_USER_OBJ:
+		case ACL_GROUP_OBJ:
+		case ACL_MASK:
+		case ACL_OTHER:
+			MSG(0, "tag:0x%x perm:0x%x\n",
+					le16_to_cpu(entry->e_tag),
+					le16_to_cpu(entry->e_perm));
+			entry = (struct f2fs_acl_entry *)((char *)entry +
+					sizeof(struct f2fs_acl_entry_short));
+			break;
+		case ACL_USER:
+			MSG(0, "tag:0x%x perm:0x%x uid:%u\n",
+					le16_to_cpu(entry->e_tag),
+					le16_to_cpu(entry->e_perm),
+					le32_to_cpu(entry->e_id));
+			entry = (struct f2fs_acl_entry *)((char *)entry +
+					sizeof(struct f2fs_acl_entry));
+			break;
+		case ACL_GROUP:
+			MSG(0, "tag:0x%x perm:0x%x gid:%u\n",
+					le16_to_cpu(entry->e_tag),
+					le16_to_cpu(entry->e_perm),
+					le32_to_cpu(entry->e_id));
+			entry = (struct f2fs_acl_entry *)((char *)entry +
+					sizeof(struct f2fs_acl_entry));
+			break;
+		default:
+			MSG(0, "Unknown ACL tag 0x%x\n",
+					le16_to_cpu(entry->e_tag));
+			return;
+		}
+	}
+}
+#else
+#define print_acl(value, size) do {		\
+	int i;					\
+	for (i = 0; i < size; i++)		\
+		MSG(0, "%02X", value[i]);	\
+	MSG(0, "\n");				\
+} while (0)
+#endif
+
+void print_xattr_entry(struct f2fs_xattr_entry *ent)
+{
+	char *value = (char *)(ent->e_name + le16_to_cpu(ent->e_name_len));
+	struct fscrypt_context *ctx;
+	int i;
+
+	MSG(0, "\nxattr: e_name_index:%d e_name:", ent->e_name_index);
+	for (i = 0; i < le16_to_cpu(ent->e_name_len); i++)
+		MSG(0, "%c", ent->e_name[i]);
+	MSG(0, " e_name_len:%d e_value_size:%d e_value:\n",
+			ent->e_name_len, le16_to_cpu(ent->e_value_size));
+
+	switch (ent->e_name_index) {
+	case F2FS_XATTR_INDEX_POSIX_ACL_ACCESS:
+	case F2FS_XATTR_INDEX_POSIX_ACL_DEFAULT:
+		print_acl(value, le16_to_cpu(ent->e_value_size));
+		break;
+	case F2FS_XATTR_INDEX_USER:
+	case F2FS_XATTR_INDEX_SECURITY:
+	case F2FS_XATTR_INDEX_TRUSTED:
+	case F2FS_XATTR_INDEX_LUSTRE:
+		for (i = 0; i < le16_to_cpu(ent->e_value_size); i++)
+			MSG(0, "%02X", value[i]);
+		MSG(0, "\n");
+		break;
+	case F2FS_XATTR_INDEX_ENCRYPTION:
+		ctx = (struct fscrypt_context *)value;
+		MSG(0, "format: %d\n", ctx->format);
+		MSG(0, "contents_encryption_mode: 0x%x\n", ctx->contents_encryption_mode);
+		MSG(0, "filenames_encryption_mode: 0x%x\n", ctx->filenames_encryption_mode);
+		MSG(0, "flags: 0x%x\n", ctx->flags);
+		MSG(0, "master_key_descriptor: ");
+		for (i = 0; i < FS_KEY_DESCRIPTOR_SIZE; i++)
+			MSG(0, "%02X", ctx->master_key_descriptor[i]);
+		MSG(0, "\nnonce: ");
+		for (i = 0; i < FS_KEY_DERIVATION_NONCE_SIZE; i++)
+			MSG(0, "%02X", ctx->nonce[i]);
+		MSG(0, "\n");
+		break;
+	default:
+		break;
+	}
+}
+
+void print_inode_info(struct f2fs_sb_info *sbi,
+			struct f2fs_node *node, int name)
+{
+	struct f2fs_inode *inode = &node->i;
+	void *xattr_addr;
+	struct f2fs_xattr_entry *ent;
 	unsigned char en[F2FS_NAME_LEN + 1];
 	unsigned int i = 0;
 	int namelen = le32_to_cpu(inode->i_namelen);
 	int enc_name = file_enc_name(inode);
+	int ofs = __get_extra_isize(inode);
 
 	namelen = convert_encrypted_name(inode->i_name, namelen, en, enc_name);
 	en[namelen] = '\0';
@@ -87,36 +225,56 @@
 			le32_to_cpu(inode->i_ext.blk_addr),
 			le32_to_cpu(inode->i_ext.len));
 
-	DISP_u32(inode, i_addr[0]);	/* Pointers to data blocks */
-	DISP_u32(inode, i_addr[1]);	/* Pointers to data blocks */
-	DISP_u32(inode, i_addr[2]);	/* Pointers to data blocks */
-	DISP_u32(inode, i_addr[3]);	/* Pointers to data blocks */
-
-	for (i = 4; i < ADDRS_PER_INODE(inode); i++) {
-		if (inode->i_addr[i] != 0x0) {
-			printf("i_addr[0x%x] points data block\r\t\t[0x%4x]\n",
-					i, le32_to_cpu(inode->i_addr[i]));
-			break;
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
+		DISP_u16(inode, i_extra_isize);
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_FLEXIBLE_INLINE_XATTR))
+			DISP_u16(inode, i_inline_xattr_size);
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_PRJQUOTA))
+			DISP_u32(inode, i_projid);
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+			DISP_u32(inode, i_inode_checksum);
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CRTIME)) {
+			DISP_u64(inode, i_crtime);
+			DISP_u32(inode, i_crtime_nsec);
 		}
 	}
 
+	DISP_u32(inode, i_addr[ofs]);		/* Pointers to data blocks */
+	DISP_u32(inode, i_addr[ofs + 1]);	/* Pointers to data blocks */
+	DISP_u32(inode, i_addr[ofs + 2]);	/* Pointers to data blocks */
+	DISP_u32(inode, i_addr[ofs + 3]);	/* Pointers to data blocks */
+
+	for (i = ofs + 3; i < ADDRS_PER_INODE(inode); i++) {
+		if (inode->i_addr[i] == 0x0)
+			break;
+		printf("i_addr[0x%x] points data block\t\t[0x%4x]\n",
+				i, le32_to_cpu(inode->i_addr[i]));
+	}
+
 	DISP_u32(inode, i_nid[0]);	/* direct */
 	DISP_u32(inode, i_nid[1]);	/* direct */
 	DISP_u32(inode, i_nid[2]);	/* indirect */
 	DISP_u32(inode, i_nid[3]);	/* indirect */
 	DISP_u32(inode, i_nid[4]);	/* double indirect */
 
+	xattr_addr = read_all_xattrs(sbi, node);
+	list_for_each_xattr(ent, xattr_addr) {
+		print_xattr_entry(ent);
+	}
+	free(xattr_addr);
+
 	printf("\n");
 }
 
-void print_node_info(struct f2fs_node *node_block, int verbose)
+void print_node_info(struct f2fs_sb_info *sbi,
+			struct f2fs_node *node_block, int verbose)
 {
 	nid_t ino = le32_to_cpu(node_block->footer.ino);
 	nid_t nid = le32_to_cpu(node_block->footer.nid);
 	/* Is this inode? */
 	if (ino == nid) {
 		DBG(verbose, "Node ID [0x%x:%u] is inode\n", nid, nid);
-		print_inode_info(&node_block->i, verbose);
+		print_inode_info(sbi, node_block, verbose);
 	} else {
 		int i;
 		u32 *dump_blk = (u32 *)node_block;
@@ -247,6 +405,16 @@
 void print_cp_state(u32 flag)
 {
 	MSG(0, "Info: checkpoint state = %x : ", flag);
+	if (flag & CP_NOCRC_RECOVERY_FLAG)
+		MSG(0, "%s", " allow_nocrc");
+	if (flag & CP_TRIMMED_FLAG)
+		MSG(0, "%s", " trimmed");
+	if (flag & CP_NAT_BITS_FLAG)
+		MSG(0, "%s", " nat_bits");
+	if (flag & CP_CRC_RECOVERY_FLAG)
+		MSG(0, "%s", " crc");
+	if (flag & CP_FASTBOOT_FLAG)
+		MSG(0, "%s", " fastboot");
 	if (flag & CP_FSCK_FLAG)
 		MSG(0, "%s", " fsck");
 	if (flag & CP_ERROR_FLAG)
@@ -255,12 +423,6 @@
 		MSG(0, "%s", " compacted_summary");
 	if (flag & CP_ORPHAN_PRESENT_FLAG)
 		MSG(0, "%s", " orphan_inodes");
-	if (flag & CP_FASTBOOT_FLAG)
-		MSG(0, "%s", " fastboot");
-	if (flag & CP_NAT_BITS_FLAG)
-		MSG(0, "%s", " nat_bits");
-	if (flag & CP_TRIMMED_FLAG)
-		MSG(0, "%s", " trimmed");
 	if (flag & CP_UMOUNT_FLAG)
 		MSG(0, "%s", " unmount");
 	else
@@ -277,8 +439,29 @@
 	if (f & cpu_to_le32(F2FS_FEATURE_ENCRYPT)) {
 		MSG(0, "%s", " encrypt");
 	}
+	if (f & cpu_to_le32(F2FS_FEATURE_VERITY)) {
+		MSG(0, "%s", " verity");
+	}
 	if (f & cpu_to_le32(F2FS_FEATURE_BLKZONED)) {
-		MSG(0, "%s", " zoned block device");
+		MSG(0, "%s", " blkzoned");
+	}
+	if (f & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
+		MSG(0, "%s", " extra_attr");
+	}
+	if (f & cpu_to_le32(F2FS_FEATURE_PRJQUOTA)) {
+		MSG(0, "%s", " project_quota");
+	}
+	if (f & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM)) {
+		MSG(0, "%s", " inode_checksum");
+	}
+	if (f & cpu_to_le32(F2FS_FEATURE_FLEXIBLE_INLINE_XATTR)) {
+		MSG(0, "%s", " flexible_inline_xattr");
+	}
+	if (f & cpu_to_le32(F2FS_FEATURE_QUOTA_INO)) {
+		MSG(0, "%s", " quota_ino");
+	}
+	if (f & cpu_to_le32(F2FS_FEATURE_INODE_CRTIME)) {
+		MSG(0, "%s", " inode_crtime");
 	}
 	MSG(0, "\n");
 	MSG(0, "Info: superblock encrypt level = %d, salt = ",
@@ -421,6 +604,7 @@
 int validate_super_block(struct f2fs_sb_info *sbi, int block)
 {
 	u64 offset;
+	char buf[F2FS_BLKSIZE];
 
 	sbi->raw_super = malloc(sizeof(struct f2fs_super_block));
 
@@ -429,16 +613,19 @@
 	else
 		offset = F2FS_BLKSIZE + F2FS_SUPER_OFFSET;
 
-	if (dev_read(sbi->raw_super, offset, sizeof(struct f2fs_super_block)))
+	if (dev_read_block(buf, block))
 		return -1;
 
+	memcpy(sbi->raw_super, buf + F2FS_SUPER_OFFSET,
+					sizeof(struct f2fs_super_block));
+
 	if (!sanity_check_raw_super(sbi->raw_super, offset)) {
 		/* get kernel version */
 		if (c.kd >= 0) {
 			dev_read_version(c.version, 0, VERSION_LEN);
 			get_kernel_version(c.version);
 		} else {
-			memset(c.version, 0, VERSION_LEN);
+			get_kernel_uname_version(c.version);
 		}
 
 		/* build sb version */
@@ -717,12 +904,12 @@
 	struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_HOT_DATA);
 	struct f2fs_summary_block *sum = curseg->sum_blk;
 	struct f2fs_journal *journal = &sum->journal;
-	struct f2fs_nat_block nat_block;
+	struct f2fs_nat_block *nat_block;
 	block_t start_blk;
 	nid_t nid;
 	int i;
 
-	if (!(c.func == SLOAD))
+	if (!(c.func == SLOAD || c.func == FSCK))
 		return 0;
 
 	nm_i->nid_bitmap = (char *)calloc(nid_bitmap_size, 1);
@@ -732,18 +919,22 @@
 	/* arbitrarily set 0 bit */
 	f2fs_set_bit(0, nm_i->nid_bitmap);
 
-	memset((void *)&nat_block, 0, sizeof(struct f2fs_nat_block));
+	nat_block = malloc(F2FS_BLKSIZE);
+	if (!nat_block) {
+		free(nm_i->nid_bitmap);
+		return -ENOMEM;
+	}
 
 	for (nid = 0; nid < nm_i->max_nid; nid++) {
 		if (!(nid % NAT_ENTRY_PER_BLOCK)) {
 			int ret;
 
 			start_blk = current_nat_addr(sbi, nid);
-			ret = dev_read_block((void *)&nat_block, start_blk);
+			ret = dev_read_block(nat_block, start_blk);
 			ASSERT(ret >= 0);
 		}
 
-		if (nat_block.entries[nid % NAT_ENTRY_PER_BLOCK].block_addr)
+		if (nat_block->entries[nid % NAT_ENTRY_PER_BLOCK].block_addr)
 			f2fs_set_bit(nid, nm_i->nid_bitmap);
 	}
 
@@ -755,6 +946,7 @@
 		if (addr != NULL_ADDR)
 			f2fs_set_bit(nid, nm_i->nid_bitmap);
 	}
+	free(nat_block);
 	return 0;
 }
 
@@ -811,7 +1003,12 @@
 				(seg_off << get_sb(log_blocks_per_seg) << 1) +
 				(i & ((1 << get_sb(log_blocks_per_seg)) - 1)));
 
-		if (f2fs_test_bit(i, nm_i->nat_bitmap))
+		/*
+		 * Should consider new nat_blocks is larger than old
+		 * nm_i->nat_blocks, since nm_i->nat_bitmap is based on
+		 * old one.
+		 */
+		if (i < nm_i->nat_blocks && f2fs_test_bit(i, nm_i->nat_bitmap))
 			blkaddr += (1 << get_sb(log_blocks_per_seg));
 
 		ret = dev_read_block(nat_block, blkaddr);
@@ -850,14 +1047,14 @@
 	struct f2fs_checkpoint *cp = F2FS_CKPT(sbi);
 	struct f2fs_nm_info *nm_i = NM_I(sbi);
 	unsigned char *version_bitmap;
-	unsigned int nat_segs, nat_blocks;
+	unsigned int nat_segs;
 
 	nm_i->nat_blkaddr = get_sb(nat_blkaddr);
 
 	/* segment_count_nat includes pair segment so divide to 2. */
 	nat_segs = get_sb(segment_count_nat) >> 1;
-	nat_blocks = nat_segs << get_sb(log_blocks_per_seg);
-	nm_i->max_nid = NAT_ENTRY_PER_BLOCK * nat_blocks;
+	nm_i->nat_blocks = nat_segs << get_sb(log_blocks_per_seg);
+	nm_i->max_nid = NAT_ENTRY_PER_BLOCK * nm_i->nat_blocks;
 	nm_i->fcnt = 0;
 	nm_i->nat_cnt = 0;
 	nm_i->init_scan_nid = get_cp(next_free_nid);
@@ -913,10 +1110,7 @@
 	for (start = 0; start < TOTAL_SEGS(sbi); start++) {
 		sit_i->sentries[start].cur_valid_map
 			= calloc(SIT_VBLOCK_MAP_SIZE, 1);
-		sit_i->sentries[start].ckpt_valid_map
-			= calloc(SIT_VBLOCK_MAP_SIZE, 1);
-		if (!sit_i->sentries[start].cur_valid_map
-				|| !sit_i->sentries[start].ckpt_valid_map)
+		if (!sit_i->sentries[start].cur_valid_map)
 			return -ENOMEM;
 	}
 
@@ -1223,9 +1417,7 @@
 		struct f2fs_sit_entry *raw_sit)
 {
 	se->valid_blocks = GET_SIT_VBLOCKS(raw_sit);
-	se->ckpt_valid_blocks = GET_SIT_VBLOCKS(raw_sit);
 	memcpy(se->cur_valid_map, raw_sit->valid_map, SIT_VBLOCK_MAP_SIZE);
-	memcpy(se->ckpt_valid_map, raw_sit->valid_map, SIT_VBLOCK_MAP_SIZE);
 	se->type = GET_SIT_TYPE(raw_sit);
 	se->orig_type = GET_SIT_TYPE(raw_sit);
 	se->mtime = le64_to_cpu(raw_sit->mtime);
@@ -1357,8 +1549,10 @@
 
 	/* check its block address */
 	if (node_blk->footer.nid == node_blk->footer.ino) {
-		oldaddr = le32_to_cpu(node_blk->i.i_addr[ofs_in_node]);
-		node_blk->i.i_addr[ofs_in_node] = cpu_to_le32(newaddr);
+		int ofs = get_extra_isize(node_blk);
+
+		oldaddr = le32_to_cpu(node_blk->i.i_addr[ofs + ofs_in_node]);
+		node_blk->i.i_addr[ofs + ofs_in_node] = cpu_to_le32(newaddr);
 	} else {
 		oldaddr = le32_to_cpu(node_blk->dn.addr[ofs_in_node]);
 		node_blk->dn.addr[ofs_in_node] = cpu_to_le32(newaddr);
@@ -1417,8 +1611,14 @@
 void get_node_info(struct f2fs_sb_info *sbi, nid_t nid, struct node_info *ni)
 {
 	struct f2fs_nat_entry raw_nat;
-	get_nat_entry(sbi, nid, &raw_nat);
+
 	ni->nid = nid;
+	if (c.func == FSCK) {
+		node_info_from_raw_nat(ni, &(F2FS_FSCK(sbi)->entries[nid]));
+		return;
+	}
+
+	get_nat_entry(sbi, nid, &raw_nat);
 	node_info_from_raw_nat(ni, &raw_nat);
 }
 
@@ -1694,7 +1894,7 @@
 	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
 	struct seg_entry *se;
 	u32 segno;
-	u64 offset;
+	u32 offset;
 	int not_enough = 0;
 	u64 end_blkaddr = (get_sb(segment_count_main) <<
 			get_sb(log_blocks_per_seg)) + get_sb(main_blkaddr);
@@ -1845,7 +2045,7 @@
 		if (le32_to_cpu(nid_in_journal(journal, i)) == nid) {
 			memset(&nat_in_journal(journal, i), 0,
 					sizeof(struct f2fs_nat_entry));
-			FIX_MSG("Remove nid [0x%x] in nat journal\n", nid);
+			FIX_MSG("Remove nid [0x%x] in nat journal", nid);
 			return;
 		}
 	}
@@ -1858,8 +2058,15 @@
 	ret = dev_read_block(nat_block, block_addr);
 	ASSERT(ret >= 0);
 
-	memset(&nat_block->entries[entry_off], 0,
+	if (nid == F2FS_NODE_INO(sbi) || nid == F2FS_META_INO(sbi)) {
+		FIX_MSG("nid [0x%x] block_addr= 0x%x -> 0x1", nid,
+			le32_to_cpu(nat_block->entries[entry_off].block_addr));
+		nat_block->entries[entry_off].block_addr = cpu_to_le32(0x1);
+	} else {
+		memset(&nat_block->entries[entry_off], 0,
 					sizeof(struct f2fs_nat_entry));
+		FIX_MSG("Remove nid [0x%x] in NAT", nid);
+	}
 
 	ret = dev_write_block(nat_block, block_addr);
 	ASSERT(ret >= 0);
@@ -1918,13 +2125,17 @@
 		ASSERT(ret >= 0);
 	}
 
-	/* write the last cp */
-	ret = dev_write_block(cp, cp_blk_no++);
-	ASSERT(ret >= 0);
-
 	/* Write nat bits */
 	if (flags & CP_NAT_BITS_FLAG)
 		write_nat_bits(sbi, sb, cp, sbi->cur_cp);
+
+	/* in case of sudden power off */
+	ret = f2fs_fsync_device();
+	ASSERT(ret >= 0);
+
+	/* write the last cp */
+	ret = dev_write_block(cp, cp_blk_no++);
+	ASSERT(ret >= 0);
 }
 
 void build_nat_area_bitmap(struct f2fs_sb_info *sbi)
@@ -1978,13 +2189,14 @@
 
 			if ((nid + i) == F2FS_NODE_INO(sbi) ||
 					(nid + i) == F2FS_META_INO(sbi)) {
-				/* block_addr of node/meta inode should be 0x1 */
+				/*
+				 * block_addr of node/meta inode should be 0x1.
+				 * Set this bit, and fsck_verify will fix it.
+				 */
 				if (le32_to_cpu(nat_block->entries[i].block_addr) != 0x1) {
-					FIX_MSG("ino: 0x%x node/meta inode, block_addr= 0x%x -> 0x1",
+					ASSERT_MSG("\tError: ino[0x%x] block_addr[0x%x] is invalid\n",
 							nid + i, le32_to_cpu(nat_block->entries[i].block_addr));
-					nat_block->entries[i].block_addr = cpu_to_le32(0x1);
-					ret = dev_write_block(nat_block, block_addr);
-					ASSERT(ret >= 0);
+					f2fs_set_bit(nid + i, fsck->nat_area_bitmap);
 				}
 				continue;
 			}
@@ -2009,7 +2221,6 @@
 				 */
 				ASSERT_MSG("Invalid nat entry[0]: "
 					"blk_addr[0x%x]\n", ni.blk_addr);
-				c.fix_on = 1;
 				fsck->chk.valid_nat_entry_cnt--;
 			}
 
@@ -2137,13 +2348,22 @@
 	if (c.auto_fix || c.preen_mode) {
 		u32 flag = get_cp(ckpt_flags);
 
-		if (flag & CP_FSCK_FLAG)
+		if (flag & CP_FSCK_FLAG ||
+			(exist_qf_ino(sb) && (!(flag & CP_UMOUNT_FLAG) ||
+						flag & CP_ERROR_FLAG))) {
 			c.fix_on = 1;
-		else if (!c.preen_mode)
+		} else if (!c.preen_mode) {
+			print_cp_state(flag);
 			return 1;
+		}
 	}
 
 	c.bug_on = 0;
+	c.feature = sb->feature;
+
+	/* precompute checksum seed for metadata */
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+		c.chksum_seed = f2fs_cal_crc32(~0, sb->uuid, sizeof(sb->uuid));
 
 	sbi->total_valid_node_count = get_cp(valid_node_count);
 	sbi->total_valid_inode_count = get_cp(valid_inode_count);
@@ -2163,7 +2383,7 @@
 	}
 
 	/* Check nat_bits */
-	if (is_set_ckpt_flags(cp, CP_NAT_BITS_FLAG)) {
+	if (c.func != DUMP && is_set_ckpt_flags(cp, CP_NAT_BITS_FLAG)) {
 		u_int32_t nat_bits_bytes, nat_bits_blocks;
 		__le64 *kaddr;
 		u_int32_t blk;
@@ -2197,16 +2417,15 @@
 	unsigned int i;
 
 	/* free nm_info */
-	if (c.func == SLOAD)
+	if (c.func == SLOAD || c.func == FSCK)
 		free(nm_i->nid_bitmap);
 	free(nm_i->nat_bitmap);
 	free(sbi->nm_info);
 
 	/* free sit_info */
-	for (i = 0; i < TOTAL_SEGS(sbi); i++) {
+	for (i = 0; i < TOTAL_SEGS(sbi); i++)
 		free(sit_i->sentries[i].cur_valid_map);
-		free(sit_i->sentries[i].ckpt_valid_map);
-	}
+
 	free(sit_i->sit_bitmap);
 	free(sm_i->sit_info);
 
diff --git a/fsck/node.c b/fsck/node.c
index fe923e5..7f4a28b 100644
--- a/fsck/node.c
+++ b/fsck/node.c
@@ -63,7 +63,7 @@
 	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
 	struct f2fs_summary sum;
 	struct node_info ni;
-	block_t blkaddr;
+	block_t blkaddr = NULL_ADDR;
 	int type;
 
 	f2fs_inode = dn->inode_blk;
@@ -105,10 +105,10 @@
  *
  * By default, it sets inline_xattr and inline_data
  */
-static int get_node_path(unsigned long block,
+static int get_node_path(struct f2fs_node *node, long block,
 				int offset[4], unsigned int noffset[4])
 {
-	const long direct_index = DEF_ADDRS_PER_INODE_INLINE_XATTR;
+	const long direct_index = ADDRS_PER_INODE(&node->i);
 	const long direct_blks = ADDRS_PER_BLOCK;
 	const long dptrs_per_blk = NIDS_PER_BLOCK;
 	const long indirect_blks = ADDRS_PER_BLOCK * NIDS_PER_BLOCK;
@@ -191,7 +191,7 @@
 	int level, i;
 	int ret;
 
-	level = get_node_path(index, offset, noffset);
+	level = get_node_path(dn->inode_blk, index, offset, noffset);
 
 	nids[0] = dn->nid;
 	parent = dn->inode_blk;
diff --git a/fsck/node.h b/fsck/node.h
index 721e5b7..cbf7ed7 100644
--- a/fsck/node.h
+++ b/fsck/node.h
@@ -26,9 +26,14 @@
 	return ((node)->footer.nid == (node)->footer.ino);
 }
 
+static inline __le32 *blkaddr_in_inode(struct f2fs_node *node)
+{
+	return node->i.i_addr + get_extra_isize(node);
+}
+
 static inline __le32 *blkaddr_in_node(struct f2fs_node *node)
 {
-	return IS_INODE(node) ? node->i.i_addr : node->dn.addr;
+	return IS_INODE(node) ? blkaddr_in_inode(node) : node->dn.addr;
 }
 
 static inline block_t datablock_addr(struct f2fs_node *node_page,
diff --git a/fsck/quotaio.c b/fsck/quotaio.c
new file mode 100644
index 0000000..afadf56
--- /dev/null
+++ b/fsck/quotaio.c
@@ -0,0 +1,221 @@
+/** quotaio.c
+ *
+ * Generic IO operations on quotafiles
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ * Aditya Kali <adityakali@google.com> - Ported to e2fsprogs
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <assert.h>
+
+#include "common.h"
+#include "quotaio.h"
+
+static const char * const extensions[MAXQUOTAS] = {
+	[USRQUOTA] = "user",
+	[GRPQUOTA] = "group",
+	[PRJQUOTA] = "project",
+};
+
+/* Header in all newer quotafiles */
+struct disk_dqheader {
+	__le32 dqh_magic;
+	__le32 dqh_version;
+} __attribute__ ((packed));
+
+/**
+ * Convert type of quota to written representation
+ */
+const char *quota_type2name(enum quota_type qtype)
+{
+	if (qtype >= MAXQUOTAS)
+		return "unknown";
+	return extensions[qtype];
+}
+
+/*
+ * Set grace time if needed
+ */
+void update_grace_times(struct dquot *q)
+{
+	time_t now;
+
+	time(&now);
+	if (q->dq_dqb.dqb_bsoftlimit && toqb(q->dq_dqb.dqb_curspace) >
+			q->dq_dqb.dqb_bsoftlimit) {
+		if (!q->dq_dqb.dqb_btime)
+			q->dq_dqb.dqb_btime =
+				now + q->dq_h->qh_info.dqi_bgrace;
+	} else {
+		q->dq_dqb.dqb_btime = 0;
+	}
+
+	if (q->dq_dqb.dqb_isoftlimit && q->dq_dqb.dqb_curinodes >
+			q->dq_dqb.dqb_isoftlimit) {
+		if (!q->dq_dqb.dqb_itime)
+				q->dq_dqb.dqb_itime =
+					now + q->dq_h->qh_info.dqi_igrace;
+	} else {
+		q->dq_dqb.dqb_itime = 0;
+	}
+}
+
+/* Functions to read/write quota file. */
+static unsigned int quota_write_nomount(struct quota_file *qf,
+					long offset,
+					void *buf, unsigned int size)
+{
+	unsigned int written;
+
+	written = f2fs_write(qf->sbi, qf->ino, buf, size, offset);
+	if (qf->filesize < offset + written)
+		qf->filesize = offset + written;
+
+	return written;
+}
+
+static unsigned int quota_read_nomount(struct quota_file *qf, long offset,
+		void *buf, unsigned int size)
+{
+	return f2fs_read(qf->sbi, qf->ino, buf, size, offset);
+}
+
+/*
+ * Detect quota format and initialize quota IO
+ */
+errcode_t quota_file_open(struct f2fs_sb_info *sbi, struct quota_handle *h,
+			  enum quota_type qtype, int flags)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	quota_ctx_t qctx = fsck->qctx;
+	f2fs_ino_t qf_ino;
+	errcode_t err = 0;
+	int allocated_handle = 0;
+
+	if (qtype >= MAXQUOTAS)
+		return EINVAL;
+
+	qf_ino = sb->qf_ino[qtype];
+
+	if (!h) {
+		if (qctx->quota_file[qtype]) {
+			h = qctx->quota_file[qtype];
+			(void) quota_file_close(sbi, h, 0);
+		}
+		err = quota_get_mem(sizeof(struct quota_handle), &h);
+		if (err) {
+			log_err("Unable to allocate quota handle");
+			return err;
+		}
+		allocated_handle = 1;
+	}
+
+	h->qh_qf.sbi = sbi;
+	h->qh_qf.ino = qf_ino;
+	h->write = quota_write_nomount;
+	h->read = quota_read_nomount;
+	h->qh_file_flags = flags;
+	h->qh_io_flags = 0;
+	h->qh_type = qtype;
+	h->qh_fmt = QFMT_VFS_V1;
+	memset(&h->qh_info, 0, sizeof(h->qh_info));
+	h->qh_ops = &quotafile_ops_2;
+
+	if (h->qh_ops->check_file &&
+	    (h->qh_ops->check_file(h, qtype) == 0)) {
+		log_err("qh_ops->check_file failed");
+		err = EIO;
+		goto errout;
+	}
+
+	if (h->qh_ops->init_io && (h->qh_ops->init_io(h) < 0)) {
+		log_err("qh_ops->init_io failed");
+		err = EIO;
+		goto errout;
+	}
+	if (allocated_handle)
+		qctx->quota_file[qtype] = h;
+errout:
+	return err;
+}
+
+/*
+ * Create new quotafile of specified format on given filesystem
+ */
+errcode_t quota_file_create(struct f2fs_sb_info *sbi, struct quota_handle *h,
+		enum quota_type qtype)
+{
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	f2fs_ino_t qf_inum = sb->qf_ino[qtype];
+	errcode_t err = 0;
+
+	h->qh_qf.sbi = sbi;
+	h->qh_qf.ino = qf_inum;
+	h->write = quota_write_nomount;
+	h->read = quota_read_nomount;
+
+	log_debug("Creating quota ino=%u, type=%d", qf_inum, qtype);
+	h->qh_io_flags = 0;
+	h->qh_type = qtype;
+	h->qh_fmt = QFMT_VFS_V1;
+	memset(&h->qh_info, 0, sizeof(h->qh_info));
+	h->qh_ops = &quotafile_ops_2;
+
+	if (h->qh_ops->new_io && (h->qh_ops->new_io(h) < 0)) {
+		log_err("qh_ops->new_io failed");
+		err = EIO;
+	}
+
+	return err;
+}
+
+/*
+ * Close quotafile and release handle
+ */
+errcode_t quota_file_close(struct f2fs_sb_info *sbi, struct quota_handle *h,
+		int update_filesize)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	quota_ctx_t qctx = fsck->qctx;
+
+        if (h->qh_io_flags & IOFL_INFODIRTY) {
+		if (h->qh_ops->write_info && h->qh_ops->write_info(h) < 0)
+			return EIO;
+		h->qh_io_flags &= ~IOFL_INFODIRTY;
+	}
+	if (h->qh_ops->end_io && h->qh_ops->end_io(h) < 0)
+		return EIO;
+	if (update_filesize) {
+		f2fs_filesize_update(sbi, h->qh_qf.ino, h->qh_qf.filesize);
+	}
+	if (qctx->quota_file[h->qh_type] == h)
+		quota_free_mem(&qctx->quota_file[h->qh_type]);
+	return 0;
+}
+
+/*
+ * Create empty quota structure
+ */
+struct dquot *get_empty_dquot(void)
+{
+	struct dquot *dquot;
+
+	if (quota_get_memzero(sizeof(struct dquot), &dquot)) {
+		log_err("Failed to allocate dquot");
+		return NULL;
+	}
+
+	dquot->dq_id = -1;
+	return dquot;
+}
diff --git a/fsck/quotaio.h b/fsck/quotaio.h
new file mode 100644
index 0000000..8087309
--- /dev/null
+++ b/fsck/quotaio.h
@@ -0,0 +1,256 @@
+/** quotaio.h
+ *
+ * Interface to the quota library.
+ *
+ * The quota library provides interface for creating and updating the quota
+ * files and the ext4 superblock fields. It supports the new VFS_V1 quota
+ * format. The quota library also provides support for keeping track of quotas
+ * in memory.
+ *
+ * Aditya Kali <adityakali@google.com>
+ * Header of IO operations for quota utilities
+ *
+ * Jan Kara <jack@suse.cz>
+ *
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+
+#ifndef GUARD_QUOTAIO_H
+#define GUARD_QUOTAIO_H
+
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+
+#include "dict.h"
+#include "f2fs_fs.h"
+#include "f2fs.h"
+#include "node.h"
+#include "fsck.h"
+
+#include "dqblk_v2.h"
+
+typedef int64_t qsize_t;	/* Type in which we store size limitations */
+typedef int32_t f2fs_ino_t;
+typedef int errcode_t;
+
+enum quota_type {
+	USRQUOTA = 0,
+	GRPQUOTA = 1,
+	PRJQUOTA = 2,
+	MAXQUOTAS = 3,
+};
+
+#if MAXQUOTAS > 32
+#error "cannot have more than 32 quota types to fit in qtype_bits"
+#endif
+
+
+#define QUOTA_USR_BIT (1 << USRQUOTA)
+#define QUOTA_GRP_BIT (1 << GRPQUOTA)
+#define QUOTA_PRJ_BIT (1 << PRJQUOTA)
+#define QUOTA_ALL_BIT (QUOTA_USR_BIT | QUOTA_GRP_BIT | QUOTA_PRJ_BIT)
+
+typedef struct quota_ctx *quota_ctx_t;
+
+struct quota_ctx {
+	struct f2fs_sb_info *sbi;
+	struct dict_t *quota_dict[MAXQUOTAS];
+	struct quota_handle *quota_file[MAXQUOTAS];
+	struct dict_t linked_inode_dict;
+};
+
+/*
+ * Definitions of magics and versions of current quota files
+ */
+#define INITQMAGICS {\
+	0xd9c01f11,	/* USRQUOTA */\
+	0xd9c01927,	/* GRPQUOTA */\
+	0xd9c03f14	/* PRJQUOTA */\
+}
+
+/* Size of blocks in which are counted size limits in generic utility parts */
+#define QUOTABLOCK_BITS 10
+#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS)
+#define toqb(x) (((x) + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS)
+
+/* Quota format type IDs */
+#define	QFMT_VFS_OLD 1
+#define	QFMT_VFS_V0 2
+#define	QFMT_VFS_V1 4
+
+/*
+ * The following constants define the default amount of time given a user
+ * before the soft limits are treated as hard limits (usually resulting
+ * in an allocation failure). The timer is started when the user crosses
+ * their soft limit, it is reset when they go below their soft limit.
+ */
+#define MAX_IQ_TIME  604800	/* (7*24*60*60) 1 week */
+#define MAX_DQ_TIME  604800	/* (7*24*60*60) 1 week */
+
+#define IOFL_INFODIRTY	0x01	/* Did info change? */
+
+struct quotafile_ops;
+
+/* Generic information about quotafile */
+struct util_dqinfo {
+	time_t dqi_bgrace;	/* Block grace time for given quotafile */
+	time_t dqi_igrace;	/* Inode grace time for given quotafile */
+	union {
+		struct v2_mem_dqinfo v2_mdqi;
+	} u;			/* Format specific info about quotafile */
+};
+
+struct quota_file {
+	struct f2fs_sb_info *sbi;
+	f2fs_ino_t ino;
+	int64_t filesize;
+};
+
+/* Structure for one opened quota file */
+struct quota_handle {
+	enum quota_type qh_type;	/* Type of quotafile */
+	int qh_fmt;		/* Quotafile format */
+	int qh_file_flags;
+	int qh_io_flags;	/* IO flags for file */
+	struct quota_file qh_qf;
+	unsigned int (*read)(struct quota_file *qf, long offset,
+			 void *buf, unsigned int size);
+	unsigned int (*write)(struct quota_file *qf, long offset,
+			  void *buf, unsigned int size);
+	struct quotafile_ops *qh_ops;	/* Operations on quotafile */
+	struct util_dqinfo qh_info;	/* Generic quotafile info */
+};
+
+/* Utility quota block */
+struct util_dqblk {
+	qsize_t dqb_ihardlimit;
+	qsize_t dqb_isoftlimit;
+	qsize_t dqb_curinodes;
+	qsize_t dqb_bhardlimit;
+	qsize_t dqb_bsoftlimit;
+	qsize_t dqb_curspace;
+	time_t dqb_btime;
+	time_t dqb_itime;
+	union {
+		struct v2_mem_dqblk v2_mdqb;
+	} u;			/* Format specific dquot information */
+};
+
+/* Structure for one loaded quota */
+struct dquot {
+	struct dquot *dq_next;	/* Pointer to next dquot in the list */
+	qid_t dq_id;		/* ID dquot belongs to */
+	int dq_flags;		/* Some flags for utils */
+	struct quota_handle *dq_h;	/* Handle of quotafile for this dquot */
+	struct util_dqblk dq_dqb;	/* Parsed data of dquot */
+};
+
+#define DQF_SEEN	0x0001
+
+/* Structure of quotafile operations */
+struct quotafile_ops {
+	/* Check whether quotafile is in our format */
+	int (*check_file) (struct quota_handle *h, int type);
+	/* Open quotafile */
+	int (*init_io) (struct quota_handle *h);
+	/* Create new quotafile */
+	int (*new_io) (struct quota_handle *h);
+	/* Write all changes and close quotafile */
+	int (*end_io) (struct quota_handle *h);
+	/* Write info about quotafile */
+	int (*write_info) (struct quota_handle *h);
+	/* Read dquot into memory */
+	struct dquot *(*read_dquot) (struct quota_handle *h, qid_t id);
+	/* Write given dquot to disk */
+	int (*commit_dquot) (struct dquot *dquot);
+	/* Scan quotafile and call callback on every structure */
+	int (*scan_dquots) (struct quota_handle *h,
+			    int (*process_dquot) (struct dquot *dquot,
+						  void *data),
+			    void *data);
+	/* Function to print format specific file information */
+	int (*report) (struct quota_handle *h, int verbose);
+};
+
+#ifdef __CHECKER__
+# ifndef __bitwise
+#  define __bitwise             __attribute__((bitwise))
+# endif
+#define __force                 __attribute__((force))
+#else
+# ifndef __bitwise
+#  define __bitwise
+# endif
+#define __force
+#endif
+
+#define be32_to_cpu(n) ntohl(n)
+
+/* Open existing quotafile of given type (and verify its format) on given
+ * filesystem. */
+errcode_t quota_file_open(struct f2fs_sb_info *sbi, struct quota_handle *h,
+			  enum quota_type qtype, int flags);
+
+/* Create new quotafile of specified format on given filesystem */
+errcode_t quota_file_create(struct f2fs_sb_info *sbi, struct quota_handle *h,
+		enum quota_type qtype);
+
+/* Close quotafile */
+errcode_t quota_file_close(struct f2fs_sb_info *sbi, struct quota_handle *h,
+		int update_filesize);
+
+/* Get empty quota structure */
+struct dquot *get_empty_dquot(void);
+const char *quota_type2name(enum quota_type qtype);
+void update_grace_times(struct dquot *q);
+
+/* In mkquota.c */
+errcode_t quota_init_context(struct f2fs_sb_info *sbi);
+void quota_data_inodes(quota_ctx_t qctx, struct f2fs_inode *inode, int adjust);
+void quota_data_add(quota_ctx_t qctx, struct f2fs_inode *inode, qsize_t space);
+void quota_data_sub(quota_ctx_t qctx, struct f2fs_inode *inode, qsize_t space);
+errcode_t quota_write_inode(struct f2fs_sb_info *sbi, enum quota_type qtype);
+void quota_add_inode_usage(quota_ctx_t qctx, f2fs_ino_t ino,
+		struct f2fs_inode* inode);
+void quota_release_context(quota_ctx_t *qctx);
+errcode_t quota_compare_and_update(struct f2fs_sb_info *sbi,
+		enum quota_type qtype, int *usage_inconsistent,
+		int preserve_limits);
+
+static inline errcode_t quota_get_mem(unsigned long size, void *ptr)
+{
+        void *pp;
+
+        pp = malloc(size);
+        if (!pp)
+                return -1;
+        memcpy(ptr, &pp, sizeof (pp));
+        return 0;
+}
+
+static inline errcode_t quota_get_memzero(unsigned long size, void *ptr)
+{
+        void *pp;
+
+        pp = malloc(size);
+        if (!pp)
+                return -1;
+        memset(pp, 0, size);
+        memcpy(ptr, &pp, sizeof(pp));
+        return 0;
+}
+
+static inline errcode_t quota_free_mem(void *ptr)
+{
+        void *p;
+
+        memcpy(&p, ptr, sizeof(p));
+        free(p);
+        p = 0;
+        memcpy(ptr, &p, sizeof(p));
+        return 0;
+}
+
+#endif /* GUARD_QUOTAIO_H */
diff --git a/fsck/quotaio_tree.c b/fsck/quotaio_tree.c
new file mode 100644
index 0000000..5aef228
--- /dev/null
+++ b/fsck/quotaio_tree.c
@@ -0,0 +1,679 @@
+/*
+ * Implementation of new quotafile format
+ *
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "quotaio_tree.h"
+#include "quotaio.h"
+
+typedef char *dqbuf_t;
+
+#define freedqbuf(buf)		quota_free_mem(&buf)
+
+static inline dqbuf_t getdqbuf(void)
+{
+	dqbuf_t buf;
+	if (quota_get_memzero(QT_BLKSIZE, &buf)) {
+		log_err("Failed to allocate dqbuf");
+		return NULL;
+	}
+
+	return buf;
+}
+
+/* Is given dquot empty? */
+int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk)
+{
+	unsigned int i;
+
+	for (i = 0; i < info->dqi_entry_size; i++)
+		if (disk[i])
+			return 0;
+	return 1;
+}
+
+int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info)
+{
+	return (QT_BLKSIZE - sizeof(struct qt_disk_dqdbheader)) /
+		info->dqi_entry_size;
+}
+
+static int get_index(qid_t id, int depth)
+{
+	return (id >> ((QT_TREEDEPTH - depth - 1) * 8)) & 0xff;
+}
+
+static inline void mark_quotafile_info_dirty(struct quota_handle *h)
+{
+	h->qh_io_flags |= IOFL_INFODIRTY;
+}
+
+/* Read given block */
+static void read_blk(struct quota_handle *h, unsigned int blk, dqbuf_t buf)
+{
+	int err;
+
+	err = h->read(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf,
+			QT_BLKSIZE);
+	if (err < 0)
+		log_err("Cannot read block %u: %s", blk, strerror(errno));
+	else if (err != QT_BLKSIZE)
+		memset(buf + err, 0, QT_BLKSIZE - err);
+}
+
+/* Write block */
+static int write_blk(struct quota_handle *h, unsigned int blk, dqbuf_t buf)
+{
+	int err;
+
+	err = h->write(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf,
+			QT_BLKSIZE);
+	if (err < 0 && errno != ENOSPC)
+		log_err("Cannot write block (%u): %s", blk, strerror(errno));
+	if (err != QT_BLKSIZE)
+		return -ENOSPC;
+	return 0;
+}
+
+/* Get free block in file (either from free list or create new one) */
+static int get_free_dqblk(struct quota_handle *h)
+{
+	dqbuf_t buf = getdqbuf();
+	struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	int blk;
+
+	if (!buf)
+		return -ENOMEM;
+
+	if (info->dqi_free_blk) {
+		blk = info->dqi_free_blk;
+		read_blk(h, blk, buf);
+		info->dqi_free_blk = le32_to_cpu(dh->dqdh_next_free);
+	} else {
+		memset(buf, 0, QT_BLKSIZE);
+		/* Assure block allocation... */
+		if (write_blk(h, info->dqi_blocks, buf) < 0) {
+			freedqbuf(buf);
+			log_err("Cannot allocate new quota block "
+				"(out of disk space).");
+			return -ENOSPC;
+		}
+		blk = info->dqi_blocks++;
+	}
+	mark_quotafile_info_dirty(h);
+	freedqbuf(buf);
+	return blk;
+}
+
+/* Put given block to free list */
+static void put_free_dqblk(struct quota_handle *h, dqbuf_t buf,
+			   unsigned int blk)
+{
+	struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+
+	dh->dqdh_next_free = cpu_to_le32(info->dqi_free_blk);
+	dh->dqdh_prev_free = cpu_to_le32(0);
+	dh->dqdh_entries = cpu_to_le16(0);
+	info->dqi_free_blk = blk;
+	mark_quotafile_info_dirty(h);
+	write_blk(h, blk, buf);
+}
+
+/* Remove given block from the list of blocks with free entries */
+static void remove_free_dqentry(struct quota_handle *h, dqbuf_t buf,
+				unsigned int blk)
+{
+	dqbuf_t tmpbuf = getdqbuf();
+	struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+	unsigned int nextblk = le32_to_cpu(dh->dqdh_next_free), prevblk =
+		le32_to_cpu(dh->dqdh_prev_free);
+
+	if (!tmpbuf)
+		return;
+
+	if (nextblk) {
+		read_blk(h, nextblk, tmpbuf);
+		((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free =
+				dh->dqdh_prev_free;
+		write_blk(h, nextblk, tmpbuf);
+	}
+	if (prevblk) {
+		read_blk(h, prevblk, tmpbuf);
+		((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_next_free =
+				dh->dqdh_next_free;
+		write_blk(h, prevblk, tmpbuf);
+	} else {
+		h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = nextblk;
+		mark_quotafile_info_dirty(h);
+	}
+	freedqbuf(tmpbuf);
+	dh->dqdh_next_free = dh->dqdh_prev_free = cpu_to_le32(0);
+	write_blk(h, blk, buf);	/* No matter whether write succeeds
+				 * block is out of list */
+}
+
+/* Insert given block to the beginning of list with free entries */
+static void insert_free_dqentry(struct quota_handle *h, dqbuf_t buf,
+				unsigned int blk)
+{
+	dqbuf_t tmpbuf = getdqbuf();
+	struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+
+	if (!tmpbuf)
+		return;
+
+	dh->dqdh_next_free = cpu_to_le32(info->dqi_free_entry);
+	dh->dqdh_prev_free = cpu_to_le32(0);
+	write_blk(h, blk, buf);
+	if (info->dqi_free_entry) {
+		read_blk(h, info->dqi_free_entry, tmpbuf);
+		((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free =
+				cpu_to_le32(blk);
+		write_blk(h, info->dqi_free_entry, tmpbuf);
+	}
+	freedqbuf(tmpbuf);
+	info->dqi_free_entry = blk;
+	mark_quotafile_info_dirty(h);
+}
+
+/* Find space for dquot */
+static unsigned int find_free_dqentry(struct quota_handle *h,
+				      struct dquot *dquot, int *err)
+{
+	int blk, i;
+	struct qt_disk_dqdbheader *dh;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	char *ddquot;
+	dqbuf_t buf;
+
+	*err = 0;
+	buf = getdqbuf();
+	if (!buf) {
+		*err = -ENOMEM;
+		return 0;
+	}
+
+	dh = (struct qt_disk_dqdbheader *)buf;
+	if (info->dqi_free_entry) {
+		blk = info->dqi_free_entry;
+		read_blk(h, blk, buf);
+	} else {
+		blk = get_free_dqblk(h);
+		if (blk < 0) {
+			freedqbuf(buf);
+			*err = blk;
+			return 0;
+		}
+		memset(buf, 0, QT_BLKSIZE);
+		info->dqi_free_entry = blk;
+		mark_quotafile_info_dirty(h);
+	}
+
+	/* Block will be full? */
+	if (le16_to_cpu(dh->dqdh_entries) + 1 >=
+	    qtree_dqstr_in_blk(info))
+		remove_free_dqentry(h, buf, blk);
+
+	dh->dqdh_entries =
+		cpu_to_le16(le16_to_cpu(dh->dqdh_entries) + 1);
+	/* Find free structure in block */
+	ddquot = buf + sizeof(struct qt_disk_dqdbheader);
+	for (i = 0;
+	     i < qtree_dqstr_in_blk(info) && !qtree_entry_unused(info, ddquot);
+	     i++)
+		ddquot += info->dqi_entry_size;
+
+	if (i == qtree_dqstr_in_blk(info))
+		log_err("find_free_dqentry(): Data block full unexpectedly.");
+
+	write_blk(h, blk, buf);
+	dquot->dq_dqb.u.v2_mdqb.dqb_off =
+		(blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) +
+		i * info->dqi_entry_size;
+	freedqbuf(buf);
+	return blk;
+}
+
+/* Insert reference to structure into the trie */
+static int do_insert_tree(struct quota_handle *h, struct dquot *dquot,
+			  unsigned int * treeblk, int depth)
+{
+	dqbuf_t buf;
+	int newson = 0, newact = 0;
+	__le32 *ref;
+	unsigned int newblk;
+	int ret = 0;
+
+	log_debug("inserting in tree: treeblk=%u, depth=%d", *treeblk, depth);
+	buf = getdqbuf();
+	if (!buf)
+		return -ENOMEM;
+
+	if (!*treeblk) {
+		ret = get_free_dqblk(h);
+		if (ret < 0)
+			goto out_buf;
+		*treeblk = ret;
+		memset(buf, 0, QT_BLKSIZE);
+		newact = 1;
+	} else {
+		read_blk(h, *treeblk, buf);
+	}
+
+	ref = (__le32 *) buf;
+	newblk = le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
+	if (!newblk)
+		newson = 1;
+	if (depth == QT_TREEDEPTH - 1) {
+		if (newblk)
+			log_err("Inserting already present quota entry "
+				"(block %u).",
+				ref[get_index(dquot->dq_id, depth)]);
+		newblk = find_free_dqentry(h, dquot, &ret);
+	} else {
+		ret = do_insert_tree(h, dquot, &newblk, depth + 1);
+	}
+
+	if (newson && ret >= 0) {
+		ref[get_index(dquot->dq_id, depth)] =
+			cpu_to_le32(newblk);
+		write_blk(h, *treeblk, buf);
+	} else if (newact && ret < 0) {
+		put_free_dqblk(h, buf, *treeblk);
+	}
+
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Wrapper for inserting quota structure into tree */
+static void dq_insert_tree(struct quota_handle *h, struct dquot *dquot)
+{
+	unsigned int tmp = QT_TREEOFF;
+
+	if (do_insert_tree(h, dquot, &tmp, 0) < 0)
+		log_err("Cannot write quota (id %u): %s",
+			(unsigned int) dquot->dq_id, strerror(errno));
+}
+
+/* Write dquot to file */
+void qtree_write_dquot(struct dquot *dquot)
+{
+	errcode_t retval;
+	unsigned int ret;
+	char *ddquot;
+	struct quota_handle *h = dquot->dq_h;
+	struct qtree_mem_dqinfo *info =
+			&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
+
+
+	log_debug("writing ddquot 1: off=%llu, info->dqi_entry_size=%u",
+			dquot->dq_dqb.u.v2_mdqb.dqb_off,
+			info->dqi_entry_size);
+	retval = quota_get_mem(info->dqi_entry_size, &ddquot);
+	if (retval) {
+		errno = ENOMEM;
+		log_err("Quota write failed (id %u): %s",
+			(unsigned int)dquot->dq_id, strerror(errno));
+		return;
+	}
+	memset(ddquot, 0, info->dqi_entry_size);
+
+	if (!dquot->dq_dqb.u.v2_mdqb.dqb_off) {
+		dq_insert_tree(dquot->dq_h, dquot);
+	}
+	info->dqi_ops->mem2disk_dqblk(ddquot, dquot);
+	log_debug("writing ddquot 2: off=%llu, info->dqi_entry_size=%u",
+			dquot->dq_dqb.u.v2_mdqb.dqb_off,
+			info->dqi_entry_size);
+	ret = h->write(&h->qh_qf, dquot->dq_dqb.u.v2_mdqb.dqb_off, ddquot,
+			info->dqi_entry_size);
+
+	if (ret != info->dqi_entry_size) {
+		if (ret > 0)
+			errno = ENOSPC;
+		log_err("Quota write failed (id %u): %s",
+			(unsigned int)dquot->dq_id, strerror(errno));
+	}
+	quota_free_mem(&ddquot);
+}
+
+/* Free dquot entry in data block */
+static void free_dqentry(struct quota_handle *h, struct dquot *dquot,
+			 unsigned int blk)
+{
+	struct qt_disk_dqdbheader *dh;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	dqbuf_t buf = getdqbuf();
+
+	if (!buf)
+		return;
+
+	if (dquot->dq_dqb.u.v2_mdqb.dqb_off >> QT_BLKSIZE_BITS != blk)
+		log_err("Quota structure has offset to other block (%u) "
+			"than it should (%u).", blk,
+			  (unsigned int) (dquot->dq_dqb.u.v2_mdqb.dqb_off >>
+				  QT_BLKSIZE_BITS));
+
+	read_blk(h, blk, buf);
+	dh = (struct qt_disk_dqdbheader *)buf;
+	dh->dqdh_entries =
+		cpu_to_le16(le16_to_cpu(dh->dqdh_entries) - 1);
+
+	if (!le16_to_cpu(dh->dqdh_entries)) {	/* Block got free? */
+		remove_free_dqentry(h, buf, blk);
+		put_free_dqblk(h, buf, blk);
+	} else {
+		memset(buf + (dquot->dq_dqb.u.v2_mdqb.dqb_off &
+			      ((1 << QT_BLKSIZE_BITS) - 1)),
+		       0, info->dqi_entry_size);
+
+		/* First free entry? */
+		if (le16_to_cpu(dh->dqdh_entries) ==
+				qtree_dqstr_in_blk(info) - 1)
+			/* This will also write data block */
+			insert_free_dqentry(h, buf, blk);
+		else
+			write_blk(h, blk, buf);
+	}
+	dquot->dq_dqb.u.v2_mdqb.dqb_off = 0;
+	freedqbuf(buf);
+}
+
+/* Remove reference to dquot from tree */
+static void remove_tree(struct quota_handle *h, struct dquot *dquot,
+			unsigned int * blk, int depth)
+{
+	dqbuf_t buf = getdqbuf();
+	unsigned int newblk;
+	__le32 *ref = (__le32 *) buf;
+
+	if (!buf)
+		return;
+
+	read_blk(h, *blk, buf);
+	newblk = le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
+	if (depth == QT_TREEDEPTH - 1) {
+		free_dqentry(h, dquot, newblk);
+		newblk = 0;
+	} else {
+		remove_tree(h, dquot, &newblk, depth + 1);
+	}
+
+	if (!newblk) {
+		int i;
+
+		ref[get_index(dquot->dq_id, depth)] = cpu_to_le32(0);
+
+		/* Block got empty? */
+		for (i = 0; i < QT_BLKSIZE && !buf[i]; i++);
+
+		/* Don't put the root block into the free block list */
+		if (i == QT_BLKSIZE && *blk != QT_TREEOFF) {
+			put_free_dqblk(h, buf, *blk);
+			*blk = 0;
+		} else {
+			write_blk(h, *blk, buf);
+		}
+	}
+	freedqbuf(buf);
+}
+
+/* Delete dquot from tree */
+void qtree_delete_dquot(struct dquot *dquot)
+{
+	unsigned int tmp = QT_TREEOFF;
+
+	if (!dquot->dq_dqb.u.v2_mdqb.dqb_off)	/* Even not allocated? */
+		return;
+	remove_tree(dquot->dq_h, dquot, &tmp, 0);
+}
+
+/* Find entry in block */
+static long find_block_dqentry(struct quota_handle *h,
+				      struct dquot *dquot, unsigned int blk)
+{
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	dqbuf_t buf = getdqbuf();
+	int i;
+	char *ddquot = buf + sizeof(struct qt_disk_dqdbheader);
+
+	if (!buf)
+		return -ENOMEM;
+
+	read_blk(h, blk, buf);
+	for (i = 0;
+	     i < qtree_dqstr_in_blk(info) && !info->dqi_ops->is_id(ddquot, dquot);
+	     i++)
+		ddquot += info->dqi_entry_size;
+
+	if (i == qtree_dqstr_in_blk(info))
+		log_err("Quota for id %u referenced but not present.",
+			dquot->dq_id);
+	freedqbuf(buf);
+	return (blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) +
+		i * info->dqi_entry_size;
+}
+
+/* Find entry for given id in the tree */
+static long find_tree_dqentry(struct quota_handle *h,
+				     struct dquot *dquot,
+				     unsigned int blk, int depth)
+{
+	dqbuf_t buf = getdqbuf();
+	long ret = 0;
+	__le32 *ref = (__le32 *) buf;
+
+	if (!buf)
+		return -ENOMEM;
+
+	read_blk(h, blk, buf);
+	ret = 0;
+	blk = le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
+	if (!blk)	/* No reference? */
+		goto out_buf;
+	if (depth < QT_TREEDEPTH - 1)
+		ret = find_tree_dqentry(h, dquot, blk, depth + 1);
+	else
+		ret = find_block_dqentry(h, dquot, blk);
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Find entry for given id in the tree - wrapper function */
+static inline long find_dqentry(struct quota_handle *h,
+				       struct dquot *dquot)
+{
+	return find_tree_dqentry(h, dquot, QT_TREEOFF, 0);
+}
+
+/*
+ *  Read dquot from disk.
+ */
+struct dquot *qtree_read_dquot(struct quota_handle *h, qid_t id)
+{
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	long offset;
+	unsigned int ret;
+	char *ddquot;
+	struct dquot *dquot = get_empty_dquot();
+
+	if (!dquot)
+		return NULL;
+	if (quota_get_mem(info->dqi_entry_size, &ddquot)) {
+		quota_free_mem(&dquot);
+		return NULL;
+	}
+
+	dquot->dq_id = id;
+	dquot->dq_h = h;
+	dquot->dq_dqb.u.v2_mdqb.dqb_off = 0;
+	memset(&dquot->dq_dqb, 0, sizeof(struct util_dqblk));
+
+	offset = find_dqentry(h, dquot);
+	if (offset > 0) {
+		dquot->dq_dqb.u.v2_mdqb.dqb_off = offset;
+		ret = h->read(&h->qh_qf, offset, ddquot,
+			info->dqi_entry_size);
+		if (ret != info->dqi_entry_size) {
+			if (ret > 0)
+				errno = EIO;
+			log_err("Cannot read quota structure for id %u: %s",
+				dquot->dq_id, strerror(errno));
+		}
+		info->dqi_ops->disk2mem_dqblk(dquot, ddquot);
+	}
+	quota_free_mem(&ddquot);
+	return dquot;
+}
+
+/*
+ * Scan all dquots in file and call callback on each
+ */
+#define set_bit(bmp, ind) ((bmp)[(ind) >> 3] |= (1 << ((ind) & 7)))
+#define get_bit(bmp, ind) ((bmp)[(ind) >> 3] & (1 << ((ind) & 7)))
+
+static int report_block(struct dquot *dquot, unsigned int blk, char *bitmap,
+			int (*process_dquot) (struct dquot *, void *),
+			void *data)
+{
+	struct qtree_mem_dqinfo *info =
+			&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
+	dqbuf_t buf = getdqbuf();
+	struct qt_disk_dqdbheader *dh;
+	char *ddata;
+	int entries, i;
+
+	if (!buf)
+		return 0;
+
+	set_bit(bitmap, blk);
+	read_blk(dquot->dq_h, blk, buf);
+	dh = (struct qt_disk_dqdbheader *)buf;
+	ddata = buf + sizeof(struct qt_disk_dqdbheader);
+	entries = le16_to_cpu(dh->dqdh_entries);
+	for (i = 0; i < qtree_dqstr_in_blk(info);
+			i++, ddata += info->dqi_entry_size)
+		if (!qtree_entry_unused(info, ddata)) {
+			dquot->dq_dqb.u.v2_mdqb.dqb_off =
+				(blk << QT_BLKSIZE_BITS) +
+				sizeof(struct qt_disk_dqdbheader) +
+				i * info->dqi_entry_size;
+			info->dqi_ops->disk2mem_dqblk(dquot, ddata);
+			if (process_dquot(dquot, data) < 0)
+				break;
+		}
+	freedqbuf(buf);
+	return entries;
+}
+
+static int check_reference(struct quota_handle *h, unsigned int blk)
+{
+	if (blk >= h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks) {
+		log_err("Illegal reference (%u >= %u) in %s quota file. "
+			"Quota file is probably corrupted.\n"
+			"Please run fsck (8) to fix it.",
+			blk,
+			h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks,
+			quota_type2name(h->qh_type));
+		return -1;
+	}
+	return 0;
+}
+
+/* Return 0 for successful run */
+static int report_tree(struct dquot *dquot, unsigned int blk, int depth,
+		       char *bitmap, int *entries,
+		       int (*process_dquot) (struct dquot *, void *),
+		       void *data)
+{
+	int i;
+	dqbuf_t buf = getdqbuf();
+	__le32 *ref = (__le32 *) buf;
+
+	if (!buf)
+		return -1;
+
+	read_blk(dquot->dq_h, blk, buf);
+	for (i = 0; i < QT_BLKSIZE >> 2; i++) {
+		blk = le32_to_cpu(ref[i]);
+		if (blk == 0)
+			continue;
+
+		if (check_reference(dquot->dq_h, blk))
+			break;
+
+		if (depth == QT_TREEDEPTH - 1) {
+			if (!get_bit(bitmap, blk))
+				*entries += report_block(dquot, blk, bitmap,
+							process_dquot, data);
+		} else {
+			if (report_tree(dquot, blk, depth + 1, bitmap, entries,
+						process_dquot, data))
+				break;
+		}
+	}
+	freedqbuf(buf);
+	return (i < QT_BLKSIZE >> 2) ? -1 : 0;
+}
+
+static unsigned int find_set_bits(char *bmp, int blocks)
+{
+	unsigned int	used = 0;
+	int		i;
+
+	for (i = 0; i < blocks; i++)
+		if (get_bit(bmp, i))
+			used++;
+	return used;
+}
+
+int qtree_scan_dquots(struct quota_handle *h,
+		      int (*process_dquot) (struct dquot *, void *),
+		      void *data)
+{
+	struct v2_mem_dqinfo *v2info = &h->qh_info.u.v2_mdqi;
+	struct qtree_mem_dqinfo *info = &v2info->dqi_qtree;
+	struct dquot *dquot = get_empty_dquot();
+	char *bitmap = NULL;
+	int ret = -1;
+	int entries = 0;
+
+	if (!dquot)
+		return -1;
+
+	dquot->dq_h = h;
+	if (quota_get_memzero((info->dqi_blocks + 7) >> 3, &bitmap))
+		goto out;
+	if (report_tree(dquot, QT_TREEOFF, 0, bitmap, &entries, process_dquot,
+				data))
+		goto out;
+
+	v2info->dqi_used_entries = entries;
+	v2info->dqi_data_blocks = find_set_bits(bitmap, info->dqi_blocks);
+	ret = 0;
+
+out:
+	if (bitmap)
+		quota_free_mem(&bitmap);
+	if (dquot)
+		quota_free_mem(&dquot);
+
+	return ret;
+}
diff --git a/fsck/quotaio_tree.h b/fsck/quotaio_tree.h
new file mode 100644
index 0000000..aed93a8
--- /dev/null
+++ b/fsck/quotaio_tree.h
@@ -0,0 +1,70 @@
+/*
+ * Definitions of structures for vfsv0 quota format
+ */
+
+#ifndef _LINUX_QUOTA_TREE_H
+#define _LINUX_QUOTA_TREE_H
+
+#include <inttypes.h>
+#ifdef HAVE_LINUX_TYPES_H
+#include <linux/types.h>
+#endif
+#include <sys/types.h>
+
+#include <f2fs_fs.h>
+
+typedef __u32 qid_t;        /* Type in which we store ids in memory */
+
+#define QT_TREEOFF	1	/* Offset of tree in file in blocks */
+#define QT_TREEDEPTH	4	/* Depth of quota tree */
+#define QT_BLKSIZE_BITS	10
+#define QT_BLKSIZE (1 << QT_BLKSIZE_BITS)	/* Size of block with quota
+						 * structures */
+
+/*
+ *  Structure of header of block with quota structures. It is padded to 16 bytes
+ *  so there will be space for exactly 21 quota-entries in a block
+ */
+struct qt_disk_dqdbheader {
+	__le32 dqdh_next_free;	/* Number of next block with free
+					 * entry */
+	__le32 dqdh_prev_free; /* Number of previous block with free
+				   * entry */
+	__le16 dqdh_entries; /* Number of valid entries in block */
+	__le16 dqdh_pad1;
+	__le32 dqdh_pad2;
+} __attribute__ ((packed));
+
+struct dquot;
+struct quota_handle;
+
+/* Operations */
+struct qtree_fmt_operations {
+	/* Convert given entry from in memory format to disk one */
+	void (*mem2disk_dqblk)(void *disk, struct dquot *dquot);
+	/* Convert given entry from disk format to in memory one */
+	void (*disk2mem_dqblk)(struct dquot *dquot, void *disk);
+	/* Is this structure for given id? */
+	int (*is_id)(void *disk, struct dquot *dquot);
+};
+
+/* Inmemory copy of version specific information */
+struct qtree_mem_dqinfo {
+	unsigned int dqi_blocks;	/* # of blocks in quota file */
+	unsigned int dqi_free_blk;	/* First block in list of free blocks */
+	unsigned int dqi_free_entry;	/* First block with free entry */
+	unsigned int dqi_entry_size;	/* Size of quota entry in quota file */
+	struct qtree_fmt_operations *dqi_ops;	/* Operations for entry
+						 * manipulation */
+};
+
+void qtree_write_dquot(struct dquot *dquot);
+struct dquot *qtree_read_dquot(struct quota_handle *h, qid_t id);
+void qtree_delete_dquot(struct dquot *dquot);
+int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk);
+int qtree_scan_dquots(struct quota_handle *h,
+		int (*process_dquot) (struct dquot *, void *), void *data);
+
+int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info);
+
+#endif /* _LINUX_QUOTAIO_TREE_H */
diff --git a/fsck/quotaio_v2.c b/fsck/quotaio_v2.c
new file mode 100644
index 0000000..478cd17
--- /dev/null
+++ b/fsck/quotaio_v2.c
@@ -0,0 +1,284 @@
+/*
+ * Implementation of new quotafile format
+ *
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+#include "quotaio_v2.h"
+#include "dqblk_v2.h"
+#include "quotaio_tree.h"
+
+static int v2_check_file(struct quota_handle *h, int type);
+static int v2_init_io(struct quota_handle *h);
+static int v2_new_io(struct quota_handle *h);
+static int v2_write_info(struct quota_handle *h);
+static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id);
+static int v2_commit_dquot(struct dquot *dquot);
+static int v2_scan_dquots(struct quota_handle *h,
+			  int (*process_dquot) (struct dquot *dquot,
+						void *data),
+			  void *data);
+static int v2_report(struct quota_handle *h, int verbose);
+
+struct quotafile_ops quotafile_ops_2 = {
+	.check_file	= v2_check_file,
+	.init_io 	= v2_init_io,
+	.new_io 	= v2_new_io,
+	.write_info	= v2_write_info,
+	.read_dquot	= v2_read_dquot,
+	.commit_dquot	= v2_commit_dquot,
+	.scan_dquots	= v2_scan_dquots,
+	.report		= v2_report,
+};
+
+/*
+ * Copy dquot from disk to memory
+ */
+static void v2r1_disk2memdqblk(struct dquot *dquot, void *dp)
+{
+	struct util_dqblk *m = &dquot->dq_dqb;
+	struct v2r1_disk_dqblk *d = dp, empty;
+
+	dquot->dq_id = le32_to_cpu(d->dqb_id);
+	m->dqb_ihardlimit = le64_to_cpu(d->dqb_ihardlimit);
+	m->dqb_isoftlimit = le64_to_cpu(d->dqb_isoftlimit);
+	m->dqb_bhardlimit = le64_to_cpu(d->dqb_bhardlimit);
+	m->dqb_bsoftlimit = le64_to_cpu(d->dqb_bsoftlimit);
+	m->dqb_curinodes = le64_to_cpu(d->dqb_curinodes);
+	m->dqb_curspace = le64_to_cpu(d->dqb_curspace);
+	m->dqb_itime = le64_to_cpu(d->dqb_itime);
+	m->dqb_btime = le64_to_cpu(d->dqb_btime);
+
+	memset(&empty, 0, sizeof(struct v2r1_disk_dqblk));
+	empty.dqb_itime = cpu_to_le64(1);
+	if (!memcmp(&empty, dp, sizeof(struct v2r1_disk_dqblk)))
+		m->dqb_itime = 0;
+}
+
+/*
+ * Copy dquot from memory to disk
+ */
+static void v2r1_mem2diskdqblk(void *dp, struct dquot *dquot)
+{
+	struct util_dqblk *m = &dquot->dq_dqb;
+	struct v2r1_disk_dqblk *d = dp;
+
+	d->dqb_ihardlimit = cpu_to_le64(m->dqb_ihardlimit);
+	d->dqb_isoftlimit = cpu_to_le64(m->dqb_isoftlimit);
+	d->dqb_bhardlimit = cpu_to_le64(m->dqb_bhardlimit);
+	d->dqb_bsoftlimit = cpu_to_le64(m->dqb_bsoftlimit);
+	d->dqb_curinodes = cpu_to_le64(m->dqb_curinodes);
+	d->dqb_curspace = cpu_to_le64(m->dqb_curspace);
+	d->dqb_itime = cpu_to_le64(m->dqb_itime);
+	d->dqb_btime = cpu_to_le64(m->dqb_btime);
+	d->dqb_id = cpu_to_le32(dquot->dq_id);
+	if (qtree_entry_unused(&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree, dp))
+		d->dqb_itime = cpu_to_le64(1);
+}
+
+static int v2r1_is_id(void *dp, struct dquot *dquot)
+{
+	struct v2r1_disk_dqblk *d = dp;
+	struct qtree_mem_dqinfo *info =
+			&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
+
+	if (qtree_entry_unused(info, dp))
+		return 0;
+	return le32_to_cpu(d->dqb_id) == dquot->dq_id;
+}
+
+static struct qtree_fmt_operations v2r1_fmt_ops = {
+	.mem2disk_dqblk = v2r1_mem2diskdqblk,
+	.disk2mem_dqblk = v2r1_disk2memdqblk,
+	.is_id = v2r1_is_id,
+};
+
+/*
+ * Copy dqinfo from disk to memory
+ */
+static inline void v2_disk2memdqinfo(struct util_dqinfo *m,
+				     struct v2_disk_dqinfo *d)
+{
+	m->dqi_bgrace = le32_to_cpu(d->dqi_bgrace);
+	m->dqi_igrace = le32_to_cpu(d->dqi_igrace);
+	m->u.v2_mdqi.dqi_flags = le32_to_cpu(d->dqi_flags) & V2_DQF_MASK;
+	m->u.v2_mdqi.dqi_qtree.dqi_blocks = le32_to_cpu(d->dqi_blocks);
+	m->u.v2_mdqi.dqi_qtree.dqi_free_blk =
+		le32_to_cpu(d->dqi_free_blk);
+	m->u.v2_mdqi.dqi_qtree.dqi_free_entry =
+				le32_to_cpu(d->dqi_free_entry);
+}
+
+/*
+ * Copy dqinfo from memory to disk
+ */
+static inline void v2_mem2diskdqinfo(struct v2_disk_dqinfo *d,
+				     struct util_dqinfo *m)
+{
+	d->dqi_bgrace = cpu_to_le32(m->dqi_bgrace);
+	d->dqi_igrace = cpu_to_le32(m->dqi_igrace);
+	d->dqi_flags = cpu_to_le32(m->u.v2_mdqi.dqi_flags & V2_DQF_MASK);
+	d->dqi_blocks = cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_blocks);
+	d->dqi_free_blk =
+		cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_blk);
+	d->dqi_free_entry =
+		cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_entry);
+}
+
+static int v2_read_header(struct quota_handle *h, struct v2_disk_dqheader *dqh)
+{
+	if (h->read(&h->qh_qf, 0, dqh, sizeof(struct v2_disk_dqheader)) !=
+			sizeof(struct v2_disk_dqheader))
+		return 0;
+
+	return 1;
+}
+
+/*
+ * Check whether given quota file is in our format
+ */
+static int v2_check_file(struct quota_handle *h, int type)
+{
+	struct v2_disk_dqheader dqh;
+	int file_magics[] = INITQMAGICS;
+	int be_magic;
+
+	if (!v2_read_header(h, &dqh))
+		return 0;
+
+	be_magic = be32_to_cpu((__force __be32)dqh.dqh_magic);
+	if (be_magic == file_magics[type]) {
+		log_err("Your quota file is stored in wrong endianity");
+		return 0;
+	}
+	if (V2_VERSION != le32_to_cpu(dqh.dqh_version))
+		return 0;
+	return 1;
+}
+
+/*
+ * Open quotafile
+ */
+static int v2_init_io(struct quota_handle *h)
+{
+	struct v2_disk_dqinfo ddqinfo;
+
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size =
+		sizeof(struct v2r1_disk_dqblk);
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r1_fmt_ops;
+
+	/* Read information about quotafile */
+	if (h->read(&h->qh_qf, V2_DQINFOOFF, &ddqinfo,
+			 sizeof(ddqinfo)) != sizeof(ddqinfo))
+		return -1;
+	v2_disk2memdqinfo(&h->qh_info, &ddqinfo);
+	return 0;
+}
+
+/*
+ * Initialize new quotafile
+ */
+static int v2_new_io(struct quota_handle *h)
+{
+	int file_magics[] = INITQMAGICS;
+	struct v2_disk_dqheader ddqheader;
+	struct v2_disk_dqinfo ddqinfo;
+
+	if (h->qh_fmt != QFMT_VFS_V1)
+		return -1;
+
+	/* Write basic quota header */
+	ddqheader.dqh_magic = cpu_to_le32(file_magics[h->qh_type]);
+	ddqheader.dqh_version = cpu_to_le32(V2_VERSION);
+	if (h->write(&h->qh_qf, 0, &ddqheader, sizeof(ddqheader)) !=
+			sizeof(ddqheader))
+		return -1;
+
+	/* Write information about quotafile */
+	h->qh_info.dqi_bgrace = MAX_DQ_TIME;
+	h->qh_info.dqi_igrace = MAX_IQ_TIME;
+	h->qh_info.u.v2_mdqi.dqi_flags = 0;
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks = QT_TREEOFF + 1;
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_blk = 0;
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = 0;
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size =
+				sizeof(struct v2r1_disk_dqblk);
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r1_fmt_ops;
+	v2_mem2diskdqinfo(&ddqinfo, &h->qh_info);
+	if (h->write(&h->qh_qf, V2_DQINFOOFF, &ddqinfo,
+			  sizeof(ddqinfo)) !=
+	    sizeof(ddqinfo))
+		return -1;
+
+	return 0;
+}
+
+/*
+ * Write information (grace times to file)
+ */
+static int v2_write_info(struct quota_handle *h)
+{
+	struct v2_disk_dqinfo ddqinfo;
+
+	v2_mem2diskdqinfo(&ddqinfo, &h->qh_info);
+	if (h->write(&h->qh_qf, V2_DQINFOOFF, &ddqinfo, sizeof(ddqinfo)) !=
+			sizeof(ddqinfo))
+		return -1;
+
+	return 0;
+}
+
+/*
+ * Read dquot from disk
+ */
+static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id)
+{
+	return qtree_read_dquot(h, id);
+}
+
+/*
+ * Commit changes of dquot to disk - it might also mean deleting it when quota
+ * became fake one and user has no blocks.
+ * User can process use 'errno' to detect errstr.
+ */
+static int v2_commit_dquot(struct dquot *dquot)
+{
+	struct util_dqblk *b = &dquot->dq_dqb;
+
+	if (!b->dqb_curspace && !b->dqb_curinodes && !b->dqb_bsoftlimit &&
+	    !b->dqb_isoftlimit && !b->dqb_bhardlimit && !b->dqb_ihardlimit)
+	{
+		qtree_delete_dquot(dquot);
+	} else {
+		qtree_write_dquot(dquot);
+	}
+	return 0;
+}
+
+static int v2_scan_dquots(struct quota_handle *h,
+			  int (*process_dquot) (struct dquot *, void *),
+			  void *data)
+{
+	return qtree_scan_dquots(h, process_dquot, data);
+}
+
+/* Report information about quotafile.
+ * TODO: Not used right now, but we should be able to use this when we add
+ * support to debugfs to read quota files.
+ */
+static int v2_report(struct quota_handle *UNUSED(h), int UNUSED(verbose))
+{
+	log_err("Not Implemented.");
+	return -1;
+}
diff --git a/fsck/quotaio_v2.h b/fsck/quotaio_v2.h
new file mode 100644
index 0000000..de2db27
--- /dev/null
+++ b/fsck/quotaio_v2.h
@@ -0,0 +1,54 @@
+/*
+ *
+ *	Header file for disk format of new quotafile format
+ *
+ */
+
+#ifndef GUARD_QUOTAIO_V2_H
+#define GUARD_QUOTAIO_V2_H
+
+#include <sys/types.h>
+#include "quotaio.h"
+
+/* Offset of info header in file */
+#define V2_DQINFOOFF		sizeof(struct v2_disk_dqheader)
+/* Supported version of quota-tree format */
+#define V2_VERSION 1
+
+struct v2_disk_dqheader {
+	__le32 dqh_magic;	/* Magic number identifying file */
+	__le32 dqh_version;	/* File version */
+} __attribute__ ((packed));
+
+/* Flags for version specific files */
+#define V2_DQF_MASK  0x0000	/* Mask for all valid ondisk flags */
+
+/* Header with type and version specific information */
+struct v2_disk_dqinfo {
+	__le32 dqi_bgrace;	/* Time before block soft limit becomes
+				 * hard limit */
+	__le32 dqi_igrace;	/* Time before inode soft limit becomes
+				 * hard limit */
+	__le32 dqi_flags;	/* Flags for quotafile (DQF_*) */
+	__le32 dqi_blocks;	/* Number of blocks in file */
+	__le32 dqi_free_blk;	/* Number of first free block in the list */
+	__le32 dqi_free_entry;	/* Number of block with at least one
+					 * free entry */
+} __attribute__ ((packed));
+
+struct v2r1_disk_dqblk {
+	__le32 dqb_id;	/* id this quota applies to */
+	__le32 dqb_pad;
+	__le64 dqb_ihardlimit;	/* absolute limit on allocated inodes */
+	__le64 dqb_isoftlimit;	/* preferred inode limit */
+	__le64 dqb_curinodes;	/* current # allocated inodes */
+	__le64 dqb_bhardlimit;	/* absolute limit on disk space
+					 * (in QUOTABLOCK_SIZE) */
+	__le64 dqb_bsoftlimit;	/* preferred limit on disk space
+					 * (in QUOTABLOCK_SIZE) */
+	__le64 dqb_curspace;	/* current space occupied (in bytes) */
+	__le64 dqb_btime;	/* time limit for excessive disk use */
+	__le64 dqb_itime;	/* time limit for excessive inode use */
+} __attribute__ ((packed));
+
+#endif
diff --git a/fsck/resize.c b/fsck/resize.c
index 4584d6f..143ad5d 100644
--- a/fsck/resize.c
+++ b/fsck/resize.c
@@ -36,7 +36,7 @@
 				zone_align_start_offset) / segment_size_bytes /
 				c.segs_per_sec * c.segs_per_sec);
 
-	blocks_for_sit = ALIGN(get_sb(segment_count), SIT_ENTRY_PER_BLOCK);
+	blocks_for_sit = SIZE_ALIGN(get_sb(segment_count), SIT_ENTRY_PER_BLOCK);
 	sit_segments = SEG_ALIGN(blocks_for_sit);
 	set_sb(segment_count_sit, sit_segments * 2);
 	set_sb(nat_blkaddr, get_sb(sit_blkaddr) +
@@ -45,7 +45,8 @@
 	total_valid_blks_available = (get_sb(segment_count) -
 			(get_sb(segment_count_ckpt) +
 			get_sb(segment_count_sit))) * blks_per_seg;
-	blocks_for_nat = ALIGN(total_valid_blks_available, NAT_ENTRY_PER_BLOCK);
+	blocks_for_nat = SIZE_ALIGN(total_valid_blks_available,
+					NAT_ENTRY_PER_BLOCK);
 	set_sb(segment_count_nat, SEG_ALIGN(blocks_for_nat));
 
 	sit_bitmap_size = ((get_sb(segment_count_sit) / 2) <<
@@ -59,8 +60,7 @@
 	 * It should be reserved minimum 1 segment for nat.
 	 * When sit is too large, we should expand cp area. It requires more pages for cp.
 	 */
-	if (max_sit_bitmap_size >
-			(CHECKSUM_OFFSET - sizeof(struct f2fs_checkpoint) + 65)) {
+	if (max_sit_bitmap_size > MAX_SIT_BITMAP_SIZE_IN_CKPT) {
 		max_nat_bitmap_size = CHECKSUM_OFFSET - sizeof(struct f2fs_checkpoint) + 1;
 		set_sb(cp_payload, F2FS_BLK_ALIGN(max_sit_bitmap_size));
 	} else {
diff --git a/fsck/segment.c b/fsck/segment.c
index 6b2f6c1..4f8bdb4 100644
--- a/fsck/segment.c
+++ b/fsck/segment.c
@@ -16,12 +16,21 @@
 #include "fsck.h"
 #include "node.h"
 
+static void write_inode(u64 blkaddr, struct f2fs_node *inode)
+{
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+		inode->i.i_inode_checksum =
+			cpu_to_le32(f2fs_inode_chksum(inode));
+	ASSERT(dev_write_block(inode, blkaddr) >= 0);
+}
+
 void reserve_new_block(struct f2fs_sb_info *sbi, block_t *to,
 			struct f2fs_summary *sum, int type)
 {
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
 	struct seg_entry *se;
-	u64 blkaddr;
-	u64 offset;
+	u64 blkaddr, offset;
+	u64 old_blkaddr = *to;
 
 	blkaddr = SM_I(sbi)->main_blkaddr;
 
@@ -35,7 +44,16 @@
 	se->type = type;
 	se->valid_blocks++;
 	f2fs_set_bit(offset, (char *)se->cur_valid_map);
-	sbi->total_valid_block_count++;
+	if (c.func == FSCK) {
+		f2fs_set_main_bitmap(sbi, blkaddr, type);
+		f2fs_set_sit_bitmap(sbi, blkaddr);
+	}
+
+	if (old_blkaddr == NULL_ADDR) {
+		sbi->total_valid_block_count++;
+		if (c.func == FSCK)
+			fsck->chk.valid_blk_cnt++;
+	}
 	se->dirty = 1;
 
 	/* read/write SSA */
@@ -48,6 +66,7 @@
 {
 	struct f2fs_summary sum;
 	struct node_info ni;
+	unsigned int blkaddr = datablock_addr(dn->node_blk, dn->ofs_in_node);
 
 	ASSERT(dn->node_blk);
 	memset(block, 0, BLOCK_SZ);
@@ -56,110 +75,200 @@
 	set_summary(&sum, dn->nid, dn->ofs_in_node, ni.version);
 	reserve_new_block(sbi, &dn->data_blkaddr, &sum, type);
 
-	inc_inode_blocks(dn);
+	if (blkaddr == NULL_ADDR)
+		inc_inode_blocks(dn);
+	else if (blkaddr == NEW_ADDR)
+		dn->idirty = 1;
 	set_data_blkaddr(dn);
 }
 
-static void f2fs_write_block(struct f2fs_sb_info *sbi, nid_t ino, void *buffer,
+u64 f2fs_read(struct f2fs_sb_info *sbi, nid_t ino, u8 *buffer,
 					u64 count, pgoff_t offset)
 {
-	u64 start = F2FS_BYTES_TO_BLK(offset);
-	u64 len = F2FS_BYTES_TO_BLK(count);
-	u64 end_offset;
-	u64 off_in_block, len_in_block, len_already;
-	struct dnode_of_data dn = {0};
-	void *data_blk;
+	struct dnode_of_data dn;
 	struct node_info ni;
 	struct f2fs_node *inode;
-	int ret = -1;
+	char *blk_buffer;
+	u64 filesize;
+	u64 off_in_blk;
+	u64 len_in_blk;
+	u64 read_count;
+	u64 remained_blkentries;
+	block_t blkaddr;
+	void *index_node = NULL;
 
+	memset(&dn, 0, sizeof(dn));
+
+	/* Memory allocation for block buffer and inode. */
+	blk_buffer = calloc(BLOCK_SZ, 2);
+	ASSERT(blk_buffer);
+	inode = (struct f2fs_node*)(blk_buffer + BLOCK_SZ);
+
+	/* Read inode */
 	get_node_info(sbi, ino, &ni);
+	ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
+	ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
+	ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
+
+	/* Adjust count with file length. */
+	filesize = le64_to_cpu(inode->i.i_size);
+	if (offset > filesize)
+		count = 0;
+	else if (count + offset > filesize)
+		count = filesize - offset;
+
+	/* Main loop for file blocks */
+	read_count = remained_blkentries = 0;
+	while (count > 0) {
+		if (remained_blkentries == 0) {
+			set_new_dnode(&dn, inode, NULL, ino);
+			get_dnode_of_data(sbi, &dn, F2FS_BYTES_TO_BLK(offset),
+					LOOKUP_NODE);
+			if (index_node)
+				free(index_node);
+			index_node = (dn.node_blk == dn.inode_blk) ?
+							NULL : dn.node_blk;
+			remained_blkentries = ADDRS_PER_PAGE(dn.node_blk);
+		}
+		ASSERT(remained_blkentries > 0);
+
+		blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
+		if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR)
+			break;
+
+		off_in_blk = offset % BLOCK_SZ;
+		len_in_blk = BLOCK_SZ - off_in_blk;
+		if (len_in_blk > count)
+			len_in_blk = count;
+
+		/* Read data from single block. */
+		if (len_in_blk < BLOCK_SZ) {
+			ASSERT(dev_read_block(blk_buffer, blkaddr) >= 0);
+			memcpy(buffer, blk_buffer + off_in_blk, len_in_blk);
+		} else {
+			/* Direct read */
+			ASSERT(dev_read_block(buffer, blkaddr) >= 0);
+		}
+
+		offset += len_in_blk;
+		count -= len_in_blk;
+		buffer += len_in_blk;
+		read_count += len_in_blk;
+
+		dn.ofs_in_node++;
+		remained_blkentries--;
+	}
+	if (index_node)
+		free(index_node);
+	free(blk_buffer);
+
+	return read_count;
+}
+
+u64 f2fs_write(struct f2fs_sb_info *sbi, nid_t ino, u8 *buffer,
+					u64 count, pgoff_t offset)
+{
+	struct dnode_of_data dn;
+	struct node_info ni;
+	struct f2fs_node *inode;
+	char *blk_buffer;
+	u64 off_in_blk;
+	u64 len_in_blk;
+	u64 written_count;
+	u64 remained_blkentries;
+	block_t blkaddr;
+	void* index_node = NULL;
+	int idirty = 0;
+
+	/* Memory allocation for block buffer and inode. */
+	blk_buffer = calloc(BLOCK_SZ, 2);
+	ASSERT(blk_buffer);
+	inode = (struct f2fs_node*)(blk_buffer + BLOCK_SZ);
+
+	/* Read inode */
+	get_node_info(sbi, ino, &ni);
+	ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
+	ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
+	ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
+
+	/* Main loop for file blocks */
+	written_count = remained_blkentries = 0;
+	while (count > 0) {
+		if (remained_blkentries == 0) {
+			set_new_dnode(&dn, inode, NULL, ino);
+			get_dnode_of_data(sbi, &dn, F2FS_BYTES_TO_BLK(offset),
+					ALLOC_NODE);
+			idirty |= dn.idirty;
+			if (index_node)
+				free(index_node);
+			index_node = (dn.node_blk == dn.inode_blk) ?
+							NULL : dn.node_blk;
+			remained_blkentries = ADDRS_PER_PAGE(dn.node_blk);
+		}
+		ASSERT(remained_blkentries > 0);
+
+		blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
+		if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR) {
+			new_data_block(sbi, blk_buffer, &dn, CURSEG_WARM_DATA);
+			blkaddr = dn.data_blkaddr;
+		}
+
+		off_in_blk = offset % BLOCK_SZ;
+		len_in_blk = BLOCK_SZ - off_in_blk;
+		if (len_in_blk > count)
+			len_in_blk = count;
+
+		/* Write data to single block. */
+		if (len_in_blk < BLOCK_SZ) {
+			ASSERT(dev_read_block(blk_buffer, blkaddr) >= 0);
+			memcpy(blk_buffer + off_in_blk, buffer, len_in_blk);
+			ASSERT(dev_write_block(blk_buffer, blkaddr) >= 0);
+		} else {
+			/* Direct write */
+			ASSERT(dev_write_block(buffer, blkaddr) >= 0);
+		}
+
+		offset += len_in_blk;
+		count -= len_in_blk;
+		buffer += len_in_blk;
+		written_count += len_in_blk;
+
+		dn.ofs_in_node++;
+		if ((--remained_blkentries == 0 || count == 0) && (dn.ndirty))
+			ASSERT(dev_write_block(dn.node_blk, dn.node_blkaddr) >= 0);
+	}
+	if (offset > le64_to_cpu(inode->i.i_size)) {
+		inode->i.i_size = cpu_to_le64(offset);
+		idirty = 1;
+	}
+	if (idirty) {
+		ASSERT(inode == dn.inode_blk);
+		write_inode(ni.blk_addr, inode);
+	}
+	if (index_node)
+		free(index_node);
+	free(blk_buffer);
+
+	return written_count;
+}
+
+/* This function updates only inode->i.i_size */
+void f2fs_filesize_update(struct f2fs_sb_info *sbi, nid_t ino, u64 filesize)
+{
+	struct node_info ni;
+	struct f2fs_node *inode;
+
 	inode = calloc(BLOCK_SZ, 1);
 	ASSERT(inode);
+	get_node_info(sbi, ino, &ni);
 
-	ret = dev_read_block(inode, ni.blk_addr);
-	ASSERT(ret >= 0);
+	ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
+	ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
+	ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
 
-	if (S_ISDIR(le16_to_cpu(inode->i.i_mode)) ||
-			S_ISLNK(le16_to_cpu(inode->i.i_mode)))
-		ASSERT(0);
+	inode->i.i_size = cpu_to_le64(filesize);
 
-	off_in_block = offset & ((1 << F2FS_BLKSIZE_BITS) - 1);
-	len_in_block = (1 << F2FS_BLKSIZE_BITS) - off_in_block;
-	len_already = 0;
-
-	/*
-	 * When calculate how many blocks this 'count' stride accross,
-	 * We should take offset in a block in account.
-	 */
-	len = F2FS_BYTES_TO_BLK(count + off_in_block
-			+ ((1 << F2FS_BLKSIZE_BITS) - 1));
-
-	data_blk = calloc(BLOCK_SZ, 1);
-	ASSERT(data_blk);
-
-	set_new_dnode(&dn, inode, NULL, ino);
-
-	while (len) {
-		if (dn.node_blk != dn.inode_blk)
-			free(dn.node_blk);
-
-		set_new_dnode(&dn, inode, NULL, ino);
-		get_dnode_of_data(sbi, &dn, start, ALLOC_NODE);
-
-		end_offset = ADDRS_PER_PAGE(dn.node_blk);
-
-		while (dn.ofs_in_node < end_offset && len) {
-			block_t blkaddr;
-
-			blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
-
-			/* A new page from WARM_DATA */
-			if (blkaddr == NULL_ADDR)
-				new_data_block(sbi, data_blk, &dn,
-							CURSEG_WARM_DATA);
-
-			/* Copy data from buffer to file */
-			ret = dev_read_block(data_blk, dn.data_blkaddr);
-			ASSERT(ret >= 0);
-
-			memcpy(data_blk + off_in_block, buffer, len_in_block);
-
-			ret = dev_write_block(data_blk, dn.data_blkaddr);
-			ASSERT(ret >= 0);
-
-			off_in_block = 0;
-			len_already += len_in_block;
-			if ((count - len_already) > (1 << F2FS_BLKSIZE_BITS))
-				len_in_block = 1 << F2FS_BLKSIZE_BITS;
-			else
-				len_in_block = count - len_already;
-			len--;
-			start++;
-			dn.ofs_in_node++;
-		}
-		/* Update the direct node */
-		if (dn.ndirty) {
-			ret = dev_write_block(dn.node_blk, dn.node_blkaddr);
-			ASSERT(ret >= 0);
-		}
-	}
-
-	/* Update the inode info */
-	if (le64_to_cpu(inode->i.i_size) < offset + count) {
-		inode->i.i_size = cpu_to_le64(offset + count);
-		dn.idirty = 1;
-	}
-
-	if (dn.idirty) {
-		ASSERT(inode == dn.inode_blk);
-		ret = dev_write_block(inode, ni.blk_addr);
-		ASSERT(ret >= 0);
-	}
-
-	if (dn.node_blk && dn.node_blk != dn.inode_blk)
-		free(dn.node_blk);
-	free(data_blk);
+	write_inode(ni.blk_addr, inode);
 	free(inode);
 }
 
@@ -167,7 +276,7 @@
 {
 	int fd, n;
 	pgoff_t off = 0;
-	char buffer[BLOCK_SZ];
+	u8 buffer[BLOCK_SZ];
 
 	if (de->ino == 0)
 		return -1;
@@ -179,7 +288,7 @@
 	}
 
 	/* inline_data support */
-	if (de->size <= MAX_INLINE_DATA) {
+	if (de->size <= DEF_MAX_INLINE_DATA) {
 		struct node_info ni;
 		struct f2fs_node *node_blk;
 		int ret;
@@ -194,18 +303,21 @@
 
 		node_blk->i.i_inline |= F2FS_INLINE_DATA;
 		node_blk->i.i_inline |= F2FS_DATA_EXIST;
+
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
+			node_blk->i.i_inline |= F2FS_EXTRA_ATTR;
+			node_blk->i.i_extra_isize =
+				cpu_to_le16(F2FS_TOTAL_EXTRA_ATTR_SIZE);
+		}
 		n = read(fd, buffer, BLOCK_SZ);
-		ASSERT(n == de->size);
-		memcpy(&node_blk->i.i_addr[1], buffer, de->size);
-
+		ASSERT((unsigned long)n == de->size);
+		memcpy(inline_data_addr(node_blk), buffer, de->size);
 		node_blk->i.i_size = cpu_to_le64(de->size);
-
-		ret = dev_write_block(node_blk, ni.blk_addr);
-		ASSERT(ret >= 0);
+		write_inode(ni.blk_addr, node_blk);
 		free(node_blk);
 	} else {
 		while ((n = read(fd, buffer, BLOCK_SZ)) > 0) {
-			f2fs_write_block(sbi, de->ino, buffer, n, off);
+			f2fs_write(sbi, de->ino, buffer, n, off);
 			off += n;
 		}
 	}
@@ -216,6 +328,11 @@
 
 	update_free_segments(sbi);
 
-	MSG(1, "Info: built a file %s, size=%lu\n", de->full_path, de->size);
+	MSG(1, "Info: Create %s -> %s\n"
+		"  -- ino=%x, type=%x, mode=%x, uid=%x, "
+		"gid=%x, cap=%"PRIx64", size=%lu, pino=%x\n",
+		de->full_path, de->path,
+		de->ino, de->file_type, de->mode,
+		de->uid, de->gid, de->capabilities, de->size, de->pino);
 	return 0;
 }
diff --git a/fsck/sload.c b/fsck/sload.c
index 68799c1..e9b12e3 100644
--- a/fsck/sload.c
+++ b/fsck/sload.c
@@ -15,33 +15,25 @@
 #include "fsck.h"
 #include <libgen.h>
 #include <dirent.h>
+#ifdef HAVE_MNTENT_H
 #include <mntent.h>
-
-#ifdef HAVE_LIBSELINUX
-#include <selinux/selinux.h>
-#include <selinux/label.h>
 #endif
 
+#ifdef HAVE_LIBSELINUX
+static struct selabel_handle *sehnd = NULL;
+#endif
+
+typedef void (*fs_config_f)(const char *path, int dir,
+			    const char *target_out_path,
+			    unsigned *uid, unsigned *gid,
+			    unsigned *mode, uint64_t *capabilities);
+
+static fs_config_f fs_config_func = NULL;
+
 #ifdef WITH_ANDROID
-#include <selinux/label.h>
+#include <selinux/android.h>
 #include <private/android_filesystem_config.h>
-
-static void handle_selabel(struct dentry *de, int dir, char *target_out)
-{
-	uint64_t capabilities;
-	unsigned int mode = 0;
-	unsigned int uid = 0;
-	unsigned int gid = 0;
-
-	fs_config(de->path, dir, target_out, &uid,
-			&gid, &mode, &capabilities);
-	de->mode = mode;
-	de->uid = uid;
-	de->gid = gid;
-	de->capabilities = capabilities;
-}
-#else
-#define handle_selabel(...)
+#include <private/canned_fs_config.h>
 #endif
 
 static int filter_dot(const struct dirent *d)
@@ -64,14 +56,123 @@
 	}
 }
 
+#ifdef HAVE_LIBSELINUX
+static int set_selinux_xattr(struct f2fs_sb_info *sbi, const char *path,
+							nid_t ino, int mode)
+{
+	char *secontext = NULL;
+	char *mnt_path = NULL;
+
+	if (!sehnd)
+		return 0;
+
+	if (asprintf(&mnt_path, "%s%s", c.mount_point, path) <= 0) {
+		ERR_MSG("cannot allocate security path for %s%s\n",
+						c.mount_point, path);
+		return -ENOMEM;
+	}
+
+	/* set root inode selinux context */
+	if (selabel_lookup(sehnd, &secontext, mnt_path, mode) < 0) {
+		ERR_MSG("cannot lookup security context for %s\n", mnt_path);
+		free(mnt_path);
+		return -EINVAL;
+	}
+
+	if (secontext) {
+		MSG(2, "%s (%d) -> SELinux context = %s\n",
+						mnt_path, ino, secontext);
+		inode_set_selinux(sbi, ino, secontext);
+	}
+	freecon(secontext);
+	free(mnt_path);
+	return 0;
+}
+#else
+#define set_selinux_xattr(...)	0
+#endif
+
+static int set_perms_and_caps(struct dentry *de)
+{
+	uint64_t capabilities = 0;
+	unsigned int uid = 0, gid = 0, imode = 0;
+	char *mnt_path = NULL;
+
+	if (asprintf(&mnt_path, "%s%s", c.mount_point, de->path) <= 0) {
+		ERR_MSG("cannot allocate mount path for %s%s\n",
+				c.mount_point, de->path);
+		return -ENOMEM;
+	}
+
+	/* Permissions */
+	if (fs_config_func != NULL) {
+		fs_config_func(mnt_path, de->file_type == F2FS_FT_DIR,
+				c.target_out_dir, &uid, &gid, &imode,
+				&capabilities);
+		de->uid = uid & 0xffff;
+		de->gid = gid & 0xffff;
+		de->mode = (de->mode & S_IFMT) | (imode & 0xffff);
+		de->capabilities = capabilities;
+	}
+	MSG(2, "%s -> mode = 0x%x, uid = 0x%x, gid = 0x%x, "
+			"capabilities = 0x%"PRIx64"\n",
+		mnt_path, de->mode, de->uid, de->gid, de->capabilities);
+	free(mnt_path);
+	return 0;
+}
+
+static void set_inode_metadata(struct dentry *de)
+{
+	struct stat stat;
+	int ret;
+
+	ret = lstat(de->full_path, &stat);
+	if (ret < 0) {
+		ERR_MSG("lstat failure\n");
+		ASSERT(0);
+	}
+
+	if (S_ISREG(stat.st_mode)) {
+		de->file_type = F2FS_FT_REG_FILE;
+	} else if (S_ISDIR(stat.st_mode)) {
+		de->file_type = F2FS_FT_DIR;
+	} else if (S_ISCHR(stat.st_mode)) {
+		de->file_type = F2FS_FT_CHRDEV;
+	} else if (S_ISBLK(stat.st_mode)) {
+		de->file_type = F2FS_FT_BLKDEV;
+	} else if (S_ISFIFO(stat.st_mode)) {
+		de->file_type = F2FS_FT_FIFO;
+	} else if (S_ISSOCK(stat.st_mode)) {
+		de->file_type = F2FS_FT_SOCK;
+	} else if (S_ISLNK(stat.st_mode)) {
+		de->file_type = F2FS_FT_SYMLINK;
+		de->link = calloc(F2FS_BLKSIZE, 1);
+		ASSERT(de->link);
+		ret = readlink(de->full_path, de->link, F2FS_BLKSIZE - 1);
+		ASSERT(ret >= 0);
+	} else {
+		ERR_MSG("unknown file type on %s", de->path);
+		ASSERT(0);
+	}
+
+	de->size = stat.st_size;
+	de->mode = stat.st_mode &
+			(S_IFMT|S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
+	if (c.fixed_time == -1 && c.from_dir)
+		de->mtime = stat.st_mtime;
+	else
+		de->mtime = c.fixed_time;
+
+	set_perms_and_caps(de);
+}
+
 static int build_directory(struct f2fs_sb_info *sbi, const char *full_path,
 			const char *dir_path, const char *target_out_dir,
-			nid_t dir_ino, struct selabel_handle *sehnd)
+			nid_t dir_ino)
 {
 	int entries = 0;
 	struct dentry *dentries;
 	struct dirent **namelist = NULL;
-	struct stat stat;
 	int i, ret = 0;
 
 	entries = scandir(full_path, &namelist, filter_dot, (void *)alphasort);
@@ -92,7 +193,7 @@
 		}
 		dentries[i].len = strlen((char *)dentries[i].name);
 
-		ret = asprintf(&dentries[i].path, "%s/%s",
+		ret = asprintf(&dentries[i].path, "%s%s",
 					dir_path, namelist[i]->d_name);
 		ASSERT(ret > 0);
 		ret = asprintf(&dentries[i].full_path, "%s/%s",
@@ -100,52 +201,9 @@
 		ASSERT(ret > 0);
 		free(namelist[i]);
 
-		ret = lstat(dentries[i].full_path, &stat);
-		if (ret < 0) {
-			ERR_MSG("Skip: lstat failure\n");
-			continue;
-		}
-		dentries[i].size = stat.st_size;
-		dentries[i].mode = stat.st_mode &
-			(S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
-		dentries[i].mtime = stat.st_mtime;
-
-		handle_selabel(dentries + i, S_ISDIR(stat.st_mode),
-							target_out_dir);
-
-#ifdef HAVE_LIBSELINUX
-		if (sehnd && selabel_lookup(sehnd, &dentries[i].secon,
-					dentries[i].path, stat.st_mode) < 0)
-			ERR_MSG("Cannot lookup security context for %s\n",
-						dentries[i].path);
-#endif
+		set_inode_metadata(dentries + i);
 
 		dentries[i].pino = dir_ino;
-
-		if (S_ISREG(stat.st_mode)) {
-			dentries[i].file_type = F2FS_FT_REG_FILE;
-		} else if (S_ISDIR(stat.st_mode)) {
-			dentries[i].file_type = F2FS_FT_DIR;
-		} else if (S_ISCHR(stat.st_mode)) {
-			dentries[i].file_type = F2FS_FT_CHRDEV;
-		} else if (S_ISBLK(stat.st_mode)) {
-			dentries[i].file_type = F2FS_FT_BLKDEV;
-		} else if (S_ISFIFO(stat.st_mode)) {
-			dentries[i].file_type = F2FS_FT_FIFO;
-		} else if (S_ISSOCK(stat.st_mode)) {
-			dentries[i].file_type = F2FS_FT_SOCK;
-		} else if (S_ISLNK(stat.st_mode)) {
-			dentries[i].file_type = F2FS_FT_SYMLINK;
-			dentries[i].link = calloc(F2FS_BLKSIZE, 1);
-			ASSERT(dentries[i].link);
-			ret = readlink(dentries[i].full_path,
-					dentries[i].link, F2FS_BLKSIZE - 1);
-			ASSERT(ret >= 0);
-		} else {
-			MSG(1, "unknown file type on %s", dentries[i].path);
-			i--;
-			entries--;
-		}
 	}
 
 	free(namelist);
@@ -157,9 +215,9 @@
 			f2fs_build_file(sbi, dentries + i);
 		} else if (dentries[i].file_type == F2FS_FT_DIR) {
 			char *subdir_full_path = NULL;
-			char *subdir_dir_path;
+			char *subdir_dir_path = NULL;
 
-			ret = asprintf(&subdir_full_path, "%s/",
+			ret = asprintf(&subdir_full_path, "%s",
 							dentries[i].full_path);
 			ASSERT(ret > 0);
 			ret = asprintf(&subdir_dir_path, "%s/",
@@ -167,7 +225,7 @@
 			ASSERT(ret > 0);
 
 			build_directory(sbi, subdir_full_path, subdir_dir_path,
-					target_out_dir, dentries[i].ino, sehnd);
+					target_out_dir, dentries[i].ino);
 			free(subdir_full_path);
 			free(subdir_dir_path);
 		} else if (dentries[i].file_type == F2FS_FT_SYMLINK) {
@@ -179,19 +237,10 @@
 			MSG(1, "Error unknown file type\n");
 		}
 
-#ifdef HAVE_LIBSELINUX
-		if (dentries[i].secon) {
-			inode_set_selinux(sbi, dentries[i].ino, dentries[i].secon);
-			MSG(1, "File = %s \n----->SELinux context = %s\n",
-					dentries[i].path, dentries[i].secon);
-			MSG(1, "----->mode = 0x%x, uid = 0x%x, gid = 0x%x, "
-					"capabilities = 0x%lx \n",
-					dentries[i].mode, dentries[i].uid,
-					dentries[i].gid, dentries[i].capabilities);
-		}
-
-		free(dentries[i].secon);
-#endif
+		ret = set_selinux_xattr(sbi, dentries[i].path,
+					dentries[i].ino, dentries[i].mode);
+		if (ret)
+			return ret;
 
 		free(dentries[i].path);
 		free(dentries[i].full_path);
@@ -202,47 +251,71 @@
 	return 0;
 }
 
-int f2fs_sload(struct f2fs_sb_info *sbi, const char *from_dir,
-				const char *mount_point,
-				const char *target_out_dir,
-				struct selabel_handle *sehnd)
+static int configure_files(void)
+{
+#ifdef HAVE_LIBSELINUX
+	if (!c.nr_opt)
+		goto skip;
+#if !defined(__ANDROID__)
+	sehnd = selabel_open(SELABEL_CTX_FILE, c.seopt_file, c.nr_opt);
+	if (!sehnd) {
+		ERR_MSG("Failed to open file contexts \"%s\"",
+					c.seopt_file[0].value);
+			return -EINVAL;
+	}
+#else
+	sehnd = selinux_android_file_context_handle();
+	if (!sehnd) {
+		ERR_MSG("Failed to get android file_contexts\n", c.mount_point);
+		return -EINVAL;
+	}
+#endif
+skip:
+#endif
+#ifdef WITH_ANDROID
+	/* Load the FS config */
+	if (c.fs_config_file) {
+		int ret = load_canned_fs_config(c.fs_config_file);
+
+		if (ret < 0) {
+			ERR_MSG("Failed to load fs_config \"%s\"",
+						c.fs_config_file);
+			return ret;
+		}
+		fs_config_func = canned_fs_config;
+	} else {
+		fs_config_func = fs_config;
+	}
+#endif
+	return 0;
+}
+
+int f2fs_sload(struct f2fs_sb_info *sbi)
 {
 	int ret = 0;
-	nid_t mnt_ino = F2FS_ROOT_INO(sbi);
+
+	ret = configure_files();
+	if (ret) {
+		ERR_MSG("Failed to configure files\n");
+		return ret;
+	}
 
 	/* flush NAT/SIT journal entries */
 	flush_journal_entries(sbi);
 
-	ret = f2fs_find_path(sbi, (char *)mount_point, &mnt_ino);
-	if (ret) {
-		ERR_MSG("Failed to get mount point %s\n", mount_point);
-		return ret;
-	}
-
-	ret = build_directory(sbi, from_dir, mount_point, target_out_dir,
-						mnt_ino, sehnd);
+	ret = build_directory(sbi, c.from_dir, "/",
+					c.target_out_dir, F2FS_ROOT_INO(sbi));
 	if (ret) {
 		ERR_MSG("Failed to build due to %d\n", ret);
 		return ret;
 	}
 
-#ifdef HAVE_LIBSELINUX
-	if (sehnd) {
-		char *secontext = NULL;
-
-		/* set root inode selinux context */
-		if (selabel_lookup(sehnd, &secontext, mount_point, S_IFDIR) < 0)
-			ERR_MSG("cannot lookup security context for %s\n",
-								mount_point);
-		if (secontext) {
-			MSG(1, "Labeling %s as %s, root_ino = %d\n",
-					mount_point, secontext, F2FS_ROOT_INO(sbi));
-			/* xattr_add for root inode */
-			inode_set_selinux(sbi, F2FS_ROOT_INO(sbi), secontext);
-		}
-		free(secontext);
+	ret = set_selinux_xattr(sbi, c.mount_point,
+					F2FS_ROOT_INO(sbi), S_IFDIR);
+	if (ret) {
+		ERR_MSG("Failed to set selinux for root: %d\n", ret);
+		return ret;
 	}
-#endif
 
 	/* update curseg info; can update sit->types */
 	move_curseg_info(sbi, SM_I(sbi)->main_blkaddr);
diff --git a/fsck/xattr.c b/fsck/xattr.c
index 3f5c7d3..1d15d1b 100644
--- a/fsck/xattr.c
+++ b/fsck/xattr.c
@@ -17,10 +17,7 @@
 #include "node.h"
 #include "xattr.h"
 
-#define XATTR_CREATE 0x1
-#define XATTR_REPLACE 0x2
-
-static void *read_all_xattrs(struct f2fs_sb_info *sbi, struct f2fs_node *inode)
+void *read_all_xattrs(struct f2fs_sb_info *sbi, struct f2fs_node *inode)
 {
 	struct f2fs_xattr_header *header;
 	void *txattr_addr;
@@ -80,7 +77,6 @@
 	u64 inline_size = inline_xattr_size(&inode->i);
 	int ret;
 
-	ASSERT(inode->i.i_inline & F2FS_INLINE_XATTR);
 	memcpy(inline_xattr_addr(&inode->i), txattr_addr, inline_size);
 
 	if (hsize <= inline_size)
diff --git a/fsck/xattr.h b/fsck/xattr.h
index b414629..e4a98e2 100644
--- a/fsck/xattr.h
+++ b/fsck/xattr.h
@@ -17,6 +17,9 @@
 #define _XATTR_H_
 
 #include "f2fs.h"
+#ifdef HAVE_SYS_XATTR_H
+#include <sys/xattr.h>
+#endif
 
 struct f2fs_xattr_header {
 	__le32 h_magic;		/* magic number for identification */
@@ -31,10 +34,79 @@
 	char e_name[0];		/* attribute name */
 };
 
+#define FS_KEY_DESCRIPTOR_SIZE 8
+#define FS_KEY_DERIVATION_NONCE_SIZE 16
+
+struct fscrypt_context {
+	u8 format;
+	u8 contents_encryption_mode;
+	u8 filenames_encryption_mode;
+	u8 flags;
+	u8 master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
+	u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE];
+} __attribute__((packed));
+
+#define F2FS_ACL_VERSION	0x0001
+
+struct f2fs_acl_entry {
+	__le16 e_tag;
+	__le16 e_perm;
+	__le32 e_id;
+};
+
+struct f2fs_acl_entry_short {
+	__le16 e_tag;
+	__le16 e_perm;
+};
+
+struct f2fs_acl_header {
+	__le32 a_version;
+};
+
+static inline int f2fs_acl_count(int size)
+{
+	ssize_t s;
+	size -= sizeof(struct f2fs_acl_header);
+	s = size - 4 * sizeof(struct f2fs_acl_entry_short);
+	if (s < 0) {
+		if (size % sizeof(struct f2fs_acl_entry_short))
+			return -1;
+		return size / sizeof(struct f2fs_acl_entry_short);
+	} else {
+		if (s % sizeof(struct f2fs_acl_entry))
+			return -1;
+		return s / sizeof(struct f2fs_acl_entry) + 4;
+	}
+}
+
+#ifndef XATTR_USER_PREFIX
+#define XATTR_USER_PREFIX	"user."
+#endif
+#ifndef XATTR_SECURITY_PREFIX
+#define XATTR_SECURITY_PREFIX	"security."
+#endif
+#ifndef XATTR_TRUSTED_PREFIX
+#define XATTR_TRUSTED_PREFIX	"trusted."
+#endif
+
+#ifndef XATTR_CREATE
+#define XATTR_CREATE 0x1
+#endif
+#ifndef XATTR_REPLACE
+#define XATTR_REPLACE 0x2
+#endif
+
 #define XATTR_ROUND	(3)
 
 #define XATTR_SELINUX_SUFFIX "selinux"
-#define F2FS_XATTR_INDEX_SECURITY	6
+#define F2FS_XATTR_INDEX_USER			1
+#define F2FS_XATTR_INDEX_POSIX_ACL_ACCESS	2
+#define F2FS_XATTR_INDEX_POSIX_ACL_DEFAULT	3
+#define F2FS_XATTR_INDEX_TRUSTED		4
+#define F2FS_XATTR_INDEX_LUSTRE			5
+#define F2FS_XATTR_INDEX_SECURITY		6
+#define F2FS_XATTR_INDEX_ENCRYPTION		9
+
 #define IS_XATTR_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0)
 
 #define XATTR_HDR(ptr)		((struct f2fs_xattr_header *)(ptr))
diff --git a/include/android_config.h b/include/android_config.h
new file mode 100644
index 0000000..4582887
--- /dev/null
+++ b/include/android_config.h
@@ -0,0 +1,68 @@
+#if defined(__linux__)
+#define HAVE_BYTESWAP_H 1
+#define HAVE_FCNTL_H 1
+#define HAVE_FALLOC_H 1
+#define HAVE_LINUX_HDREG_H 1
+#define HAVE_LINUX_LIMITS_H 1
+#define HAVE_POSIX_ACL_H 1
+#define HAVE_LINUX_TYPES_H 1
+#define HAVE_LINUX_XATTR_H 1
+#define HAVE_MNTENT_H 1
+#define HAVE_STDLIB_H 1
+#define HAVE_STRING_H 1
+#define HAVE_SYS_IOCTL_H 1
+#define HAVE_SYS_SYSCALL_H 1
+#define HAVE_SYS_MOUNT_H 1
+#define HAVE_SYS_UTSNAME_H 1
+#define HAVE_SYS_SYSMACROS_H 1
+#define HAVE_SYS_XATTR_H 1
+#define HAVE_UNISTD_H 1
+
+#define HAVE_ADD_KEY 1
+#define HAVE_FALLOCATE 1
+#define HAVE_FSETXATTR 1
+#define HAVE_FSTAT 1
+#define HAVE_FSTAT64 1
+#define HAVE_GETMNTENT 1
+#define HAVE_KEYCTL 1
+#define HAVE_LLSEEK 1
+#define HAVE_LSEEK64 1
+#define HAVE_MEMSET 1
+#define HAVE_SETMNTENT 1
+
+#ifdef WITH_SLOAD
+#define HAVE_LIBSELINUX 1
+#endif
+#endif
+
+#if defined(__APPLE__)
+#define HAVE_FCNTL_H 1
+#define HAVE_FALLOC_H 1
+#define HAVE_POSIX_ACL_H 1
+#define HAVE_STDLIB_H 1
+#define HAVE_STRING_H 1
+#define HAVE_SYS_IOCTL_H 1
+#define HAVE_SYS_SYSCALL_H 1
+#define HAVE_SYS_MOUNT_H 1
+#define HAVE_SYS_UTSNAME_H 1
+#define HAVE_SYS_XATTR_H 1
+#define HAVE_UNISTD_H 1
+
+#define HAVE_ADD_KEY 1
+#define HAVE_FALLOCATE 1
+#define HAVE_FSETXATTR 1
+#define HAVE_FSTAT 1
+#define HAVE_FSTAT64 1
+#define HAVE_GETMNTENT 1
+#define HAVE_KEYCTL 1
+#define HAVE_LLSEEK 1
+#define HAVE_MEMSET 1
+
+#ifdef WITH_SLOAD
+#define HAVE_LIBSELINUX 1
+#endif
+#endif
+
+#if defined(_WIN32)
+#define HAVE_LSEEK64
+#endif
diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
index dd2635b..e800004 100644
--- a/include/f2fs_fs.h
+++ b/include/f2fs_fs.h
@@ -12,28 +12,80 @@
 #ifndef __F2FS_FS_H__
 #define __F2FS_FS_H__
 
-#include <inttypes.h>
-#include <linux/types.h>
-#include <sys/types.h>
-
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
+#ifdef __ANDROID__
+#define WITH_ANDROID
+#endif
+
+#ifdef WITH_ANDROID
+#include <android_config.h>
+#else
+#define WITH_DUMP
+#define WITH_DEFRAG
+#define WITH_RESIZE
+#define WITH_SLOAD
+#endif
+
+#include <inttypes.h>
+#ifdef HAVE_LINUX_TYPES_H
+#include <linux/types.h>
+#endif
+#include <sys/types.h>
+
 #ifdef HAVE_LINUX_BLKZONED_H
 #include <linux/blkzoned.h>
 #endif
 
+#ifdef HAVE_LIBSELINUX
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#endif
+
+#ifdef UNUSED
+#elif defined(__GNUC__)
+# define UNUSED(x) UNUSED_ ## x __attribute__((unused))
+#elif defined(__LCLINT__)
+# define UNUSED(x) x
+#else
+# define UNUSED(x) x
+#endif
+
+#ifdef ANDROID_WINDOWS_HOST
+#undef HAVE_LINUX_TYPES_H
+typedef uint64_t u_int64_t;
+typedef uint32_t u_int32_t;
+typedef uint16_t u_int16_t;
+typedef uint8_t u_int8_t;
+#endif
+
 typedef u_int64_t	u64;
 typedef u_int32_t	u32;
 typedef u_int16_t	u16;
 typedef u_int8_t	u8;
 typedef u32		block_t;
 typedef u32		nid_t;
+#ifndef bool
 typedef u8		bool;
+#endif
 typedef unsigned long	pgoff_t;
 typedef unsigned short	umode_t;
 
+#ifndef HAVE_LINUX_TYPES_H
+typedef u8	__u8;
+typedef u16	__u16;
+typedef u32	__u32;
+typedef u64	__u64;
+typedef u16	__le16;
+typedef u32	__le32;
+typedef u64	__le64;
+typedef u16	__be16;
+typedef u32	__be32;
+typedef u64	__be64;
+#endif
+
 #if HAVE_BYTESWAP_H
 #include <byteswap.h>
 #else
@@ -167,6 +219,14 @@
 		printf("%-30s" fmt, #member, ((ptr)->member));	\
 	} while (0)
 
+#define DISP_u16(ptr, member)						\
+	do {								\
+		assert(sizeof((ptr)->member) == 2);			\
+		printf("%-30s" "\t\t[0x%8x : %u]\n",			\
+			#member, le16_to_cpu(((ptr)->member)),		\
+			le16_to_cpu(((ptr)->member)));			\
+	} while (0)
+
 #define DISP_u32(ptr, member)						\
 	do {								\
 		assert(sizeof((ptr)->member) <= 4);			\
@@ -209,7 +269,9 @@
 		snprintf(buf, len, #member)
 
 /* these are defined in kernel */
+#ifndef PAGE_SIZE
 #define PAGE_SIZE		4096
+#endif
 #define PAGE_CACHE_SIZE		4096
 #define BITS_PER_BYTE		8
 #define F2FS_SUPER_MAGIC	0xF2F52010	/* F2FS Magic Number */
@@ -230,6 +292,7 @@
 #define VERSION_LEN	256
 
 enum f2fs_config_func {
+	MKFS,
 	FSCK,
 	DUMP,
 	DEFRAG,
@@ -272,6 +335,7 @@
 	u_int64_t device_size;
 	u_int64_t total_sectors;
 	u_int64_t wanted_total_sectors;
+	u_int64_t wanted_sector_size;
 	u_int64_t target_sectors;
 	u_int32_t sectors_per_blk;
 	u_int32_t blks_per_seg;
@@ -292,11 +356,13 @@
 	int trimmed;
 	int func;
 	void *private;
+	int dry_run;
 	int fix_on;
 	int bug_on;
 	int auto_fix;
 	int preen_mode;
 	int ro;
+	int preserve_limits;		/* preserve quota limits */
 	__le32 feature;			/* defined features */
 
 	/* defragmentation parameters */
@@ -308,6 +374,16 @@
 	/* sload parameters */
 	char *from_dir;
 	char *mount_point;
+	char *target_out_dir;
+	char *fs_config_file;
+	time_t fixed_time;
+#ifdef HAVE_LIBSELINUX
+	struct selinux_opt seopt_file[8];
+	int nr_opt;
+#endif
+
+	/* precomputed fs UUID checksum for seeding other checksums */
+	u_int32_t chksum_seed;
 };
 
 #ifdef CONFIG_64BIT
@@ -393,6 +469,7 @@
  */
 #define __round_mask(x, y)	((__typeof__(x))((y)-1))
 #define round_down(x, y)	((x) & ~__round_mask(x, y))
+
 #define min(x, y) ({				\
 	typeof(x) _min1 = (x);			\
 	typeof(y) _min2 = (y);			\
@@ -447,6 +524,12 @@
 #define F2FS_NODE_INO(sbi)	(sbi->node_ino_num)
 #define F2FS_META_INO(sbi)	(sbi->meta_ino_num)
 
+#define F2FS_MAX_QUOTAS		3
+#define QUOTA_DATA(i)		(2)
+#define QUOTA_INO(sb,t)	(le32_to_cpu((sb)->qf_ino[t]))
+
+#define FS_IMMUTABLE_FL		0x00000010 /* Immutable file */
+
 /* This flag is used by node and meta inodes, and by recovery */
 #define GFP_F2FS_ZERO	(GFP_NOFS | __GFP_ZERO)
 
@@ -460,14 +543,23 @@
 #define MAX_ACTIVE_NODE_LOGS	8
 #define MAX_ACTIVE_DATA_LOGS	8
 
-#define F2FS_FEATURE_ENCRYPT	0x0001
-#define F2FS_FEATURE_BLKZONED	0x0002
+#define F2FS_FEATURE_ENCRYPT		0x0001
+#define F2FS_FEATURE_BLKZONED		0x0002
+#define F2FS_FEATURE_ATOMIC_WRITE	0x0004
+#define F2FS_FEATURE_EXTRA_ATTR		0x0008
+#define F2FS_FEATURE_PRJQUOTA		0x0010
+#define F2FS_FEATURE_INODE_CHKSUM	0x0020
+#define F2FS_FEATURE_FLEXIBLE_INLINE_XATTR	0x0040
+#define F2FS_FEATURE_QUOTA_INO		0x0080
+#define F2FS_FEATURE_INODE_CRTIME	0x0100
+#define F2FS_FEATURE_VERITY		0x0400	/* reserved */
 
 #define MAX_VOLUME_NAME		512
 
 /*
  * For superblock
  */
+#pragma pack(push, 1)
 struct f2fs_device {
 	__u8 path[MAX_PATH_LEN];
 	__le32 total_segments;
@@ -512,12 +604,14 @@
 	__u8 encryption_level;		/* versioning level for encryption */
 	__u8 encrypt_pw_salt[16];	/* Salt used for string2key algorithm */
 	struct f2fs_device devs[MAX_DEVICES];	/* device list */
-	__u8 reserved[327];		/* valid reserved region */
+	__le32 qf_ino[F2FS_MAX_QUOTAS];	/* quota inode numbers */
+	__u8 reserved[315];		/* valid reserved region */
 } __attribute__((packed));
 
 /*
  * For checkpoint
  */
+#define CP_NOCRC_RECOVERY_FLAG	0x00000200
 #define CP_TRIMMED_FLAG		0x00000100
 #define CP_NAT_BITS_FLAG	0x00000080
 #define CP_CRC_RECOVERY_FLAG	0x00000040
@@ -559,6 +653,9 @@
 	unsigned char sit_nat_version_bitmap[1];
 } __attribute__((packed));
 
+#define MAX_SIT_BITMAP_SIZE_IN_CKPT	\
+	(CHECKSUM_OFFSET - sizeof(struct f2fs_checkpoint) + 1 - 64)
+
 /*
  * For orphan inode management
  */
@@ -583,11 +680,12 @@
 } __attribute__((packed));
 
 #define F2FS_NAME_LEN		255
-#define F2FS_INLINE_XATTR_ADDRS	50	/* 200 bytes for inline xattrs */
+/* 200 bytes for inline xattrs by default */
+#define DEFAULT_INLINE_XATTR_ADDRS	50
 #define DEF_ADDRS_PER_INODE	923	/* Address Pointers in an Inode */
+#define CUR_ADDRS_PER_INODE(inode)	(DEF_ADDRS_PER_INODE - \
+					__get_extra_isize(inode))
 #define ADDRS_PER_INODE(i)	addrs_per_inode(i)
-#define DEF_ADDRS_PER_INODE_INLINE_XATTR				\
-		(DEF_ADDRS_PER_INODE - F2FS_INLINE_XATTR_ADDRS)
 #define ADDRS_PER_BLOCK         1018	/* Address Pointers in a Direct Block */
 #define NIDS_PER_BLOCK          1018	/* Node IDs in an Indirect Block */
 
@@ -602,22 +700,44 @@
 #define F2FS_INLINE_DENTRY	0x04	/* file inline dentry flag */
 #define F2FS_DATA_EXIST		0x08	/* file inline data exist flag */
 #define F2FS_INLINE_DOTS	0x10	/* file having implicit dot dentries */
+#define F2FS_EXTRA_ATTR		0x20	/* file having extra attribute */
 
-#define MAX_INLINE_DATA (sizeof(__le32) *				\
-			(DEF_ADDRS_PER_INODE_INLINE_XATTR - 1))
+#if !defined(offsetof)
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#endif
 
+#define F2FS_TOTAL_EXTRA_ATTR_SIZE			\
+	(offsetof(struct f2fs_inode, i_extra_end) -	\
+	offsetof(struct f2fs_inode, i_extra_isize))	\
+
+#define	F2FS_DEF_PROJID		0	/* default project ID */
+
+#define MAX_INLINE_DATA(node) (sizeof(__le32) *				\
+				(DEF_ADDRS_PER_INODE -			\
+				get_inline_xattr_addrs(&node->i) -	\
+				get_extra_isize(node) -			\
+				DEF_INLINE_RESERVED_SIZE))
+#define DEF_MAX_INLINE_DATA	(sizeof(__le32) *			\
+				(DEF_ADDRS_PER_INODE -			\
+				DEFAULT_INLINE_XATTR_ADDRS -		\
+				F2FS_TOTAL_EXTRA_ATTR_SIZE -		\
+				DEF_INLINE_RESERVED_SIZE))
 #define INLINE_DATA_OFFSET	(PAGE_CACHE_SIZE - sizeof(struct node_footer) \
-				- sizeof(__le32)*(DEF_ADDRS_PER_INODE + 5 - 1))
+				- sizeof(__le32)*(DEF_ADDRS_PER_INODE + 5 - \
+				DEF_INLINE_RESERVED_SIZE))
 
 #define DEF_DIR_LEVEL		0
 
 /*
  * i_advise uses FADVISE_XXX_BIT. We can add additional hints later.
  */
-#define FADVISE_COLD_BIT       0x01
-#define FADVISE_LOST_PINO_BIT  0x02
-#define FADVISE_ENCRYPT_BIT    0x04
-#define FADVISE_ENC_NAME_BIT   0x08
+#define FADVISE_COLD_BIT	0x01
+#define FADVISE_LOST_PINO_BIT	0x02
+#define FADVISE_ENCRYPT_BIT	0x04
+#define FADVISE_ENC_NAME_BIT	0x08
+#define FADVISE_KEEP_SIZE_BIT	0x10
+#define FADVISE_HOT_BIT		0x20
+#define FADVISE_VERITY_BIT	0x40	/* reserved */
 
 #define file_is_encrypt(fi)      ((fi)->i_advise & FADVISE_ENCRYPT_BIT)
 #define file_enc_name(fi)        ((fi)->i_advise & FADVISE_ENC_NAME_BIT)
@@ -648,12 +768,23 @@
 
 	struct f2fs_extent i_ext;	/* caching a largest extent */
 
-	__le32 i_addr[DEF_ADDRS_PER_INODE];	/* Pointers to data blocks */
-
+	union {
+		struct {
+			__le16 i_extra_isize;	/* extra inode attribute size */
+			__le16 i_inline_xattr_size;	/* inline xattr size, unit: 4 bytes */
+			__le32 i_projid;	/* project id */
+			__le32 i_inode_checksum;/* inode meta checksum */
+			__le64 i_crtime;	/* creation time */
+			__le32 i_crtime_nsec;	/* creation time in nano scale */
+			__le32 i_extra_end[0];	/* for attribute size calculation */
+		} __attribute__((packed));
+		__le32 i_addr[DEF_ADDRS_PER_INODE];	/* Pointers to data blocks */
+	};
 	__le32 i_nid[5];		/* direct(2), indirect(2),
 						double_indirect(1) node id */
 } __attribute__((packed));
 
+
 struct direct_node {
 	__le32 addr[ADDRS_PER_BLOCK];	/* array of data block address */
 } __attribute__((packed));
@@ -671,7 +802,6 @@
 
 #define XATTR_NODE_OFFSET	((((unsigned int)-1) << OFFSET_BIT_SHIFT) \
 				>> OFFSET_BIT_SHIFT)
-
 struct node_footer {
 	__le32 nid;		/* node id */
 	__le32 ino;		/* inode nunmber */
@@ -721,7 +851,7 @@
  * disk is 16 TB and it equals to 16 * 1024 * 1024 / 2 segments.
  */
 #define F2FS_MAX_SEGMENT       ((16 * 1024 * 1024) / 2)
-#define MAX_SIT_BITMAP_SIZE    (SEG_ALIGN(ALIGN(F2FS_MAX_SEGMENT, \
+#define MAX_SIT_BITMAP_SIZE    (SEG_ALIGN(SIZE_ALIGN(F2FS_MAX_SEGMENT, \
 						SIT_ENTRY_PER_BLOCK)) * \
 						c.blks_per_seg / 8)
 
@@ -908,24 +1038,21 @@
 	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
 
 /* for inline dir */
-#define NR_INLINE_DENTRY	(MAX_INLINE_DATA * BITS_PER_BYTE / \
+#define NR_INLINE_DENTRY(node)	(MAX_INLINE_DATA(node) * BITS_PER_BYTE / \
 				((SIZE_OF_DIR_ENTRY + F2FS_SLOT_LEN) * \
 				BITS_PER_BYTE + 1))
-#define INLINE_DENTRY_BITMAP_SIZE	((NR_INLINE_DENTRY + \
+#define INLINE_DENTRY_BITMAP_SIZE(node)	((NR_INLINE_DENTRY(node) + \
 					BITS_PER_BYTE - 1) / BITS_PER_BYTE)
-#define INLINE_RESERVED_SIZE	(MAX_INLINE_DATA - \
+#define INLINE_RESERVED_SIZE(node)	(MAX_INLINE_DATA(node) - \
 				((SIZE_OF_DIR_ENTRY + F2FS_SLOT_LEN) * \
-				NR_INLINE_DENTRY + INLINE_DENTRY_BITMAP_SIZE))
-
-/* inline directory entry structure */
-struct f2fs_inline_dentry {
-	__u8 dentry_bitmap[INLINE_DENTRY_BITMAP_SIZE];
-	__u8 reserved[INLINE_RESERVED_SIZE];
-	struct f2fs_dir_entry dentry[NR_INLINE_DENTRY];
-	__u8 filename[NR_INLINE_DENTRY][F2FS_SLOT_LEN];
-} __attribute__((packed));
+				NR_INLINE_DENTRY(node) + \
+				INLINE_DENTRY_BITMAP_SIZE(node)))
 
 /* file types used in inode_info->flags */
 enum FILE_TYPE {
@@ -954,6 +1081,7 @@
 extern int utf16_to_utf8(char *, const u_int16_t *, size_t, size_t);
 extern int log_base_2(u_int32_t);
 extern unsigned int addrs_per_inode(struct f2fs_inode *);
+extern __u32 f2fs_inode_chksum(struct f2fs_node *);
 
 extern int get_bits_in_byte(unsigned char n);
 extern int test_and_set_bit_le(u32, u8 *);
@@ -973,7 +1101,9 @@
 extern int f2fs_dev_is_umounted(char *);
 extern int f2fs_get_device_info(void);
 extern int get_device_info(int);
-extern void f2fs_finalize_device(void);
+extern int f2fs_init_sparse_file(void);
+extern int f2fs_finalize_device(void);
+extern int f2fs_fsync_device(void);
 
 extern int dev_read(void *, __u64, size_t);
 extern int dev_write(void *, __u64, size_t);
@@ -988,8 +1118,35 @@
 
 extern int dev_read_version(void *, __u64, size_t);
 extern void get_kernel_version(__u8 *);
+extern void get_kernel_uname_version(__u8 *);
 f2fs_hash_t f2fs_dentry_hash(const unsigned char *, int);
 
+static inline bool f2fs_has_extra_isize(struct f2fs_inode *inode)
+{
+	return (inode->i_inline & F2FS_EXTRA_ATTR);
+}
+
+static inline int __get_extra_isize(struct f2fs_inode *inode)
+{
+	if (f2fs_has_extra_isize(inode))
+		return le16_to_cpu(inode->i_extra_isize) / sizeof(__le32);
+	return 0;
+}
+
+extern struct f2fs_configuration c;
+static inline int get_inline_xattr_addrs(struct f2fs_inode *inode)
+{
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_FLEXIBLE_INLINE_XATTR))
+		return le16_to_cpu(inode->i_inline_xattr_size);
+	else if (inode->i_inline & F2FS_INLINE_XATTR ||
+			inode->i_inline & F2FS_INLINE_DENTRY)
+		return DEFAULT_INLINE_XATTR_ADDRS;
+	else
+		return 0;
+}
+
+#define get_extra_isize(node)	__get_extra_isize(&node->i)
+
 #define F2FS_ZONED_NONE		0
 #define F2FS_ZONED_HA		1
 #define F2FS_ZONED_HM		2
@@ -1059,9 +1216,9 @@
 
 extern struct f2fs_configuration c;
 
-#define ALIGN(val, size)	((val) + (size) - 1) / (size)
-#define SEG_ALIGN(blks)		ALIGN(blks, c.blks_per_seg)
-#define ZONE_ALIGN(blks)	ALIGN(blks, c.blks_per_seg * \
+#define SIZE_ALIGN(val, size)	((val) + (size) - 1) / (size)
+#define SEG_ALIGN(blks)		SIZE_ALIGN(blks, c.blks_per_seg)
+#define ZONE_ALIGN(blks)	SIZE_ALIGN(blks, c.blks_per_seg * \
 					c.segs_per_zone)
 
 static inline double get_best_overprovision(struct f2fs_super_block *sb)
@@ -1103,4 +1260,24 @@
 	return cpu_to_le64(cp_ver);
 }
 
+static inline int exist_qf_ino(struct f2fs_super_block *sb)
+{
+	int i;
+
+	for (i = 0; i < F2FS_MAX_QUOTAS; i++)
+		if (sb->qf_ino[i])
+			return 1;
+	return 0;
+}
+
+static inline int is_qf_ino(struct f2fs_super_block *sb, nid_t ino)
+{
+	int i;
+
+	for (i = 0; i < F2FS_MAX_QUOTAS; i++)
+		if (sb->qf_ino[i] == ino)
+			return 1;
+	return 0;
+}
+
 #endif	/*__F2FS_FS_H */
diff --git a/include/list.h b/include/list.h
deleted file mode 100644
index 571cd5c..0000000
--- a/include/list.h
+++ /dev/null
@@ -1,88 +0,0 @@
-
-#define POISON_POINTER_DELTA 0
-#define LIST_POISON1  ((void *) (0x00100100 + POISON_POINTER_DELTA))
-#define LIST_POISON2  ((void *) (0x00200200 + POISON_POINTER_DELTA))
-
-#if !defined(offsetof)
-#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
-#endif
-#define container_of(ptr, type, member) ({                      \
-		const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
-		(type *)( (char *)__mptr - offsetof(type,member) );})
-
-struct list_head {
-	struct list_head *next, *prev;
-};
-
-#define LIST_HEAD_INIT(name) { &(name), &(name) }
-
-#define LIST_HEAD(name) \
-	struct list_head name = LIST_HEAD_INIT(name)
-
-static inline void INIT_LIST_HEAD(struct list_head *list)
-{
-	list->next = list;
-	list->prev = list;
-}
-
-static inline void __list_add(struct list_head *new,
-		struct list_head *prev,
-		struct list_head *next)
-{
-	next->prev = new;
-	new->next = next;
-	new->prev = prev;
-	prev->next = new;
-}
-
-static inline void list_add(struct list_head *new, struct list_head *head)
-{
-	__list_add(new, head, head->next);
-}
-
-static inline void list_add_tail(struct list_head *new, struct list_head *head)
-{
-	__list_add(new, head->prev, head);
-}
-
-static inline void __list_del(struct list_head * prev, struct list_head * next)
-{
-	next->prev = prev;
-	prev->next = next;
-}
-
-static inline void __list_del_entry(struct list_head *entry)
-{
-	__list_del(entry->prev, entry->next);
-}
-
-static inline void list_del(struct list_head *entry)
-{
-	__list_del(entry->prev, entry->next);
-	entry->next = LIST_POISON1;
-	entry->prev = LIST_POISON2;
-}
-
-static inline int list_empty(const struct list_head *head)
-{
-	return head->next == head;
-}
-
-#define list_entry(ptr, type, member) \
-	container_of(ptr, type, member)
-
-#define list_for_each(pos, head) \
-	for (pos = (head)->next; pos != (head); pos = pos->next)
-
-#define list_for_each_safe(pos, n, head) \
-	for (pos = (head)->next, n = pos->next; pos != (head); \
-			pos = n, n = pos->next)
-#define list_for_each_entry(pos, head, member)                          \
-	for (pos = list_entry((head)->next, typeof(*pos), member);      \
-			&pos->member != (head);    \
-			pos = list_entry(pos->member.next, typeof(*pos), member))
-#define list_for_each_entry_safe(pos, n, head, member)                  \
-	for (pos = list_entry((head)->next, typeof(*pos), member),      \
-			n = list_entry(pos->member.next, typeof(*pos), member); \
-			&pos->member != (head);                                    \
-			pos = n, n = list_entry(n->member.next, typeof(*n), member))
diff --git a/include/quota.h b/include/quota.h
new file mode 100644
index 0000000..f578621
--- /dev/null
+++ b/include/quota.h
@@ -0,0 +1,79 @@
+/*
+ *
+ * Header file for disk format of new quotafile format
+ *
+ * Copied essential definitions and structures for mkfs.f2fs from quotaio.h
+ *
+ * Aditya Kali <adityakali@google.com>
+ * Jan Kara <jack@suse.cz>
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ *
+ */
+#ifndef F2FS_QUOTA_H
+#define F2FS_QUOTA_H
+
+enum quota_type {
+	USRQUOTA = 0,
+	GRPQUOTA = 1,
+	PRJQUOTA = 2,
+	MAXQUOTAS = 3,
+};
+
+#if MAXQUOTAS > 32
+#error "cannot have more than 32 quota types to fit in qtype_bits"
+#endif
+
+#define QUOTA_USR_BIT (1 << USRQUOTA)
+#define QUOTA_GRP_BIT (1 << GRPQUOTA)
+#define QUOTA_PRJ_BIT (1 << PRJQUOTA)
+#define QUOTA_ALL_BIT (QUOTA_USR_BIT | QUOTA_GRP_BIT | QUOTA_PRJ_BIT)
+
+/*
+ * Definitions of magics and versions of current quota files
+ */
+#define INITQMAGICS {\
+	0xd9c01f11,	/* USRQUOTA */\
+	0xd9c01927,	/* GRPQUOTA */\
+	0xd9c03f14      /* PRJQUOTA */\
+}
+
+#define V2_DQINFOOFF	sizeof(struct v2_disk_dqheader)	/* Offset of info header in file */
+
+#define MAX_IQ_TIME  604800	/* (7*24*60*60) 1 week */
+#define MAX_DQ_TIME  604800	/* (7*24*60*60) 1 week */
+
+#define QT_TREEOFF	1	/* Offset of tree in file in blocks */
+
+#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 */
+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 */
+	u_int32_t dqi_flags;	/* Flags for quotafile (DQF_*) */
+	u_int32_t dqi_blocks;	/* Number of blocks in file */
+	u_int32_t dqi_free_blk;	/* Number of first free block in the list */
+	u_int32_t dqi_free_entry;	/* Number of block with at least one free entry */
+} __attribute__ ((packed));
+
+struct v2r1_disk_dqblk {
+	__le32 dqb_id;  	/* id this quota applies to */
+	__le32 dqb_pad;
+	__le64 dqb_ihardlimit;  /* absolute limit on allocated inodes */
+	__le64 dqb_isoftlimit;  /* preferred inode limit */
+	__le64 dqb_curinodes;   /* current # allocated inodes */
+	__le64 dqb_bhardlimit;  /* absolute limit on disk space
+				 * (in QUOTABLOCK_SIZE) */
+	__le64 dqb_bsoftlimit;  /* preferred limit on disk space
+				 * (in QUOTABLOCK_SIZE) */
+	__le64 dqb_curspace;    /* current space occupied (in bytes) */
+	__le64 dqb_btime;       /* time limit for excessive disk use */
+	__le64 dqb_itime;       /* time limit for excessive inode use */
+} __attribute__ ((packed));
+#pragma pack(pop)
+
+#endif
diff --git a/lib/libf2fs.c b/lib/libf2fs.c
index 31836db..5ef0214 100644
--- a/lib/libf2fs.c
+++ b/lib/libf2fs.c
@@ -8,32 +8,50 @@
  */
 #define _LARGEFILE64_SOURCE
 
+#include <f2fs_fs.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <unistd.h>
 #include <fcntl.h>
+#ifdef HAVE_MNTENT_H
 #include <mntent.h>
+#endif
 #include <time.h>
 #include <sys/stat.h>
+#ifndef ANDROID_WINDOWS_HOST
 #include <sys/mount.h>
 #include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h>
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
 #ifndef WITH_ANDROID
+#ifdef HAVE_SCSI_SG_H
 #include <scsi/sg.h>
 #endif
+#endif
+#ifdef HAVE_LINUX_HDREG_H
 #include <linux/hdreg.h>
+#endif
+#ifdef HAVE_LINUX_LIMITS_H
 #include <linux/limits.h>
-
-#include <f2fs_fs.h>
+#endif
 
 #ifndef WITH_ANDROID
 /* SCSI command for standard inquiry*/
 #define MODELINQUIRY	0x12,0x00,0x00,0x00,0x4A,0x00
 #endif
 
-#ifndef _WIN32 /* O_BINARY is windows-specific flag */
+#ifndef ANDROID_WINDOWS_HOST /* O_BINARY is windows-specific flag */
 #define O_BINARY 0
+#else
+/* On Windows, wchar_t is 8 bit sized and it causes compilation errors. */
+#define wchar_t	int
 #endif
 
 /*
@@ -456,9 +474,7 @@
 
 unsigned int addrs_per_inode(struct f2fs_inode *i)
 {
-	if (i->i_inline & F2FS_INLINE_XATTR)
-		return DEF_ADDRS_PER_INODE - F2FS_INLINE_XATTR_ADDRS;
-	return DEF_ADDRS_PER_INODE;
+	return CUR_ADDRS_PER_INODE(i) - get_inline_xattr_addrs(i);
 }
 
 /*
@@ -493,11 +509,36 @@
 	return 0;
 }
 
+__u32 f2fs_inode_chksum(struct f2fs_node *node)
+{
+	struct f2fs_inode *ri = &node->i;
+	__le32 ino = node->footer.ino;
+	__le32 gen = ri->i_generation;
+	__u32 chksum, chksum_seed;
+	__u32 dummy_cs = 0;
+	unsigned int offset = offsetof(struct f2fs_inode, i_inode_checksum);
+	unsigned int cs_size = sizeof(dummy_cs);
+
+	chksum = f2fs_cal_crc32(c.chksum_seed, (__u8 *)&ino,
+							sizeof(ino));
+	chksum_seed = f2fs_cal_crc32(chksum, (__u8 *)&gen, sizeof(gen));
+
+	chksum = f2fs_cal_crc32(chksum_seed, (__u8 *)ri, offset);
+	chksum = f2fs_cal_crc32(chksum, (__u8 *)&dummy_cs, cs_size);
+	offset += cs_size;
+	chksum = f2fs_cal_crc32(chksum, (__u8 *)ri + offset,
+						F2FS_BLKSIZE - offset);
+	return chksum;
+}
+
 /*
  * try to identify the root device
  */
 const char *get_rootdev()
 {
+#if defined(ANDROID_WINDOWS_HOST) || defined(WITH_ANDROID)
+	return NULL;
+#else
 	struct stat sb;
 	int fd, ret;
 	char buf[32];
@@ -538,6 +579,7 @@
 	snprintf(rootdev, PATH_MAX + 1, "/dev/%s", buf);
 
 	return rootdev;
+#endif
 }
 
 /*
@@ -554,9 +596,15 @@
 	c.blks_per_seg = DEFAULT_BLOCKS_PER_SEGMENT;
 	c.rootdev_name = get_rootdev();
 	c.wanted_total_sectors = -1;
+	c.wanted_sector_size = -1;
 	c.zoned_mode = 0;
 	c.zoned_model = 0;
 	c.zone_blocks = 0;
+#ifdef WITH_ANDROID
+	c.preserve_limits = 0;
+#else
+	c.preserve_limits = 1;
+#endif
 
 	for (i = 0; i < MAX_DEVICES; i++) {
 		memset(&c.devices[i], 0, sizeof(struct device_info));
@@ -578,8 +626,11 @@
 	c.trimmed = 0;
 	c.ro = 0;
 	c.kd = -1;
+	c.dry_run = 0;
+	c.fixed_time = -1;
 }
 
+#ifdef HAVE_SETMNTENT
 static int is_mounted(const char *mpt, const char *device)
 {
 	FILE *file = NULL;
@@ -601,10 +652,14 @@
 	endmntent(file);
 	return mnt ? 1 : 0;
 }
+#endif
 
 int f2fs_dev_is_umounted(char *path)
 {
-	struct stat st_buf;
+#ifdef ANDROID_WINDOWS_HOST
+	return 0;
+#else
+	struct stat *st_buf;
 	int is_rootdev = 0;
 	int ret = 0;
 
@@ -615,46 +670,57 @@
 	 * try with /proc/mounts fist to detect RDONLY.
 	 * f2fs_stop_checkpoint makes RO in /proc/mounts while RW in /etc/mtab.
 	 */
+#ifdef __linux__
 	ret = is_mounted("/proc/mounts", path);
 	if (ret) {
 		MSG(0, "Info: Mounted device!\n");
 		return -1;
 	}
-
+#endif
+#if defined(MOUNTED) || defined(_PATH_MOUNTED)
+#ifndef MOUNTED
+#define MOUNTED _PATH_MOUNTED
+#endif
 	ret = is_mounted(MOUNTED, path);
 	if (ret) {
 		MSG(0, "Info: Mounted device!\n");
 		return -1;
 	}
-
+#endif
 	/*
 	 * If we are supposed to operate on the root device, then
 	 * also check the mounts for '/dev/root', which sometimes
 	 * functions as an alias for the root device.
 	 */
 	if (is_rootdev) {
+#ifdef __linux__
 		ret = is_mounted("/proc/mounts", "/dev/root");
 		if (ret) {
 			MSG(0, "Info: Mounted device!\n");
 			return -1;
 		}
+#endif
 	}
 
 	/*
 	 * If f2fs is umounted with -l, the process can still use
 	 * the file system. In this case, we should not format.
 	 */
-	if (stat(path, &st_buf) == 0 && S_ISBLK(st_buf.st_mode)) {
+	st_buf = malloc(sizeof(struct stat));
+	if (stat(path, st_buf) == 0 && S_ISBLK(st_buf->st_mode)) {
 		int fd = open(path, O_RDONLY | O_EXCL);
 
 		if (fd >= 0) {
 			close(fd);
 		} else if (errno == EBUSY) {
 			MSG(0, "\tError: In use by the system!\n");
+			free(st_buf);
 			return -1;
 		}
 	}
-	return 0;
+	free(st_buf);
+	return ret;
+#endif
 }
 
 int f2fs_devs_are_umounted(void)
@@ -677,6 +743,41 @@
 	memset(version + i, 0, VERSION_LEN + 1 - i);
 }
 
+void get_kernel_uname_version(__u8 *version)
+{
+#ifdef HAVE_SYS_UTSNAME_H
+	struct utsname buf;
+
+	memset(version, 0, VERSION_LEN);
+	if (uname(&buf))
+		return;
+
+	snprintf((char *)version,
+		VERSION_LEN, "%s %s", buf.release, buf.version);
+#else
+	memset(version, 0, VERSION_LEN);
+#endif
+}
+
+#if defined(__linux__) && defined(_IO) && !defined(BLKGETSIZE)
+#define BLKGETSIZE	_IO(0x12,96)
+#endif
+
+#if defined(__linux__) && defined(_IOR) && !defined(BLKGETSIZE64)
+#define BLKGETSIZE64	_IOR(0x12,114, size_t)
+#endif
+
+#if defined(__linux__) && defined(_IO) && !defined(BLKSSZGET)
+#define BLKSSZGET	_IO(0x12,104)
+#endif
+
+#if defined(__APPLE__)
+#include <sys/disk.h>
+#define BLKGETSIZE	DKIOCGETBLOCKCOUNT
+#define BLKSSZGET	DKIOCGETBLOCKCOUNT
+#endif /* APPLE_DARWIN */
+
+#ifndef ANDROID_WINDOWS_HOST
 int get_device_info(int i)
 {
 	int32_t fd = 0;
@@ -684,9 +785,11 @@
 #ifndef BLKGETSIZE64
 	uint32_t total_sectors;
 #endif
-	struct stat stat_buf;
+	struct stat *stat_buf;
+#ifdef HDIO_GETGIO
 	struct hd_geometry geom;
-#ifndef WITH_ANDROID
+#endif
+#if !defined(WITH_ANDROID) && defined(__linux__)
 	sg_io_hdr_t io_hdr;
 	unsigned char reply_buffer[96] = {0};
 	unsigned char model_inq[6] = {MODELINQUIRY};
@@ -694,7 +797,7 @@
 	struct device_info *dev = c.devices + i;
 
 	if (c.sparse_mode) {
-		fd = open((char *)dev->path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
+		fd = open((char *)dev->path, O_RDWR | O_CREAT | O_BINARY, 0644);
 	} else {
 		fd = open((char *)dev->path, O_RDWR);
 	}
@@ -705,36 +808,49 @@
 
 	dev->fd = fd;
 
+	if (c.sparse_mode) {
+		if (f2fs_init_sparse_file())
+			return -1;
+	}
+
 	if (c.kd == -1) {
+#if !defined(WITH_ANDROID) && defined(__linux__)
 		c.kd = open("/proc/version", O_RDONLY);
+#endif
 		if (c.kd < 0) {
 			MSG(0, "\tInfo: No support kernel version!\n");
 			c.kd = -2;
 		}
 	}
 
-	if (fstat(fd, &stat_buf) < 0 ) {
+	stat_buf = malloc(sizeof(struct stat));
+	if (fstat(fd, stat_buf) < 0 ) {
 		MSG(0, "\tError: Failed to get the device stat!\n");
+		free(stat_buf);
 		return -1;
 	}
 
 	if (c.sparse_mode) {
 		dev->total_sectors = c.device_size / dev->sector_size;
-	} else if (S_ISREG(stat_buf.st_mode)) {
-		dev->total_sectors = stat_buf.st_size / dev->sector_size;
-	} else if (S_ISBLK(stat_buf.st_mode)) {
+	} else if (S_ISREG(stat_buf->st_mode)) {
+		dev->total_sectors = stat_buf->st_size / dev->sector_size;
+	} else if (S_ISBLK(stat_buf->st_mode)) {
+#ifdef BLKSSZGET
 		if (ioctl(fd, BLKSSZGET, &sector_size) < 0)
 			MSG(0, "\tError: Using the default sector size\n");
 		else if (dev->sector_size < sector_size)
 			dev->sector_size = sector_size;
+#endif
 #ifdef BLKGETSIZE64
 		if (ioctl(fd, BLKGETSIZE64, &dev->total_sectors) < 0) {
 			MSG(0, "\tError: Cannot get the device size\n");
+			free(stat_buf);
 			return -1;
 		}
 #else
 		if (ioctl(fd, BLKGETSIZE, &total_sectors) < 0) {
 			MSG(0, "\tError: Cannot get the device size\n");
+			free(stat_buf);
 			return -1;
 		}
 		dev->total_sectors = total_sectors;
@@ -742,13 +858,17 @@
 		dev->total_sectors /= dev->sector_size;
 
 		if (i == 0) {
+#ifdef HDIO_GETGIO
 			if (ioctl(fd, HDIO_GETGEO, &geom) < 0)
 				c.start_sector = 0;
 			else
 				c.start_sector = geom.start;
+#else
+			c.start_sector = 0;
+#endif
 		}
 
-#ifndef WITH_ANDROID
+#if !defined(WITH_ANDROID) && defined(__linux__)
 		/* Send INQUIRY command */
 		memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
 		io_hdr.interface_id = 'S';
@@ -771,6 +891,7 @@
 #endif
 	} else {
 		MSG(0, "\tError: Volume type is not supported!!!\n");
+		free(stat_buf);
 		return -1;
 	}
 
@@ -779,11 +900,12 @@
 		c.sectors_per_blk = F2FS_BLKSIZE / c.sector_size;
 	} else if (c.sector_size != c.devices[i].sector_size) {
 		MSG(0, "\tError: Different sector sizes!!!\n");
+		free(stat_buf);
 		return -1;
 	}
 
-#ifndef WITH_ANDROID
-	if (S_ISBLK(stat_buf.st_mode))
+#if !defined(WITH_ANDROID) && defined(__linux__)
+	if (S_ISBLK(stat_buf->st_mode))
 		f2fs_get_zoned_model(i);
 
 	if (dev->zoned_model != F2FS_ZONED_NONE) {
@@ -792,11 +914,13 @@
 
 		if (f2fs_get_zone_blocks(i)) {
 			MSG(0, "\tError: Failed to get number of blocks per zone\n");
+			free(stat_buf);
 			return -1;
 		}
 
 		if (f2fs_check_zones(i)) {
 			MSG(0, "\tError: Failed to check zone configuration\n");
+			free(stat_buf);
 			return -1;
 		}
 		MSG(0, "Info: Host-%s zoned block device:\n",
@@ -808,10 +932,119 @@
 				dev->zone_blocks);
 	}
 #endif
+	/* adjust wanted_total_sectors */
+	if (c.wanted_total_sectors != -1) {
+		MSG(0, "Info: wanted sectors = %"PRIu64" (in %"PRIu64" bytes)\n",
+				c.wanted_total_sectors, c.wanted_sector_size);
+		if (c.wanted_sector_size == -1) {
+			c.wanted_sector_size = dev->sector_size;
+		} else if (dev->sector_size != c.wanted_sector_size) {
+			c.wanted_total_sectors *= c.wanted_sector_size;
+			c.wanted_total_sectors /= dev->sector_size;
+		}
+	}
+
 	c.total_sectors += dev->total_sectors;
+	free(stat_buf);
 	return 0;
 }
 
+#else
+
+#include "windows.h"
+#include "winioctl.h"
+
+#if (_WIN32_WINNT >= 0x0500)
+#define HAVE_GET_FILE_SIZE_EX 1
+#endif
+
+static int win_get_device_size(const char *file, uint64_t *device_size)
+{
+	HANDLE dev;
+	PARTITION_INFORMATION pi;
+	DISK_GEOMETRY gi;
+	DWORD retbytes;
+#ifdef HAVE_GET_FILE_SIZE_EX
+	LARGE_INTEGER filesize;
+#else
+	DWORD filesize;
+#endif /* HAVE_GET_FILE_SIZE_EX */
+
+	dev = CreateFile(file, GENERIC_READ,
+			FILE_SHARE_READ | FILE_SHARE_WRITE ,
+			NULL,  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,  NULL);
+
+	if (dev == INVALID_HANDLE_VALUE)
+		return EBADF;
+	if (DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO,
+				&pi, sizeof(PARTITION_INFORMATION),
+				&pi, sizeof(PARTITION_INFORMATION),
+				&retbytes, NULL)) {
+
+		*device_size = 	pi.PartitionLength.QuadPart;
+
+	} else if (DeviceIoControl(dev, IOCTL_DISK_GET_DRIVE_GEOMETRY,
+				&gi, sizeof(DISK_GEOMETRY),
+				&gi, sizeof(DISK_GEOMETRY),
+				&retbytes, NULL)) {
+
+		*device_size = gi.BytesPerSector *
+			gi.SectorsPerTrack *
+			gi.TracksPerCylinder *
+			gi.Cylinders.QuadPart;
+
+#ifdef HAVE_GET_FILE_SIZE_EX
+	} else if (GetFileSizeEx(dev, &filesize)) {
+		*device_size = filesize.QuadPart;
+	}
+#else
+	} else {
+		filesize = GetFileSize(dev, NULL);
+		if (INVALID_FILE_SIZE != filesize)
+			return -1;
+		*device_size = filesize;
+	}
+#endif /* HAVE_GET_FILE_SIZE_EX */
+
+	CloseHandle(dev);
+	return 0;
+}
+
+int get_device_info(int i)
+{
+	struct device_info *dev = c.devices + i;
+	uint64_t device_size = 0;
+	int32_t fd = 0;
+
+	/* Block device target is not supported on Windows. */
+	if (!c.sparse_mode) {
+		if (win_get_device_size(dev->path, &device_size)) {
+			MSG(0, "\tError: Failed to get device size!\n");
+			return -1;
+		}
+	} else {
+		device_size = c.device_size;
+	}
+	if (c.sparse_mode) {
+		fd = open((char *)dev->path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
+	} else {
+		fd = open((char *)dev->path, O_RDWR | O_BINARY);
+	}
+	if (fd < 0) {
+		MSG(0, "\tError: Failed to open the device!\n");
+		return -1;
+	}
+	dev->fd = fd;
+	dev->total_sectors = device_size / dev->sector_size;
+	c.start_sector = 0;
+	c.sector_size = dev->sector_size;
+	c.sectors_per_blk = F2FS_BLKSIZE / c.sector_size;
+	c.total_sectors += dev->total_sectors;
+
+	return 0;
+}
+#endif
+
 int f2fs_get_device_info(void)
 {
 	int i;
diff --git a/lib/libf2fs_io.c b/lib/libf2fs_io.c
index aa99068..4781517 100644
--- a/lib/libf2fs_io.c
+++ b/lib/libf2fs_io.c
@@ -14,12 +14,18 @@
 #include <errno.h>
 #include <unistd.h>
 #include <fcntl.h>
+#ifdef HAVE_MNTENT_H
 #include <mntent.h>
+#endif
 #include <time.h>
+#ifndef ANDROID_WINDOWS_HOST
 #include <sys/stat.h>
 #include <sys/mount.h>
 #include <sys/ioctl.h>
+#endif
+#ifdef HAVE_LINUX_HDREG_H
 #include <linux/hdreg.h>
+#endif
 
 #include <f2fs_fs.h>
 
@@ -28,14 +34,8 @@
 #ifdef WITH_ANDROID
 #include <sparse/sparse.h>
 struct sparse_file *f2fs_sparse_file;
-
-struct buf_item {
-	void *buf;
-	size_t len;
-	struct buf_item *next;
-};
-
-struct buf_item *buf_list;
+static char **blocks;
+u_int64_t blocks_count;
 #endif
 
 static int __get_device_fd(__u64 *offset)
@@ -54,6 +54,15 @@
 	return -1;
 }
 
+#ifndef HAVE_LSEEK64
+typedef off_t	off64_t;
+
+static inline off64_t lseek64(int fd, __u64 offset, int set)
+{
+	return lseek(fd, offset, set);
+}
+#endif
+
 /*
  * IO interfaces
  */
@@ -68,12 +77,89 @@
 	return 0;
 }
 
+#ifdef WITH_ANDROID
+static int sparse_read_blk(__u64 block, int count, void *buf)
+{
+	int i;
+	char *out = buf;
+	__u64 cur_block;
+
+	for (i = 0; i < count; ++i) {
+		cur_block = block + i;
+		if (blocks[cur_block])
+			memcpy(out + (i * F2FS_BLKSIZE),
+					blocks[cur_block], F2FS_BLKSIZE);
+		else if (blocks)
+			memset(out + (i * F2FS_BLKSIZE), 0, F2FS_BLKSIZE);
+	}
+	return 0;
+}
+
+static int sparse_write_blk(__u64 block, int count, const void *buf)
+{
+	int i;
+	__u64 cur_block;
+	const char *in = buf;
+
+	for (i = 0; i < count; ++i) {
+		cur_block = block + i;
+		if (!blocks[cur_block]) {
+			blocks[cur_block] = calloc(1, F2FS_BLKSIZE);
+			if (!blocks[cur_block])
+				return -ENOMEM;
+		}
+		memcpy(blocks[cur_block], in + (i * F2FS_BLKSIZE),
+				F2FS_BLKSIZE);
+	}
+	return 0;
+}
+
+static int sparse_import_segment(void *UNUSED(priv), const void *data, int len,
+		unsigned int block, unsigned int nr_blocks)
+{
+	/* Ignore chunk headers, only write the data */
+	if (!nr_blocks || len % F2FS_BLKSIZE)
+		return 0;
+
+	return sparse_write_blk(block, nr_blocks, data);
+}
+
+static int sparse_merge_blocks(uint64_t start, uint64_t num)
+{
+	char *buf;
+	uint64_t i;
+
+	buf = calloc(num, F2FS_BLKSIZE);
+	if (!buf) {
+		fprintf(stderr, "failed to alloc %llu\n",
+			(unsigned long long)num * F2FS_BLKSIZE);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < num; i++) {
+		memcpy(buf + i * F2FS_BLKSIZE, blocks[start + i], F2FS_BLKSIZE);
+		free(blocks[start + i]);
+		blocks[start + i] = NULL;
+	}
+
+	/* free_sparse_blocks will release this buf. */
+	blocks[start] = buf;
+
+	return sparse_file_add_data(f2fs_sparse_file, blocks[start],
+					F2FS_BLKSIZE * num, start);
+}
+#else
+static int sparse_read_blk(__u64 block, int count, void *buf) { return 0; }
+static int sparse_write_blk(__u64 block, int count, const void *buf) { return 0; }
+#endif
+
 int dev_read(void *buf, __u64 offset, size_t len)
 {
 	int fd;
 
 	if (c.sparse_mode)
-		return 0;
+		return sparse_read_blk(offset / F2FS_BLKSIZE,
+					len / F2FS_BLKSIZE, buf);
 
 	fd = __get_device_fd(&offset);
 	if (fd < 0)
@@ -86,7 +172,11 @@
 	return 0;
 }
 
+#ifdef POSIX_FADV_WILLNEED
 int dev_readahead(__u64 offset, size_t len)
+#else
+int dev_readahead(__u64 offset, size_t UNUSED(len))
+#endif
 {
 	int fd = __get_device_fd(&offset);
 
@@ -99,38 +189,16 @@
 #endif
 }
 
-#ifdef WITH_ANDROID
-static int dev_write_sparse(void *buf, __u64 byte_offset, size_t byte_len)
-{
-	struct buf_item *bi = calloc(1, sizeof(struct buf_item));
-
-	if (bi == NULL) {
-		return -1;
-	}
-	bi->buf = malloc(byte_len);
-	if (bi->buf == NULL) {
-		free(bi);
-		return -1;
-	}
-
-	bi->len = byte_len;
-	memcpy(bi->buf, buf, byte_len);
-	bi->next = buf_list;
-	buf_list = bi;
-
-	sparse_file_add_data(f2fs_sparse_file, bi->buf, byte_len, byte_offset/F2FS_BLKSIZE);
-	return 0;
-}
-#else
-static int dev_write_sparse(void *buf, __u64 byte_offset, size_t byte_len) { return 0; }
-#endif
-
 int dev_write(void *buf, __u64 offset, size_t len)
 {
 	int fd;
 
+	if (c.dry_run)
+		return 0;
+
 	if (c.sparse_mode)
-		return dev_write_sparse(buf, offset, len);
+		return sparse_write_blk(offset / F2FS_BLKSIZE,
+					len / F2FS_BLKSIZE, buf);
 
 	fd = __get_device_fd(&offset);
 	if (fd < 0)
@@ -193,34 +261,116 @@
 	return dev_readahead(blk_addr << F2FS_BLKSIZE_BITS, F2FS_BLKSIZE);
 }
 
-void f2fs_finalize_device(void)
+int f2fs_fsync_device(void)
+{
+#ifndef ANDROID_WINDOWS_HOST
+	int i;
+
+	for (i = 0; i < c.ndevs; i++) {
+		if (fsync(c.devices[i].fd) < 0) {
+			MSG(0, "\tError: Could not conduct fsync!!!\n");
+			return -1;
+		}
+	}
+#endif
+	return 0;
+}
+
+int f2fs_init_sparse_file(void)
+{
+#ifdef WITH_ANDROID
+	if (c.func == MKFS) {
+		f2fs_sparse_file = sparse_file_new(F2FS_BLKSIZE, c.device_size);
+	} else {
+		f2fs_sparse_file = sparse_file_import(c.devices[0].fd,
+							true, false);
+		if (!f2fs_sparse_file)
+			return -1;
+
+		c.device_size = sparse_file_len(f2fs_sparse_file, 0, 0);
+		c.device_size &= (~((u_int64_t)(F2FS_BLKSIZE - 1)));
+	}
+
+	if (sparse_file_block_size(f2fs_sparse_file) != F2FS_BLKSIZE) {
+		MSG(0, "\tError: Corrupted sparse file\n");
+		return -1;
+	}
+	blocks_count = c.device_size / F2FS_BLKSIZE;
+	blocks = calloc(blocks_count, sizeof(char *));
+
+	return sparse_file_foreach_chunk(f2fs_sparse_file, true, false,
+				sparse_import_segment, NULL);
+#else
+	MSG(0, "\tError: Sparse mode is only supported for android\n");
+	return -1;
+#endif
+}
+
+int f2fs_finalize_device(void)
 {
 	int i;
+	int ret = 0;
 
 #ifdef WITH_ANDROID
 	if (c.sparse_mode) {
-		sparse_file_write(f2fs_sparse_file, c.devices[0].fd, /*gzip*/0, /*sparse*/1, /*crc*/0);
-		sparse_file_destroy(f2fs_sparse_file);
-		while (buf_list) {
-			struct buf_item *bi = buf_list;
-			buf_list = buf_list->next;
-			free(bi->buf);
-			free(bi);
+		int64_t chunk_start = (blocks[0] == NULL) ? -1 : 0;
+		uint64_t j;
+
+		if (c.func != MKFS) {
+			sparse_file_destroy(f2fs_sparse_file);
+			ret = ftruncate(c.devices[0].fd, 0);
+			ASSERT(!ret);
+			lseek(c.devices[0].fd, 0, SEEK_SET);
+			f2fs_sparse_file = sparse_file_new(F2FS_BLKSIZE,
+							c.device_size);
 		}
+
+		for (j = 0; j < blocks_count; ++j) {
+			if (!blocks[j] && chunk_start != -1) {
+				ret = sparse_merge_blocks(chunk_start,
+							j - chunk_start);
+				chunk_start = -1;
+			} else if (blocks[j] && chunk_start == -1) {
+				chunk_start = j;
+			}
+			ASSERT(!ret);
+		}
+		if (chunk_start != -1) {
+			ret = sparse_merge_blocks(chunk_start,
+						blocks_count - chunk_start);
+			ASSERT(!ret);
+		}
+
+		sparse_file_write(f2fs_sparse_file, c.devices[0].fd,
+				/*gzip*/0, /*sparse*/1, /*crc*/0);
+
+		sparse_file_destroy(f2fs_sparse_file);
+		for (j = 0; j < blocks_count; j++)
+			free(blocks[j]);
+		free(blocks);
+		blocks = NULL;
 		f2fs_sparse_file = NULL;
 	}
 #endif
-
 	/*
 	 * We should call fsync() to flush out all the dirty pages
 	 * in the block device page cache.
 	 */
 	for (i = 0; i < c.ndevs; i++) {
-		if (fsync(c.devices[i].fd) < 0)
+#ifndef ANDROID_WINDOWS_HOST
+		ret = fsync(c.devices[i].fd);
+		if (ret < 0) {
 			MSG(0, "\tError: Could not conduct fsync!!!\n");
-
-		if (close(c.devices[i].fd) < 0)
+			break;
+		}
+#endif
+		ret = close(c.devices[i].fd);
+		if (ret < 0) {
 			MSG(0, "\tError: Failed to close device file!!!\n");
+			break;
+		}
 	}
 	close(c.kd);
+
+	return ret;
 }
diff --git a/lib/libf2fs_zoned.c b/lib/libf2fs_zoned.c
index eebf030..6e32f32 100644
--- a/lib/libf2fs_zoned.c
+++ b/lib/libf2fs_zoned.c
@@ -15,7 +15,9 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/stat.h>
+#ifndef ANDROID_WINDOWS_HOST
 #include <sys/ioctl.h>
+#endif
 #include <libgen.h>
 
 #include <f2fs_fs.h>
diff --git a/man/mkfs.f2fs.8 b/man/mkfs.f2fs.8
index c2f9c86..442c0ea 100644
--- a/man/mkfs.f2fs.8
+++ b/man/mkfs.f2fs.8
@@ -53,6 +53,10 @@
 .I nodiscard/discard
 ]
 [
+.B \-w
+.I specific sector_size for target sectors
+]
+[
 .B \-z
 .I #-of-sections-per-zone
 ]
@@ -125,6 +129,10 @@
 If the value is equal to 1, discard policy is enabled, otherwise is disable.
 The default value is 1.
 .TP
+.BI \-w "sector-size"
+Specify the sector size in bytes along with given target sectors.
+Without it, the sectors will be calculated by device sector size.
+.TP
 .BI \-z " #-of-sections-per-zone"
 Specify the number of sections per zone. A zone consists of multiple sections.
 F2FS allocates segments for active logs with separated zones as much as possible.
diff --git a/mkfs/Makefile.am b/mkfs/Makefile.am
index 0ea8b49..bbb4917 100644
--- a/mkfs/Makefile.am
+++ b/mkfs/Makefile.am
@@ -3,7 +3,9 @@
 AM_CPPFLAGS = ${libuuid_CFLAGS} ${libblkid_CFLAGS} -I$(top_srcdir)/include
 AM_CFLAGS = -Wall -DWITH_BLKDISCARD
 sbin_PROGRAMS = mkfs.f2fs
-mkfs_f2fs_SOURCES = f2fs_format_main.c f2fs_format.c f2fs_format_utils.c f2fs_format_utils.h $(top_srcdir)/include/f2fs_fs.h
+noinst_HEADERS = f2fs_format_utils.h
+include_HEADERS = $(top_srcdir)/include/f2fs_fs.h
+mkfs_f2fs_SOURCES = f2fs_format_main.c f2fs_format.c f2fs_format_utils.c
 mkfs_f2fs_LDADD = ${libuuid_LIBS} ${libblkid_LIBS} $(top_builddir)/lib/libf2fs.la
 
 lib_LTLIBRARIES = libf2fs_format.la
diff --git a/mkfs/f2fs_format.c b/mkfs/f2fs_format.c
index ff1153a..09886b4 100644
--- a/mkfs/f2fs_format.c
+++ b/mkfs/f2fs_format.c
@@ -13,12 +13,15 @@
 #include <fcntl.h>
 #include <string.h>
 #include <unistd.h>
+#ifndef ANDROID_WINDOWS_HOST
 #include <sys/stat.h>
 #include <sys/mount.h>
+#endif
 #include <time.h>
 #include <uuid/uuid.h>
 
 #include "f2fs_fs.h"
+#include "quota.h"
 #include "f2fs_format_utils.h"
 
 extern struct f2fs_configuration c;
@@ -32,6 +35,8 @@
 #define last_zone(cur)		((cur - 1) * c.segs_per_zone)
 #define last_section(cur)	(cur + (c.secs_per_zone - 1) * c.segs_per_sec)
 
+static unsigned int quotatype_bits = 0;
+
 const char *media_ext_lists[] = {
 	"jpg",
 	"gif",
@@ -59,6 +64,7 @@
 	"jpeg",
 	"video",
 	"apk",	/* for android system */
+	"so",	/* for android system */
 	NULL
 };
 
@@ -120,14 +126,18 @@
 static void verify_cur_segs(void)
 {
 	int i, j;
+	int reorder = 0;
 
 	for (i = 0; i < NR_CURSEG_TYPE; i++) {
-		for (j = 0; j < NR_CURSEG_TYPE; j++)
-			if (c.cur_seg[i] == c.cur_seg[j])
+		for (j = i + 1; j < NR_CURSEG_TYPE; j++) {
+			if (c.cur_seg[i] == c.cur_seg[j]) {
+				reorder = 1;
 				break;
+			}
+		}
 	}
 
-	if (i == NR_CURSEG_TYPE && j == NR_CURSEG_TYPE)
+	if (!reorder)
 		return;
 
 	c.cur_seg[0] = 0;
@@ -149,6 +159,8 @@
 	u_int32_t sit_bitmap_size, max_sit_bitmap_size;
 	u_int32_t max_nat_bitmap_size, max_nat_segments;
 	u_int32_t total_zones;
+	u_int32_t next_ino;
+	enum quota_type qtype;
 	int i;
 
 	set_sb(magic, F2FS_SUPER_MAGIC);
@@ -244,7 +256,7 @@
 	set_sb(sit_blkaddr, get_sb(segment0_blkaddr) +
 			get_sb(segment_count_ckpt) * c.blks_per_seg);
 
-	blocks_for_sit = ALIGN(get_sb(segment_count), SIT_ENTRY_PER_BLOCK);
+	blocks_for_sit = SIZE_ALIGN(get_sb(segment_count), SIT_ENTRY_PER_BLOCK);
 
 	sit_segments = SEG_ALIGN(blocks_for_sit);
 
@@ -257,7 +269,7 @@
 			(get_sb(segment_count_ckpt) +
 			get_sb(segment_count_sit))) * c.blks_per_seg;
 
-	blocks_for_nat = ALIGN(total_valid_blks_available,
+	blocks_for_nat = SIZE_ALIGN(total_valid_blks_available,
 			NAT_ENTRY_PER_BLOCK);
 
 	set_sb(segment_count_nat, SEG_ALIGN(blocks_for_nat));
@@ -279,9 +291,7 @@
 	 * When sit is too large, we should expand cp area. It requires more
 	 * pages for cp.
 	 */
-	if (max_sit_bitmap_size >
-			(CHECKSUM_OFFSET -
-				sizeof(struct f2fs_checkpoint) + 1 - 64)) {
+	if (max_sit_bitmap_size > MAX_SIT_BITMAP_SIZE_IN_CKPT) {
 		max_nat_bitmap_size = CHECKSUM_OFFSET -
 				sizeof(struct f2fs_checkpoint) + 1;
 		set_sb(cp_payload, F2FS_BLK_ALIGN(max_sit_bitmap_size));
@@ -369,11 +379,30 @@
 
 	uuid_generate(sb->uuid);
 
+	/* precompute checksum seed for metadata */
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+		c.chksum_seed = f2fs_cal_crc32(~0, sb->uuid, sizeof(sb->uuid));
+
 	utf8_to_utf16(sb->volume_name, (const char *)c.vol_label,
 				MAX_VOLUME_NAME, strlen(c.vol_label));
 	set_sb(node_ino, 1);
 	set_sb(meta_ino, 2);
 	set_sb(root_ino, 3);
+	next_ino = 4;
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_QUOTA_INO)) {
+		quotatype_bits = QUOTA_USR_BIT | QUOTA_GRP_BIT;
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_PRJQUOTA))
+			quotatype_bits |= QUOTA_PRJ_BIT;
+	}
+
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (!((1 << qtype) & quotatype_bits))
+			continue;
+		sb->qf_ino[qtype] = cpu_to_le32(next_ino++);
+		MSG(0, "Info: add quota type = %u => %u\n",
+					qtype, next_ino - 1);
+	}
 
 	if (total_zones <= 6) {
 		MSG(1, "\tError: %d zones: Need more zones "
@@ -413,7 +442,7 @@
 		get_kernel_version(c.version);
 		MSG(0, "Info: format version with\n  \"%s\"\n", c.version);
 	} else {
-		memset(c.version, 0, VERSION_LEN);
+		get_kernel_uname_version(c.version);
 	}
 
 	memcpy(sb->version, c.version, VERSION_LEN);
@@ -505,6 +534,9 @@
 	char *cp_payload = NULL;
 	char *sum_compact, *sum_compact_p;
 	struct f2fs_summary *sum_entry;
+	enum quota_type qtype;
+	u_int32_t quota_inum, quota_dnum;
+	int off;
 	int ret = -1;
 
 	cp = calloc(F2FS_BLKSIZE, 1);
@@ -542,7 +574,8 @@
 	}
 
 	/* 1. cp page 1 of checkpoint pack 1 */
-	cp->checkpoint_ver = rand() | 0x1;
+	srand(time(NULL));
+	cp->checkpoint_ver = cpu_to_le64(rand() | 0x1);
 	set_cp(cur_node_segno[0], c.cur_seg[CURSEG_HOT_NODE]);
 	set_cp(cur_node_segno[1], c.cur_seg[CURSEG_WARM_NODE]);
 	set_cp(cur_node_segno[2], c.cur_seg[CURSEG_COLD_NODE]);
@@ -554,9 +587,16 @@
 		set_cp(cur_data_segno[i], 0xffffffff);
 	}
 
-	set_cp(cur_node_blkoff[0], 1);
-	set_cp(cur_data_blkoff[0], 1);
-	set_cp(valid_block_count, 2);
+	quota_inum = quota_dnum = 0;
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++)
+		if (sb->qf_ino[qtype]) {
+			quota_inum++;
+			quota_dnum += QUOTA_DATA(qtype);
+		}
+
+	set_cp(cur_node_blkoff[0], 1 + quota_inum);
+	set_cp(cur_data_blkoff[0], 1 + quota_dnum);
+	set_cp(valid_block_count, 2 + quota_inum + quota_dnum);
 	set_cp(rsvd_segment_count, c.reserved_segments);
 	set_cp(overprov_segment_count, (get_sb(segment_count_main) -
 			get_cp(rsvd_segment_count)) *
@@ -585,9 +625,9 @@
 
 	set_cp(ckpt_flags, flags);
 	set_cp(cp_pack_start_sum, 1 + get_sb(cp_payload));
-	set_cp(valid_node_count, 1);
-	set_cp(valid_inode_count, 1);
-	set_cp(next_free_nid, get_sb(root_ino) + 1);
+	set_cp(valid_node_count, 1 + quota_inum);
+	set_cp(valid_inode_count, 1 + quota_inum);
+	set_cp(next_free_nid, get_sb(root_ino) + 1 + quota_inum);
 	set_cp(sit_ver_bitmap_bytesize, ((get_sb(segment_count_sit) / 2) <<
 			get_sb(log_blocks_per_seg)) / 8);
 
@@ -645,7 +685,7 @@
 	SET_SUM_TYPE((&sum->footer), SUM_TYPE_DATA);
 
 	journal = &sum->journal;
-	journal->n_nats = cpu_to_le16(1);
+	journal->n_nats = cpu_to_le16(1 + quota_inum);
 	journal->nat_j.entries[0].nid = sb->root_ino;
 	journal->nat_j.entries[0].ne.version = 0;
 	journal->nat_j.entries[0].ne.ino = sb->root_ino;
@@ -653,6 +693,19 @@
 			get_sb(main_blkaddr) +
 			get_cp(cur_node_segno[0]) * c.blks_per_seg);
 
+	for (qtype = 0, i = 1; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		journal->nat_j.entries[i].nid = sb->qf_ino[qtype];
+		journal->nat_j.entries[i].ne.version = 0;
+		journal->nat_j.entries[i].ne.ino = sb->qf_ino[qtype];
+		journal->nat_j.entries[i].ne.block_addr = cpu_to_le32(
+				get_sb(main_blkaddr) +
+				get_cp(cur_node_segno[0]) *
+				c.blks_per_seg + i);
+		i++;
+	}
+
 	memcpy(sum_compact_p, &journal->n_nats, SUM_JOURNAL_SIZE);
 	sum_compact_p += SUM_JOURNAL_SIZE;
 
@@ -661,8 +714,11 @@
 	journal->n_sits = cpu_to_le16(6);
 	journal->sit_j.entries[0].segno = cp->cur_node_segno[0];
 	journal->sit_j.entries[0].se.vblocks =
-				cpu_to_le16((CURSEG_HOT_NODE << 10) | 1);
+				cpu_to_le16((CURSEG_HOT_NODE << 10) |
+						(1 + quota_inum));
 	f2fs_set_bit(0, (char *)journal->sit_j.entries[0].se.valid_map);
+	for (i = 1; i <= quota_inum; i++)
+		f2fs_set_bit(i, (char *)journal->sit_j.entries[0].se.valid_map);
 	journal->sit_j.entries[1].segno = cp->cur_node_segno[1];
 	journal->sit_j.entries[1].se.vblocks =
 				cpu_to_le16((CURSEG_WARM_NODE << 10));
@@ -673,8 +729,12 @@
 	/* data sit for root */
 	journal->sit_j.entries[3].segno = cp->cur_data_segno[0];
 	journal->sit_j.entries[3].se.vblocks =
-				cpu_to_le16((CURSEG_HOT_DATA << 10) | 1);
+				cpu_to_le16((CURSEG_HOT_DATA << 10) |
+						(1 + quota_dnum));
 	f2fs_set_bit(0, (char *)journal->sit_j.entries[3].se.valid_map);
+	for (i = 1; i <= quota_dnum; i++)
+		f2fs_set_bit(i, (char *)journal->sit_j.entries[3].se.valid_map);
+
 	journal->sit_j.entries[4].segno = cp->cur_data_segno[1];
 	journal->sit_j.entries[4].se.vblocks =
 				cpu_to_le16((CURSEG_WARM_DATA << 10));
@@ -689,6 +749,20 @@
 	sum_entry = (struct f2fs_summary *)sum_compact_p;
 	sum_entry->nid = sb->root_ino;
 	sum_entry->ofs_in_node = 0;
+
+	off = 1;
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		int j;
+
+		for (j = 0; j < QUOTA_DATA(qtype); j++) {
+			(sum_entry + off + j)->nid = sb->qf_ino[qtype];
+			(sum_entry + off + j)->ofs_in_node = j;
+		}
+		off += QUOTA_DATA(qtype);
+	}
+
 	/* warm data summary, nothing to do */
 	/* cold data summary, nothing to do */
 
@@ -706,6 +780,13 @@
 
 	sum->entries[0].nid = sb->root_ino;
 	sum->entries[0].ofs_in_node = 0;
+	for (qtype = i = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		sum->entries[1 + i].nid = sb->qf_ino[qtype];
+		sum->entries[1 + i].ofs_in_node = 0;
+		i++;
+	}
 
 	cp_seg_blk++;
 	DBG(1, "\tWriting Segment summary for HOT_NODE, at offset 0x%08"PRIx64"\n",
@@ -847,16 +928,24 @@
 static int discard_obsolete_dnode(struct f2fs_node *raw_node, u_int64_t offset)
 {
 	u_int64_t next_blkaddr = 0;
-	u_int64_t root_inode_pos = get_sb(main_blkaddr);
+	u64 end_blkaddr = (get_sb(segment_count_main) <<
+			get_sb(log_blocks_per_seg)) + get_sb(main_blkaddr);
+	u_int64_t start_inode_pos = get_sb(main_blkaddr);
+	u_int64_t last_inode_pos;
+	enum quota_type qtype;
+	u_int32_t quota_inum = 0;
+
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++)
+		if (sb->qf_ino[qtype]) quota_inum++;
 
 	/* only root inode was written before truncating dnodes */
-	root_inode_pos += c.cur_seg[CURSEG_HOT_NODE] * c.blks_per_seg;
+	last_inode_pos = start_inode_pos +
+		c.cur_seg[CURSEG_HOT_NODE] * c.blks_per_seg + quota_inum;
 
 	if (c.zoned_mode)
 		return 0;
 	do {
-		if (offset < get_sb(main_blkaddr) ||
-			offset >= get_sb(main_blkaddr) + get_sb(block_count))
+		if (offset < get_sb(main_blkaddr) || offset >= end_blkaddr)
 			break;
 
 		if (dev_read_block(raw_node, offset)) {
@@ -874,7 +963,7 @@
 		}
 		offset = next_blkaddr;
 		/* should avoid recursive chain due to stale data */
-		if (offset == root_inode_pos)
+		if (offset >= start_inode_pos || offset <= last_inode_pos)
 			break;
 	} while (1);
 
@@ -923,14 +1012,32 @@
 	raw_node->i.i_current_depth = cpu_to_le32(1);
 	raw_node->i.i_dir_level = DEF_DIR_LEVEL;
 
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
+		raw_node->i.i_inline = F2FS_EXTRA_ATTR;
+		raw_node->i.i_extra_isize =
+				cpu_to_le16(F2FS_TOTAL_EXTRA_ATTR_SIZE);
+	}
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_PRJQUOTA))
+		raw_node->i.i_projid = cpu_to_le32(F2FS_DEF_PROJID);
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CRTIME)) {
+		raw_node->i.i_crtime = cpu_to_le32(time(NULL));
+		raw_node->i.i_crtime_nsec = 0;
+	}
+
 	data_blk_nor = get_sb(main_blkaddr) +
 		c.cur_seg[CURSEG_HOT_DATA] * c.blks_per_seg;
-	raw_node->i.i_addr[0] = cpu_to_le32(data_blk_nor);
+	raw_node->i.i_addr[get_extra_isize(raw_node)] = cpu_to_le32(data_blk_nor);
 
 	raw_node->i.i_ext.fofs = 0;
 	raw_node->i.i_ext.blk_addr = 0;
 	raw_node->i.i_ext.len = 0;
 
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+		raw_node->i.i_inode_checksum =
+			cpu_to_le32(f2fs_inode_chksum(raw_node));
+
 	main_area_node_seg_blk_offset = get_sb(main_blkaddr);
 	main_area_node_seg_blk_offset += c.cur_seg[CURSEG_HOT_NODE] *
 					c.blks_per_seg;
@@ -956,6 +1063,170 @@
 		return -1;
 	}
 #endif
+	free(raw_node);
+	return 0;
+}
+
+static int f2fs_write_default_quota(int qtype, unsigned int blkaddr,
+						__le32 raw_id)
+{
+	char *filebuf = calloc(F2FS_BLKSIZE, 2);
+	int file_magics[] = INITQMAGICS;
+	struct v2_disk_dqheader ddqheader;
+	struct v2_disk_dqinfo ddqinfo;
+	struct v2r1_disk_dqblk dqblk;
+
+	if (filebuf == NULL) {
+		MSG(1, "\tError: Calloc Failed for filebuf!!!\n");
+		return -1;
+	}
+
+	/* Write basic quota header */
+	ddqheader.dqh_magic = cpu_to_le32(file_magics[qtype]);
+	/* only support QF_VFSV1 */
+	ddqheader.dqh_version = cpu_to_le32(1);
+
+	memcpy(filebuf, &ddqheader, sizeof(ddqheader));
+
+	/* Fill Initial quota file content */
+	ddqinfo.dqi_bgrace = cpu_to_le32(MAX_DQ_TIME);
+	ddqinfo.dqi_igrace = cpu_to_le32(MAX_IQ_TIME);
+	ddqinfo.dqi_flags = cpu_to_le32(0);
+	ddqinfo.dqi_blocks = cpu_to_le32(QT_TREEOFF + 5);
+	ddqinfo.dqi_free_blk = cpu_to_le32(0);
+	ddqinfo.dqi_free_entry = cpu_to_le32(5);
+
+	memcpy(filebuf + V2_DQINFOOFF, &ddqinfo, sizeof(ddqinfo));
+
+	filebuf[1024] = 2;
+	filebuf[2048] = 3;
+	filebuf[3072] = 4;
+	filebuf[4096] = 5;
+
+	filebuf[5120 + 8] = 1;
+
+	dqblk.dqb_id = raw_id;
+	dqblk.dqb_pad = cpu_to_le32(0);
+	dqblk.dqb_ihardlimit = cpu_to_le64(0);
+	dqblk.dqb_isoftlimit = cpu_to_le64(0);
+	dqblk.dqb_curinodes = cpu_to_le64(1);
+	dqblk.dqb_bhardlimit = cpu_to_le64(0);
+	dqblk.dqb_bsoftlimit = cpu_to_le64(0);
+	dqblk.dqb_curspace = cpu_to_le64(4096);
+	dqblk.dqb_btime = cpu_to_le64(0);
+	dqblk.dqb_itime = cpu_to_le64(0);
+
+	memcpy(filebuf + 5136, &dqblk, sizeof(struct v2r1_disk_dqblk));
+
+	/* Write two blocks */
+	if (dev_write_block(filebuf, blkaddr) ||
+	    dev_write_block(filebuf + F2FS_BLKSIZE, blkaddr + 1)) {
+		MSG(1, "\tError: While writing the quota_blk to disk!!!\n");
+		free(filebuf);
+		return -1;
+	}
+	DBG(1, "\tWriting quota data, at offset %08x, %08x\n",
+					blkaddr, blkaddr + 1);
+	free(filebuf);
+	return 0;
+}
+
+static int f2fs_write_qf_inode(int qtype)
+{
+	struct f2fs_node *raw_node = NULL;
+	u_int64_t data_blk_nor;
+	u_int64_t main_area_node_seg_blk_offset = 0;
+	__le32 raw_id;
+	int i;
+
+	raw_node = calloc(F2FS_BLKSIZE, 1);
+	if (raw_node == NULL) {
+		MSG(1, "\tError: Calloc Failed for raw_node!!!\n");
+		return -1;
+	}
+
+	raw_node->footer.nid = sb->qf_ino[qtype];
+	raw_node->footer.ino = sb->qf_ino[qtype];
+	raw_node->footer.cp_ver = cpu_to_le64(1);
+	raw_node->footer.next_blkaddr = cpu_to_le32(
+			get_sb(main_blkaddr) +
+			c.cur_seg[CURSEG_HOT_NODE] *
+			c.blks_per_seg + 1 + qtype + 1);
+
+	raw_node->i.i_mode = cpu_to_le16(0x8180);
+	raw_node->i.i_links = cpu_to_le32(1);
+	raw_node->i.i_uid = cpu_to_le32(getuid());
+	raw_node->i.i_gid = cpu_to_le32(getgid());
+
+	raw_node->i.i_size = cpu_to_le64(1024 * 6); /* Hard coded */
+	raw_node->i.i_blocks = cpu_to_le64(1 + QUOTA_DATA(qtype));
+
+	raw_node->i.i_atime = cpu_to_le32(time(NULL));
+	raw_node->i.i_atime_nsec = 0;
+	raw_node->i.i_ctime = cpu_to_le32(time(NULL));
+	raw_node->i.i_ctime_nsec = 0;
+	raw_node->i.i_mtime = cpu_to_le32(time(NULL));
+	raw_node->i.i_mtime_nsec = 0;
+	raw_node->i.i_generation = 0;
+	raw_node->i.i_xattr_nid = 0;
+	raw_node->i.i_flags = FS_IMMUTABLE_FL;
+	raw_node->i.i_current_depth = cpu_to_le32(1);
+	raw_node->i.i_dir_level = DEF_DIR_LEVEL;
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
+		raw_node->i.i_inline = F2FS_EXTRA_ATTR;
+		raw_node->i.i_extra_isize =
+				cpu_to_le16(F2FS_TOTAL_EXTRA_ATTR_SIZE);
+	}
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_PRJQUOTA))
+		raw_node->i.i_projid = cpu_to_le32(F2FS_DEF_PROJID);
+
+	data_blk_nor = get_sb(main_blkaddr) +
+		c.cur_seg[CURSEG_HOT_DATA] * c.blks_per_seg + 1;
+
+	for (i = 0; i < qtype; i++)
+		if (sb->qf_ino[i])
+			data_blk_nor += QUOTA_DATA(i);
+	if (qtype == 0)
+		raw_id = raw_node->i.i_uid;
+	else if (qtype == 1)
+		raw_id = raw_node->i.i_gid;
+	else if (qtype == 2)
+		raw_id = raw_node->i.i_projid;
+	else
+		ASSERT(0);
+
+	/* write two blocks */
+	if (f2fs_write_default_quota(qtype, data_blk_nor, raw_id)) {
+		free(raw_node);
+		return -1;
+	}
+
+	for (i = 0; i < QUOTA_DATA(qtype); i++)
+		raw_node->i.i_addr[get_extra_isize(raw_node) + i] =
+					cpu_to_le32(data_blk_nor + i);
+	raw_node->i.i_ext.fofs = 0;
+	raw_node->i.i_ext.blk_addr = 0;
+	raw_node->i.i_ext.len = 0;
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+		raw_node->i.i_inode_checksum =
+			cpu_to_le32(f2fs_inode_chksum(raw_node));
+
+	main_area_node_seg_blk_offset = get_sb(main_blkaddr);
+	main_area_node_seg_blk_offset += c.cur_seg[CURSEG_HOT_NODE] *
+					c.blks_per_seg + qtype + 1;
+
+	DBG(1, "\tWriting quota inode (hot node), %x %x %x at offset 0x%08"PRIu64"\n",
+			get_sb(main_blkaddr),
+			c.cur_seg[CURSEG_HOT_NODE],
+			c.blks_per_seg, main_area_node_seg_blk_offset);
+	if (dev_write_block(raw_node, main_area_node_seg_blk_offset)) {
+		MSG(1, "\tError: While writing the raw_node to disk!!!\n");
+		free(raw_node);
+		return -1;
+	}
 
 	free(raw_node);
 	return 0;
@@ -965,6 +1236,8 @@
 {
 	struct f2fs_nat_block *nat_blk = NULL;
 	u_int64_t nat_seg_blk_offset = 0;
+	enum quota_type qtype;
+	int i;
 
 	nat_blk = calloc(F2FS_BLKSIZE, 1);
 	if(nat_blk == NULL) {
@@ -972,6 +1245,18 @@
 		return -1;
 	}
 
+	/* update quota */
+	for (qtype = i = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		nat_blk->entries[sb->qf_ino[qtype]].block_addr =
+				cpu_to_le32(get_sb(main_blkaddr) +
+				c.cur_seg[CURSEG_HOT_NODE] *
+				c.blks_per_seg + i + 1);
+		nat_blk->entries[sb->qf_ino[qtype]].ino = sb->qf_ino[qtype];
+		i++;
+	}
+
 	/* update root */
 	nat_blk->entries[get_sb(root_ino)].block_addr = cpu_to_le32(
 		get_sb(main_blkaddr) +
@@ -1026,6 +1311,7 @@
 	/* bitmap for . and .. */
 	test_and_set_bit_le(0, dent_blk->dentry_bitmap);
 	test_and_set_bit_le(1, dent_blk->dentry_bitmap);
+
 	data_blk_offset = get_sb(main_blkaddr);
 	data_blk_offset += c.cur_seg[CURSEG_HOT_DATA] *
 				c.blks_per_seg;
@@ -1044,6 +1330,7 @@
 
 static int f2fs_create_root_dir(void)
 {
+	enum quota_type qtype;
 	int err = 0;
 
 	err = f2fs_write_root_inode();
@@ -1052,6 +1339,16 @@
 		goto exit;
 	}
 
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++)  {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		err = f2fs_write_qf_inode(qtype);
+		if (err < 0) {
+			MSG(1, "\tError: Failed to write quota inode!!!\n");
+			goto exit;
+		}
+	}
+
 	err = f2fs_update_nat_root();
 	if (err < 0) {
 		MSG(1, "\tError: Failed to update NAT for root!!!\n");
diff --git a/mkfs/f2fs_format_main.c b/mkfs/f2fs_format_main.c
index 5525d1c..36228d5 100644
--- a/mkfs/f2fs_format_main.c
+++ b/mkfs/f2fs_format_main.c
@@ -14,7 +14,9 @@
 #include <string.h>
 #include <unistd.h>
 #include <sys/stat.h>
+#ifndef ANDROID_WINDOWS_HOST
 #include <sys/mount.h>
+#endif
 #include <time.h>
 #include <uuid/uuid.h>
 #include <errno.h>
@@ -52,6 +54,7 @@
 	MSG(0, "  -s # of segments per section [default:1]\n");
 	MSG(0, "  -S sparse mode\n");
 	MSG(0, "  -t 0: nodiscard, 1: discard [default:1]\n");
+	MSG(0, "  -w wanted sector size\n");
 	MSG(0, "  -z # of sections per zone [default:1]\n");
 	MSG(0, "sectors: number of sectors. [default: determined by device size]\n");
 	exit(1);
@@ -80,6 +83,20 @@
 		features++;
 	if (!strcmp(features, "encrypt")) {
 		c.feature |= cpu_to_le32(F2FS_FEATURE_ENCRYPT);
+	} else if (!strcmp(features, "verity")) {
+		c.feature |= cpu_to_le32(F2FS_FEATURE_VERITY);
+	} else if (!strcmp(features, "extra_attr")) {
+		c.feature |= cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR);
+	} else if (!strcmp(features, "project_quota")) {
+		c.feature |= cpu_to_le32(F2FS_FEATURE_PRJQUOTA);
+	} else if (!strcmp(features, "inode_checksum")) {
+		c.feature |= cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM);
+	} else if (!strcmp(features, "flexible_inline_xattr")) {
+		c.feature |= cpu_to_le32(F2FS_FEATURE_FLEXIBLE_INLINE_XATTR);
+	} else if (!strcmp(features, "quota")) {
+		c.feature |= cpu_to_le32(F2FS_FEATURE_QUOTA_INO);
+	} else if (!strcmp(features, "inode_crtime")) {
+		c.feature |= cpu_to_le32(F2FS_FEATURE_INODE_CRTIME);
 	} else {
 		MSG(0, "Error: Wrong features\n");
 		mkfs_usage();
@@ -88,7 +105,7 @@
 
 static void f2fs_parse_options(int argc, char *argv[])
 {
-	static const char *option_string = "qa:c:d:e:l:mo:O:s:S:z:t:f";
+	static const char *option_string = "qa:c:d:e:l:mo:O:s:S:z:t:fw:";
 	int32_t option=0;
 
 	while ((option = getopt(argc,argv,option_string)) != EOF) {
@@ -152,6 +169,9 @@
 		case 'f':
 			force_overwrite = 1;
 			break;
+		case 'w':
+			c.wanted_sector_size = atoi(optarg);
+			break;
 		default:
 			MSG(0, "\tError: Unknown option %c\n",option);
 			mkfs_usage();
@@ -159,6 +179,29 @@
 		}
 	}
 
+	if (!(c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR))) {
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_PRJQUOTA)) {
+			MSG(0, "\tInfo: project quota feature should always been"
+				"enabled with extra attr feature\n");
+			exit(1);
+		}
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM)) {
+			MSG(0, "\tInfo: inode checksum feature should always been"
+				"enabled with extra attr feature\n");
+			exit(1);
+		}
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_FLEXIBLE_INLINE_XATTR)) {
+			MSG(0, "\tInfo: flexible inline xattr feature should always been"
+				"enabled with extra attr feature\n");
+			exit(1);
+		}
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CRTIME)) {
+			MSG(0, "\tInfo: inode crtime feature should always been"
+				"enabled with extra attr feature\n");
+			exit(1);
+		}
+	}
+
 	if (optind >= argc) {
 		MSG(0, "\tError: Device not specified\n");
 		mkfs_usage();
@@ -263,6 +306,8 @@
 
 	f2fs_show_info();
 
+	c.func = MKFS;
+
 	if (!force_overwrite && f2fs_check_overwrite()) {
 		MSG(0, "\tUse the -f option to force overwrite.\n");
 		return -1;
@@ -292,20 +337,15 @@
 	}
 
 	if (c.sparse_mode) {
-#ifndef WITH_ANDROID
-		MSG(0, "\tError: Sparse mode is only supported for android\n");
-		return -1;
-#else
-		if (f2fs_sparse_file)
-			sparse_file_destroy(f2fs_sparse_file);
-		f2fs_sparse_file = sparse_file_new(F2FS_BLKSIZE, c.device_size);
-#endif
+		if (f2fs_init_sparse_file())
+			return -1;
 	}
 
 	if (f2fs_format_device() < 0)
 		return -1;
 
-	f2fs_finalize_device();
+	if (f2fs_finalize_device() < 0)
+		return -1;
 
 	MSG(0, "Info: format successful\n");
 
diff --git a/mkfs/f2fs_format_utils.c b/mkfs/f2fs_format_utils.c
index 558684d..bf9ffbd 100644
--- a/mkfs/f2fs_format_utils.c
+++ b/mkfs/f2fs_format_utils.c
@@ -6,20 +6,27 @@
  *
  * Dual licensed under the GPL or LGPL version 2 licenses.
  */
+#ifndef _LARGEFILE_SOURCE
 #define _LARGEFILE_SOURCE
+#endif
+#ifndef _LARGEFILE64_SOURCE
 #define _LARGEFILE64_SOURCE
+#endif
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE
 #endif
 
+#include <f2fs_fs.h>
+
 #include <stdio.h>
 #include <unistd.h>
+#include <stdlib.h>
+#ifndef ANDROID_WINDOWS_HOST
 #include <sys/ioctl.h>
+#endif
 #include <sys/stat.h>
 #include <fcntl.h>
 
-#include "f2fs_fs.h"
-
 #ifdef HAVE_LINUX_FS_H
 #include <linux/fs.h>
 #endif
@@ -36,14 +43,17 @@
 
 static int trim_device(int i)
 {
+#ifndef ANDROID_WINDOWS_HOST
 	unsigned long long range[2];
-	struct stat stat_buf;
+	struct stat *stat_buf;
 	struct device_info *dev = c.devices + i;
 	u_int64_t bytes = dev->total_sectors * dev->sector_size;
 	int fd = dev->fd;
 
-	if (fstat(fd, &stat_buf) < 0 ) {
+	stat_buf = malloc(sizeof(struct stat));
+	if (fstat(fd, stat_buf) < 0 ) {
 		MSG(1, "\tError: Failed to get the device stat!!!\n");
+		free(stat_buf);
 		return -1;
 	}
 
@@ -52,23 +62,27 @@
 
 #if defined(WITH_BLKDISCARD) && defined(BLKDISCARD)
 	MSG(0, "Info: [%s] Discarding device\n", dev->path);
-	if (S_ISREG(stat_buf.st_mode)) {
+	if (S_ISREG(stat_buf->st_mode)) {
 #if defined(HAVE_FALLOCATE) && defined(FALLOC_FL_PUNCH_HOLE)
 		if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
 				range[0], range[1]) < 0) {
 			MSG(0, "Info: fallocate(PUNCH_HOLE|KEEP_SIZE) is failed\n");
 		}
 #endif
+		free(stat_buf);
 		return 0;
-	} else if (S_ISBLK(stat_buf.st_mode)) {
-		if (dev->zoned_model != F2FS_ZONED_NONE)
+	} else if (S_ISBLK(stat_buf->st_mode)) {
+		if (dev->zoned_model != F2FS_ZONED_NONE) {
+			free(stat_buf);
 			return f2fs_reset_zones(i);
+		}
 #ifdef BLKSECDISCARD
 		if (ioctl(fd, BLKSECDISCARD, &range) < 0) {
 			MSG(0, "Info: This device doesn't support BLKSECDISCARD\n");
 		} else {
 			MSG(0, "Info: Secure Discarded %lu MB\n",
-						stat_buf.st_size >> 20);
+					(unsigned long)stat_buf->st_size >> 20);
+			free(stat_buf);
 			return 0;
 		}
 #endif
@@ -77,8 +91,12 @@
 		} else {
 			MSG(0, "Info: Discarded %llu MB\n", range[1] >> 20);
 		}
-	} else
+	} else {
+		free(stat_buf);
 		return -1;
+	}
+#endif
+	free(stat_buf);
 #endif
 	return 0;
 }
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 5a9303f..25a8c6f 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -2,10 +2,16 @@
 
 AM_CPPFLAGS = ${libuuid_CFLAGS} -I$(top_srcdir)/include
 AM_CFLAGS = -Wall
-sbin_PROGRAMS = f2fstat fibmap.f2fs parse.f2fs f2fscrypt
+sbin_PROGRAMS = f2fstat fibmap.f2fs parse.f2fs
 f2fstat_SOURCES = f2fstat.c
 fibmap_f2fs_SOURCES = fibmap.c
 parse_f2fs_SOURCES = f2fs_io_parse.c
+
+if LINUX
+sbin_PROGRAMS += f2fscrypt
 f2fscrypt_SOURCES = f2fscrypt.c sha512.c
 f2fscrypt_LDFLAGS = -luuid
 dist_man_MANS = f2fscrypt.8
+endif
+
+SUBDIRS = sg_write_buffer
diff --git a/tools/check_f2fs.c b/tools/check_f2fs.c
new file mode 100644
index 0000000..93b9567
--- /dev/null
+++ b/tools/check_f2fs.c
@@ -0,0 +1,158 @@
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define F2FS_IOCTL_MAGIC		0xf5
+#define F2FS_IOC_START_ATOMIC_WRITE     _IO(F2FS_IOCTL_MAGIC, 1)
+#define F2FS_IOC_COMMIT_ATOMIC_WRITE    _IO(F2FS_IOCTL_MAGIC, 2)
+#define F2FS_IOC_START_VOLATILE_WRITE   _IO(F2FS_IOCTL_MAGIC, 3)
+#define F2FS_IOC_RELEASE_VOLATILE_WRITE _IO(F2FS_IOCTL_MAGIC, 4)
+#define F2FS_IOC_ABORT_VOLATILE_WRITE   _IO(F2FS_IOCTL_MAGIC, 5)
+#define F2FS_IOC_GARBAGE_COLLECT        _IO(F2FS_IOCTL_MAGIC, 6)
+
+#define DB1_PATH "/data/database_file1"
+#define DB2_PATH "/sdcard/database_file2"
+#define FILE_PATH "/data/testfile"
+
+#define BLOCK 4096
+#define BLOCKS (2 * BLOCK)
+
+int buf[BLOCKS];
+char cmd[BLOCK];
+
+static int run(char *cmd)
+{
+	int status;
+
+	fflush(stdout);
+
+	switch (fork()) {
+	case 0:
+		/* redirect stderr to stdout */
+		dup2(1, 2);
+		execl("/system/bin/sh", "sh", "-c", cmd, (char *) 0);
+	default:
+		wait(&status);
+	}
+	return 0;
+}
+
+static int test_atomic_write(char *path)
+{
+	int db, ret, written;
+
+	printf("\tOpen  %s... \n", path);
+	db = open(path, O_RDWR|O_CREAT, 0666);
+	if (db < 0) {
+		printf("open failed errno:%d\n", errno);
+		return -1;
+	}
+	printf("\tStart ... \n");
+	ret = ioctl(db, F2FS_IOC_START_ATOMIC_WRITE);
+	if (ret) {
+		printf("ioctl failed errno:%d\n", errno);
+		return -1;
+	}
+	printf("\tWrite to the %dkB ... \n", BLOCKS / 1024);
+	written = write(db, buf, BLOCKS);
+	if (written != BLOCKS) {
+		printf("write fail written:%d, errno:%d\n", written, errno);
+		return -1;
+	}
+	printf("\tCheck : Atomic in-memory count: 2\n");
+	run("cat /sys/kernel/debug/f2fs/status | grep atomic");
+
+	printf("\tCommit  ... \n");
+	ret = ioctl(db, F2FS_IOC_COMMIT_ATOMIC_WRITE);
+	if (ret) {
+		printf("ioctl failed errno:%d\n", errno);
+		return -1;
+	}
+	return 0;
+}
+
+static int test_bad_write_call(char *path)
+{
+	int fd, written;
+	struct stat sb;
+	int large_size = 1024 * 1024 * 100;	/* 100 MB */
+
+	printf("\tOpen  %s... \n", path);
+	fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0666);
+	if (fd < 0) {
+		printf("open failed errno:%d\n", errno);
+		return -1;
+	}
+
+	/* 8KB-sized buffer, but submit 100 MB size */
+	printf("\tWrite to the %dkB ... \n", BLOCKS / 1024);
+	written = write(fd, buf, large_size);
+	if (written != BLOCKS)
+		printf("Ok: write fail written:%d, errno:%d\n", written, errno);
+	close(fd);
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		printf("open failed errno:%d\n", errno);
+		return -1;
+	}
+
+	if (stat(path, &sb) == -1) {
+		printf("stat failed errno:%d\n", errno);
+		return -1;
+	}
+
+	if ((long long)sb.st_size / 512 != (long long)sb.st_blocks) {
+		printf("FAIL: Mismatch i_size and i_blocks: %lld %lld\n",
+			(long long)sb.st_size, (long long)sb.st_blocks);
+		printf("FAIL: missing patch "
+			"\"f2fs: do not preallocate blocks which has wrong buffer\"\n");
+	}
+	close(fd);
+	unlink(path);
+	return 0;
+}
+
+int main(void)
+{
+	memset(buf, 0xff, BLOCKS);
+
+	printf("# Test 0: Check F2FS support\n");
+	run("cat /proc/filesystems");
+
+	printf("# Test 1: Check F2FS status on /userdata\n");
+	printf("\t= FS type /userdata\n");
+	run("mount | grep data");
+
+	printf("\n\t= F2FS features\n");
+	run("ls -1 /sys/fs/f2fs/features/");
+	run("find /sys/fs/f2fs -type f -name \"features\" -print -exec cat {} \\;");
+	run("find /sys/fs/f2fs -type f -name \"ipu_policy\" -print -exec cat {} \\;");
+	run("find /sys/fs/f2fs -type f -name \"discard_granularity\" -print -exec cat {} \\;");
+	run("cat /sys/kernel/debug/f2fs/status");
+
+	printf("\n\n# Test 2: Atomic_write on /userdata\n");
+	if (test_atomic_write(DB1_PATH))
+		return -1;
+
+	printf("# Test 3: Atomic_write on /sdcard\n");
+	if (test_atomic_write(DB2_PATH))
+		return -1;
+
+	printf("# Test 4: Bad write(2) call\n");
+	if (test_bad_write_call(FILE_PATH))
+		return -1;
+	return 0;
+}
diff --git a/tools/f2fscrypt.c b/tools/f2fscrypt.c
index 48ea5f6..81ef830 100644
--- a/tools/f2fscrypt.c
+++ b/tools/f2fscrypt.c
@@ -30,7 +30,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#ifdef HAVE_MNTENT_H
 #include <mntent.h>
+#endif
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -38,7 +40,9 @@
 #include <termios.h>
 #include <unistd.h>
 #include <signal.h>
+#ifdef __KERNEL__
 #include <linux/fs.h>
+#endif
 #include <uuid/uuid.h>
 
 #if !defined(HAVE_ADD_KEY) || !defined(HAVE_KEYCTL)
@@ -47,6 +51,7 @@
 #ifdef HAVE_SYS_KEY_H
 #include <sys/key.h>
 #endif
+#include <f2fs_fs.h>
 
 #define F2FS_MAX_KEY_SIZE		64
 #define F2FS_MAX_PASSPHRASE_SIZE	1024
@@ -121,7 +126,7 @@
 extern void f2fs_sha512(const unsigned char *in, unsigned long in_size,
 						unsigned char *out);
 
-#ifndef HAVE_KEYCTL
+#if !defined(HAVE_KEYCTL)
 static long keyctl(int cmd, ...)
 {
 	va_list va;
@@ -137,7 +142,7 @@
 }
 #endif
 
-#ifndef HAVE_ADD_KEY
+#if !defined(HAVE_ADD_KEY)
 static key_serial_t add_key(const char *type, const char *description,
 			    const void *payload, size_t plen,
 			    key_serial_t keyring)
diff --git a/tools/fibmap.c b/tools/fibmap.c
index 6b092f5..d17144a 100644
--- a/tools/fibmap.c
+++ b/tools/fibmap.c
@@ -1,4 +1,20 @@
+#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__)
+#define _XOPEN_SOURCE 600
+#define _DARWIN_C_SOURCE
+#define _FILE_OFFSET_BITS 64
+#ifndef _LARGEFILE_SOURCE
+#define _LARGEFILE_SOURCE
+#endif
+#ifndef _LARGEFILE64_SOURCE
 #define _LARGEFILE64_SOURCE
+#endif
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#endif
+#ifndef O_LARGEFILE
+#define O_LARGEFILE 0
+#endif
 #include <unistd.h>
 #include <string.h>
 #include <stdlib.h>
@@ -8,12 +24,25 @@
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
+#ifdef HAVE_SYS_SYSMACROS_H
 #include <sys/sysmacros.h>
+#endif
 #include <libgen.h>
+#ifdef HAVE_LINUX_HDREG_H
 #include <linux/hdreg.h>
+#endif
+#ifdef HAVE_LINUX_TYPES_H
 #include <linux/types.h>
+#endif
+#ifdef __KERNEL__
 #include <linux/fs.h>
+#endif
 #include <inttypes.h>
+#include <f2fs_fs.h>
+
+#ifndef FIBMAP
+#define FIBMAP          _IO(0x00, 1)    /* bmap access */
+#endif
 
 struct file_ext {
 	__u32 f_pos;
@@ -31,28 +60,42 @@
 					ext->end_blk, ext->blk_count);
 }
 
+#if defined(HAVE_FSTAT64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
 void print_stat(struct stat64 *st)
+#else
+void print_stat(struct stat *st)
+#endif
 {
 	printf("--------------------------------------------\n");
 	printf("dev       [%d:%d]\n", major(st->st_dev), minor(st->st_dev));
 	printf("ino       [0x%8"PRIx64" : %"PRIu64"]\n",
 						st->st_ino, st->st_ino);
 	printf("mode      [0x%8x : %d]\n", st->st_mode, st->st_mode);
-	printf("nlink     [0x%8lx : %ld]\n", st->st_nlink, st->st_nlink);
+	printf("nlink     [0x%8lx : %ld]\n",
+					(unsigned long)st->st_nlink,
+					(long)st->st_nlink);
 	printf("uid       [0x%8x : %d]\n", st->st_uid, st->st_uid);
 	printf("gid       [0x%8x : %d]\n", st->st_gid, st->st_gid);
 	printf("size      [0x%8"PRIx64" : %"PRIu64"]\n",
-						st->st_size, st->st_size);
-	printf("blksize   [0x%8lx : %ld]\n", st->st_blksize, st->st_blksize);
+					(u64)st->st_size, (u64)st->st_size);
+	printf("blksize   [0x%8lx : %ld]\n",
+					(unsigned long)st->st_blksize,
+					(long)st->st_blksize);
 	printf("blocks    [0x%8"PRIx64" : %"PRIu64"]\n",
-					st->st_blocks, st->st_blocks);
+					(u64)st->st_blocks, (u64)st->st_blocks);
 	printf("--------------------------------------------\n\n");
 }
 
-void stat_bdev(struct stat64 *st, unsigned int *start_lba)
+#if defined(HAVE_FSTAT64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
+static void stat_bdev(struct stat64 *st, unsigned int *start_lba)
+#else
+static void stat_bdev(struct stat *st, unsigned int *start_lba)
+#endif
 {
 	struct stat bdev_stat;
+#ifdef HDIO_GETGIO
 	struct hd_geometry geom;
+#endif
 	char devname[32] = { 0, };
 	char linkname[32] = { 0, };
 	int fd;
@@ -67,10 +110,14 @@
 		goto out;
 
 	if (S_ISBLK(bdev_stat.st_mode)) {
+#ifdef HDIO_GETGIO
 		if (ioctl(fd, HDIO_GETGEO, &geom) < 0)
 			*start_lba = 0;
 		else
 			*start_lba = geom.start;
+#else
+		*start_lba = 0;
+#endif
 	}
 
 	if (readlink(devname, linkname, sizeof(linkname)) < 0)
@@ -90,7 +137,11 @@
 	int fd;
 	int ret = 0;
 	char *filename;
+#if defined(HAVE_FSTAT64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
 	struct stat64 st;
+#else
+	struct stat st;
+#endif
 	int total_blks;
 	unsigned int i;
 	struct file_ext ext;
@@ -112,7 +163,11 @@
 
 	fsync(fd);
 
+#if defined(HAVE_FSTAT64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
 	if (fstat64(fd, &st) < 0) {
+#else
+	if (fstat(fd, &st) < 0) {
+#endif
 		ret = errno;
 		perror(filename);
 		goto out;
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;
+}