add forwarded sense descriptor; new sg_decode_sense utility

git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@365 6180dd3e-e324-4e3e-922d-17de1ae2f315
diff --git a/ChangeLog b/ChangeLog
index dc8ef18..e7360e1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,8 +2,12 @@
 some description at the top of its ".c" file. All utilities in the main
 directory have their own "man" pages. There is also a sg3_utils man page.
 
-Changelog for sg3_utils-1.31 [20101112] [svn: r364]
-  - xxxxxxxxxxxxx
+Changelog for sg3_utils-1.31 [20101205] [svn: r365]
+  - sg_decode_sense: new utility to decode sense data
+  - sync asc/ascq and version descriptors with spc4r28
+  - sg_lib: implement forwarded sense data descriptor
+  - sg_lib, sg_turs, sg_format: more precision for progress
+    indication (two places after decimal point)
 
 Changelog for sg3_utils-1.30 [20101111] [svn: r363]
   - sg_referrals: new utility for REPORT REFERRALS
@@ -21,7 +25,7 @@
   - sg_vpd: rdac vendor page [0xc8] parse corrections
     - extended inquiry vpd page add extended self test
       completion minutes field
-  - sg_ses: expand --date (in) buffer to 2048 bytes
+  - sg_ses: expand --data (in) buffer to 2048 bytes
   - sg_opcodes: add extended parameter data for TMFs (spc4r26)
   - sg_dd: clean count calculation, document nocache flag
     - treat bsg devices as implicit sg_io
diff --git a/README b/README
index 925dd47..0a1d6ea 100644
--- a/README
+++ b/README
@@ -194,12 +194,12 @@
 =========
 Here is list in alphabetical order of utilities found in the 'src'
 subdirectory of the sg3_utils package:
-  - sginfo, sgm_dd, sgp_dd, sg_dd, sg_emc_trespass, sg_get_config,
-    sg_get_lba_status, sg_format, sg_ident, sg_inq, sg_logs, sg_luns, sg_map,
-    sg_map26, sg_modes, sg_opcodes, sg_persist, sg_prevent, sg_raw, sg_rbuf,
-    sg_rdac, sg_read, sg_readcap, sg_read_block_limits, sg_read_buffer,
-    sg_read_long, sg_reassign, sg_referrals, sg_request, sg_reset, sg_rmsn,
-    sg_rtpg, sg_safte, sg_sat_identify, sg_sat_phy_event,
+  - sginfo, sgm_dd, sgp_dd, sg_dd, sg_decode_sense, sg_emc_trespass,
+  - sg_format, sg_get_config, sg_get_lba_status, sg_ident, sg_inq, sg_logs,
+  - sg_luns, sg_map, sg_map26, sg_modes, sg_opcodes, sg_persist, sg_prevent,
+  - sg_raw, sg_rbuf, sg_rdac, sg_read, sg_readcap, sg_read_block_limits,
+  - sg_read_buffer, sg_read_long, sg_reassign, sg_referrals, sg_request,
+  - sg_reset, sg_rmsn, sg_rtpg, sg_safte, sg_sat_identify, sg_sat_phy_event,
     sg_sat_set_features, sg_scan, sg_senddiag, sg_ses, sg_start, sg_stpg,
     sg_sync, sg_test_rwbuff, sg_turs, sg_unmap, sg_verify, sg_vpd,
     sg_write_buffer, sg_write_long, sg_write_same, sg_wr_mode
@@ -318,10 +318,10 @@
 or using '-O' as the first command line option.
 
 The more recent utilities that use "getopt_long" only are:
-  - sg_format sg_get_config sg_get_lba_status sg_ident sg_luns sg_map26
-    sg_persist sg_prevent sg_raw sg_read_block_limits sg_read_buffer
-    sg_read_long sg_reassign sg_referrals sg_requests sg_rmsn sg_rtpg
-    sg_safte sg_sat_identify sg_sat_phy_event sg_sat_set_features
+  - sg_decode_sense, sg_format sg_get_config sg_get_lba_status sg_ident
+    sg_luns sg_map26 sg_persist sg_prevent sg_raw sg_read_block_limits
+    sg_read_buffer sg_read_long sg_reassign sg_referrals sg_requests sg_rmsn
+    sg_rtpg sg_safte sg_sat_identify sg_sat_phy_event sg_sat_set_features
     sg_scan(w) sg_ses sg_stpg sg_sync sg_test_rwbuf sg_unmap sg_verify
     sg_vpd sg_write_buffer sg_write_long sg_write_same sg_wr_mode
 
@@ -345,4 +345,4 @@
 
 
 Doug Gilbert
-31st October 2010
+5th December 2010
diff --git a/README.freebsd b/README.freebsd
index 1db80d6..401a607 100644
--- a/README.freebsd
+++ b/README.freebsd
@@ -13,6 +13,7 @@
 Supported Utilities
 ===================
 Here is a list of utilities that have been ported:
+    sg_decode_sense
     sg_format
     sg_get_config
     sg_get_lba_status
@@ -104,4 +105,4 @@
 
 
 Doug Gilbert
-13th September 2010
+5th December 2010
diff --git a/README.solaris b/README.solaris
index 1288802..7fa6e30 100644
--- a/README.solaris
+++ b/README.solaris
@@ -11,6 +11,7 @@
 Supported Utilities
 ===================
 Here is a list of utilities that have been ported:
+    sg_decode_sense
     sg_format
     sg_get_config
     sg_get_lba_status
@@ -134,4 +135,4 @@
 
 
 Doug Gilbert
-13th September 2010
+5th December 2010
diff --git a/README.tru64 b/README.tru64
index f35ee47..e3b474d 100644
--- a/README.tru64
+++ b/README.tru64
@@ -8,6 +8,7 @@
 Supported Utilities
 ===================
 Here is a list of utilities that have been ported:
+    sg_decode_sense
     sg_format
     sg_get_config
     sg_get_lba_status
@@ -91,4 +92,4 @@
 
 
 Doug Gilbert
-13th September 2010
+5th December 2010
diff --git a/README.win32 b/README.win32
index 8ea76e5..b7f5404 100644
--- a/README.win32
+++ b/README.win32
@@ -23,6 +23,7 @@
 Supported Utilities
 ===================
 Here is a list of utilities that have been ported:
+    sg_decode_sense
     sg_format
     sg_get_config
     sg_get_lba_status
@@ -196,4 +197,4 @@
 
 
 Doug Gilbert
-13th September 2010
+5th December 2010
diff --git a/debian/compat b/debian/compat
index 7f8f011..45a4fb7 100644
--- a/debian/compat
+++ b/debian/compat
@@ -1 +1 @@
-7
+8
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 2c4f75e..fa6ecec 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -8,7 +8,7 @@
 # sg_scan is shared by Linux and Win32
 
 man_MANS = \
-	sg3_utils.8 \
+	sg3_utils.8 sg_decode_sense.8 \
 	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 	sg_dd.8 sg_emc_trespass.8 sg_format.8 sg_get_config.8 \
 	sg_get_lba_status.8 sg_ident.8 sginfo.8 sg_inq.8 sg_logs.8 \
@@ -34,7 +34,7 @@
 if OS_WIN32_MINGW
 
 man_MANS = \
-	sg3_utils.8 \
+	sg3_utils.8 sg_decode_sense.8 \
 	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 	sg_inq.8 sg_logs.8 sg_luns.8 sg_modes.8 sg_opcodes.8 sg_persist.8 \
@@ -58,7 +58,7 @@
 if OS_WIN32_CYGWIN
 
 man_MANS = \
-	sg3_utils.8 \
+	sg3_utils.8 sg_decode_sense.8 \
 	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 	sg_inq.8 sg_logs.8 sg_luns.8 sg_modes.8 sg_opcodes.8 \
@@ -82,7 +82,7 @@
 if OS_FREEBSD
 
 man_MANS = \
-	sg3_utils.8 \
+	sg3_utils.8 sg_decode_sense.8 \
 	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 	sg_inq.8 sg_logs.8 sg_luns.8 \
@@ -100,7 +100,7 @@
 if OS_SOLARIS
 
 man_MANS = \
-	sg3_utils.8 \
+	sg3_utils.8 sg_decode_sense.8 \
 	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 	sg_inq.8 sg_logs.8 sg_luns.8 \
@@ -118,7 +118,7 @@
 if OS_OSF
 
 man_MANS = \
-	sg3_utils.8 \
+	sg3_utils.8 sg_decode_sense.8 \
 	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 	sg_inq.8 sg_logs.8 sg_luns.8 \
diff --git a/doc/Makefile.in b/doc/Makefile.in
index 53b5c90..e0c4763 100644
--- a/doc/Makefile.in
+++ b/doc/Makefile.in
@@ -186,7 +186,7 @@
 top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 @OS_FREEBSD_TRUE@man_MANS = \
-@OS_FREEBSD_TRUE@	sg3_utils.8 \
+@OS_FREEBSD_TRUE@	sg3_utils.8 sg_decode_sense.8 \
 @OS_FREEBSD_TRUE@	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 @OS_FREEBSD_TRUE@	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 @OS_FREEBSD_TRUE@	sg_inq.8 sg_logs.8 sg_luns.8 \
@@ -205,7 +205,7 @@
 #
 # sg_scan is shared by Linux and Win32
 @OS_LINUX_TRUE@man_MANS = \
-@OS_LINUX_TRUE@	sg3_utils.8 \
+@OS_LINUX_TRUE@	sg3_utils.8 sg_decode_sense.8 \
 @OS_LINUX_TRUE@	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 @OS_LINUX_TRUE@	sg_dd.8 sg_emc_trespass.8 sg_format.8 sg_get_config.8 \
 @OS_LINUX_TRUE@	sg_get_lba_status.8 sg_ident.8 sginfo.8 sg_inq.8 sg_logs.8 \
@@ -220,7 +220,7 @@
 @OS_LINUX_TRUE@	sg_write_buffer.8 sg_write_long.8 sg_write_same.8 sg_wr_mode.8
 
 @OS_OSF_TRUE@man_MANS = \
-@OS_OSF_TRUE@	sg3_utils.8 \
+@OS_OSF_TRUE@	sg3_utils.8 sg_decode_sense.8 \
 @OS_OSF_TRUE@	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 @OS_OSF_TRUE@	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 @OS_OSF_TRUE@	sg_inq.8 sg_logs.8 sg_luns.8 \
@@ -233,7 +233,7 @@
 @OS_OSF_TRUE@	sg_write_buffer.8 sg_write_long.8 sg_write_same.8 sg_wr_mode.8
 
 @OS_SOLARIS_TRUE@man_MANS = \
-@OS_SOLARIS_TRUE@	sg3_utils.8 \
+@OS_SOLARIS_TRUE@	sg3_utils.8 sg_decode_sense.8 \
 @OS_SOLARIS_TRUE@	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 @OS_SOLARIS_TRUE@	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 @OS_SOLARIS_TRUE@	sg_inq.8 sg_logs.8 sg_luns.8 \
@@ -246,7 +246,7 @@
 @OS_SOLARIS_TRUE@	sg_write_buffer.8 sg_write_long.8 sg_write_same.8 sg_wr_mode.8
 
 @OS_WIN32_CYGWIN_TRUE@man_MANS = \
-@OS_WIN32_CYGWIN_TRUE@	sg3_utils.8 \
+@OS_WIN32_CYGWIN_TRUE@	sg3_utils.8 sg_decode_sense.8 \
 @OS_WIN32_CYGWIN_TRUE@	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 @OS_WIN32_CYGWIN_TRUE@	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 @OS_WIN32_CYGWIN_TRUE@	sg_inq.8 sg_logs.8 sg_luns.8 sg_modes.8 sg_opcodes.8 \
@@ -259,7 +259,7 @@
 @OS_WIN32_CYGWIN_TRUE@	sg_write_buffer.8 sg_write_long.8 sg_write_same.8 sg_wr_mode.8
 
 @OS_WIN32_MINGW_TRUE@man_MANS = \
-@OS_WIN32_MINGW_TRUE@	sg3_utils.8 \
+@OS_WIN32_MINGW_TRUE@	sg3_utils.8 sg_decode_sense.8 \
 @OS_WIN32_MINGW_TRUE@	scsi_readcap.8  scsi_ready.8  scsi_start.8  scsi_stop.8 \
 @OS_WIN32_MINGW_TRUE@	sg_format.8 sg_get_config.8 sg_get_lba_status.8 sg_ident.8 \
 @OS_WIN32_MINGW_TRUE@	sg_inq.8 sg_logs.8 sg_luns.8 sg_modes.8 sg_opcodes.8 sg_persist.8 \
diff --git a/doc/sg3_utils.8 b/doc/sg3_utils.8
index 586d3db..20927a4 100644
--- a/doc/sg3_utils.8
+++ b/doc/sg3_utils.8
@@ -1,4 +1,4 @@
-.TH SG3_UTILS "8" "September 2010" "sg3_utils\-1.30" SG3_UTILS
+.TH SG3_UTILS "8" "December 2010" "sg3_utils\-1.31" SG3_UTILS
 .SH NAME
 sg3_utils \- a package of utilities for sending SCSI commands
 .SH SYNOPSIS
@@ -391,7 +391,7 @@
 .SH "REPORTING BUGS"
 Report bugs to <dgilbert at interlog dot com>.
 .SH COPYRIGHT
-Copyright \(co 1999\-2010 Douglas Gilbert
+Copyright \(co 1999\-2011 Douglas Gilbert
 .br
 Some utilities are distributed under a GPL version 2 license while
 others, usually more recent ones, are under a FreeBSD license. The files
diff --git a/doc/sg_decode_sense.8 b/doc/sg_decode_sense.8
new file mode 100644
index 0000000..40551b3
--- /dev/null
+++ b/doc/sg_decode_sense.8
@@ -0,0 +1,79 @@
+.TH SG_DECODE_SENSE "8" "December 2010" "sg3_utils\-1.31" SG3_UTILS
+.SH NAME
+sg_decode_sense \- decode SCSI sense data
+.SH SYNOPSIS
+.B sg_decode_sense
+[\fI\-\-binary=FN\fR] [\fI\-\-help\fR] [\fI\-\-hex=FN\fR]
+[\fI\-\-status=SS\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+[\fI\-\-write=WFN\fR] [H1 H2 H3 ...]
+.SH DESCRIPTION
+.\" Add any additional description here
+This utility takes SCSI sense data in binary or as a sequence of
+ASCII hexadecimal bytes and decodes it. The primary reference for the
+decoding is SPC\-3 ANSI INCITS 408-2005 and the most recent draft
+SPC\-4 revision 28 which can be found at http://www.t10.org .
+.PP
+SCSI sense data is often found in kernel log files as a result of
+something going wrong but may just be informative. It is often shown as
+a sequence of hexadecimal bytes, starting with 70, 71, 72 or 73.
+Sense data could be up to 252 bytes long but typically is much shorter
+than that, 18 bytes long is often seen and is usually associated with
+the older "fixed" format sense data.
+.PP
+The sense data can be provided on the command line or in a file. If
+given on the command line the sense data should be a sequence of
+hexadecimal bytes separated by space. Alternatively a file can be
+given with the contents in binary or ASCII hexadecimal bytes. The
+latter form can contain several lines each with none, one or more
+ASCII hexadecimal bytes separated by space (comma or tab). The
+hash symbol may appear and it and the rest of the line is ignored
+making it useful for comments.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-binary\fR=\fIFN\fR
+the sense data is read in binary from a file called \fIFN\fR.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-in\fR=\fIFN\fR
+the sense data is read in ASCII hexadecimal from a file called \fIFN\fR.
+The sense data should appear as a sequence of bytes separated by space,
+comma, tab or newline. Everything from and including a hash symbol to the
+end of that line is ignored.
+.TP
+\fB\-s\fR, \fB\-\-status\fR=\fISS\fR
+where \fISS\fR is a SCSI status byte value, given in hexadecimal. The
+SCSI status byte is related to but distinct from sense data.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the degree of verbosity (debug messages).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+output version string then exit.
+.TP
+\fB\-w\fR, \fB\-\-write\fR=\fIWFN\fR
+writes the sense data out in binary to a file called \fIWFN\fR. If
+necessary \fIWFN\fR is created. If \fIWFN\fR exists then it is
+truncated prior to writing the sense data to it. This option is
+a convenience and may be helpful in converting the ASCII hexadecimal
+representation of sense data into the equivalent binary.
+.SH NOTES
+Unlike most utilities in this package, this utility does not access a
+SCSI device (logical unit). This utility accesses a library associated
+with this package. Amongst other things the library decodes sense data.
+.SH EXIT STATUS
+The exit status of sg_decode_sense is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2010\-2011 Douglas Gilbert
+.br
+This software is distributed under a FreeBSD license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_requests(sg3_utils)
diff --git a/lib/sg_lib.c b/lib/sg_lib.c
index 8d612e4..c5f78d4 100644
--- a/lib/sg_lib.c
+++ b/lib/sg_lib.c
@@ -317,7 +317,7 @@
                           int * progress_outp)
 {
     const unsigned char * ucp;
-    int sk;
+    int sk, sk_pr;
 
     if (sb_len < 7)
         return 0;
@@ -336,14 +336,19 @@
             return 0;
     case 0x72:
     case 0x73:
+        /* sense key specific progress (0x2) or progress descriptor (0xa) */
         sk = (sensep[1] & 0xf);
-        if ((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk))
-            return 0;
-        ucp = sg_scsi_sense_desc_find(sensep, sb_len, 2 /* sense key spec. */);
-        if (ucp && (0x6 == ucp[1]) && (0x80 & ucp[4])) {
+        sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk);
+        if (sk_pr && ((ucp = sg_scsi_sense_desc_find(sensep, sb_len, 2))) &&
+            (0x6 == ucp[1]) && (0x80 & ucp[4])) {
             if (progress_outp)
                 *progress_outp = (ucp[5] << 8) + ucp[6];
             return 1;
+        } else if (((ucp = sg_scsi_sense_desc_find(sensep, sb_len, 0xa))) &&
+                   ((0x6 == ucp[1]))) {
+            if (progress_outp)
+                *progress_outp = (ucp[6] << 8) + ucp[7];
+            return 1;
         } else
             return 0;
     default:
@@ -371,6 +376,12 @@
     return buff;
 }
 
+static const char * sdata_src[] = {
+    "unknown",
+    "Extended Copy command source device",
+    "Extended Copy command destination device",
+    };
+
 
 /* Print descriptor format sense descriptors (assumes sense buffer is
    in descriptor format) */
@@ -379,9 +390,10 @@
                              int buff_len, char * buff)
 {
     int add_sen_len, add_len, desc_len, k, j, sense_key, processed;
-    int n, progress;
+    int n, progress, pr, rem;
     const unsigned char * descp;
-    char b[256];
+    const char * dtsp = "   >> descriptor too short";
+    char b[800];
 
     if ((NULL == buff) || (buff_len <= 0))
         return;
@@ -393,7 +405,9 @@
     sense_key = (sense_buffer[1] & 0xf);
     for (desc_len = 0, k = 0; k < add_sen_len; k += desc_len) {
         descp += desc_len;
-        add_len = (k < (add_sen_len - 1)) ? descp[1]: -1;
+        add_len = (k < (add_sen_len - 1)) ? descp[1] : -1;
+        if ((k + add_len + 2) > add_sen_len)
+            add_len = add_sen_len - k - 2;
         desc_len = add_len + 2;
         n = 0;
         n += sprintf(b + n, "  Descriptor type: ");
@@ -406,8 +420,10 @@
                 for (j = 0; j < 8; ++j)
                     n += sprintf(b + n, "%02x", descp[4 + j]);
                 n += sprintf(b + n, "\n");
-            } else
+            } else {
+                n += sprintf(b + n, "%s\n", dtsp);
                 processed = 0;
+            }
             break;
         case 1:
             n += sprintf(b + n, "Command specific\n");
@@ -416,8 +432,10 @@
                 for (j = 0; j < 8; ++j)
                     n += sprintf(b + n, "%02x", descp[4 + j]);
                 n += sprintf(b + n, "\n");
-            } else
+            } else {
+                n += sprintf(b + n, "%s\n", dtsp);
                 processed = 0;
+            }
             break;
         case 2:
             n += sprintf(b + n, "Sense key specific:");
@@ -425,6 +443,7 @@
             case SPC_SK_ILLEGAL_REQUEST:
                 n += sprintf(b + n, " Field pointer\n");
                 if (add_len < 6) {
+                    n += sprintf(b + n, "%s\n", dtsp);
                     processed = 0;
                     break;
                 }
@@ -441,6 +460,7 @@
             case SPC_SK_RECOVERED_ERROR:
                 n += sprintf(b + n, " Actual retry count\n");
                 if (add_len < 6) {
+                    n += sprintf(b + n, "%s\n", dtsp);
                     processed = 0;
                     break;
                 }
@@ -451,17 +471,19 @@
             case SPC_SK_NOT_READY:
                 n += sprintf(b + n, " Progress indication: ");
                 if (add_len < 6) {
+                    n += sprintf(b + n, "%s\n", dtsp);
                     processed = 0;
-                    n += sprintf(b + n, " field too short\n");
                     break;
                 }
                 progress = (descp[5] << 8) + descp[6];
-                n += sprintf(b + n, "%d %%\n",
-                        (progress * 100) / 0x10000);
+                pr = (progress * 100) / 65536;
+                rem = ((progress * 100) % 65536) / 655;
+                n += sprintf(b + n, "%d.%02d%%\n", pr, rem);
                 break;
             case SPC_SK_COPY_ABORTED:
                 n += sprintf(b + n, " Segment pointer\n");
                 if (add_len < 6) {
+                    n += sprintf(b + n, "%s\n", dtsp);
                     processed = 0;
                     break;
                 }
@@ -490,8 +512,10 @@
             n += sprintf(b + n, "Field replaceable unit\n");
             if (add_len >= 2)
                 n += sprintf(b + n, "    code=0x%x\n", descp[3]);
-            else
+            else {
+                n += sprintf(b + n, "%s\n", dtsp);
                 processed = 0;
+            }
             break;
         case 4:
             n += sprintf(b + n, "Stream commands\n");
@@ -504,16 +528,20 @@
                     n += sprintf(b + n, "    Incorrect Length Indicator "
                             "(ILI)");
                 n += sprintf(b + n, "\n");
-            } else
+            } else {
+                n += sprintf(b + n, "%s\n", dtsp);
                 processed = 0;
+            }
             break;
         case 5:
             n += sprintf(b + n, "Block commands\n");
             if (add_len >= 2)
                 n += sprintf(b + n, "    Incorrect Length Indicator "
                         "(ILI) %s\n", (descp[3] & 0x20) ? "set" : "clear");
-            else
+            else {
+                n += sprintf(b + n, "%s\n", dtsp);
                 processed = 0;
+            }
             break;
         case 6:
             n += sprintf(b + n, "OSD object identification\n");
@@ -546,18 +574,22 @@
                                  descp[11], descp[9], descp[7]);
                 n += sprintf(b + n, "    device=0x%x  status=0x%x\n",
                         descp[12], descp[13]);
-            } else
+            } else {
+                n += sprintf(b + n, "%s\n", dtsp);
                 processed = 0;
+            }
             break;
         case 0xa:       /* Added in SPC-4 rev 17 */
             n += sprintf(b + n, "Progress indication\n");
             if (add_len < 6) {
+                n += sprintf(b + n, "%s\n", dtsp);
                 processed = 0;
-                n += sprintf(b + n, " field too short\n");
                 break;
             }
             progress = (descp[6] << 8) + descp[7];
-            n += sprintf(b + n, "    %d %%", (progress * 100) / 0x10000);
+            pr = (progress * 100) / 65536;
+            rem = ((progress * 100) % 65536) / 655;
+            n += sprintf(b + n, "    %d.02%d%%", pr, rem);
             n += sprintf(b + n, " [sense_key=0x%x asc,ascq=0x%x,0x%x]\n",
                          descp[2], descp[3], descp[4]);
             break;
@@ -566,6 +598,38 @@
             /* Will decode if this 'feature' stays  xxxxxxxxxxxxx */
             processed = 0;
             break;
+        case 0xc:       /* Added in SPC-4 rev 28 */
+            n += sprintf(b + n, "Forwarded sense data\n");
+            if (add_len < 2) {
+                n += sprintf(b + n, "%s\n", dtsp);
+                processed = 0;
+                break;
+            }
+            n += sprintf(b + n, "    FSDT: %s\n",
+                         (descp[2] & 0x80) ? "set" : "clear");
+            j = descp[2] & 0xf;
+            if (j < 3)
+                n += sprintf(b + n, "    Sense data source: %s\n",
+                             sdata_src[j]);
+            else
+                n += sprintf(b + n, "    Sense data source: reserved [%d]\n",
+                             j);
+            {
+                char c[200];
+
+                sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c);
+                c[sizeof(c) - 1] = '\0';
+                n += sprintf(b + n, "    Forwarded status: %s\n", c);
+                if (add_len > 2) {
+                    /* recursing; hope not to get carried away */
+                    n += sprintf(b + n, " vvvvvvvvvvvvvvvv\n");
+                    sg_get_sense_str(NULL, descp + 4, add_len - 2, 0,
+                                     sizeof(c), c);
+                    n += sprintf(b + n, "%s", c);
+                    n += sprintf(b + n, " ^^^^^^^^^^^^^^^^\n");
+                }
+            }
+            break;
         default:
             n += sprintf(b + n, "Unknown or vendor specific [0x%x]\n",
                     descp[0]);
@@ -575,8 +639,7 @@
         if (! processed) {
             if (add_len > 0) {
                 n += sprintf(b + n, "    ");
-                for (j = 0; (j < add_len) && ((k + j + 2) < add_sen_len);
-                     ++j) {
+                for (j = 0; j < add_len; ++j) {
                     if ((j > 0) && (0 == (j % 24)))
                         n += sprintf(b + n, "\n    ");
                     n += sprintf(b + n, "%02x ", descp[j + 2]);
@@ -603,7 +666,7 @@
 sg_get_sense_str(const char * leadin, const unsigned char * sense_buffer,
                  int sb_len, int raw_sinfo, int buff_len, char * buff)
 {
-    int len, valid, progress, n, r;
+    int len, valid, progress, n, r, pr, rem;
     unsigned int info;
     int descriptor_format = 0;
     const char * error = NULL;
@@ -726,8 +789,10 @@
                 case SPC_SK_NO_SENSE:
                 case SPC_SK_NOT_READY:
                     progress = (sense_buffer[16] << 8) + sense_buffer[17];
-                    r += sprintf(b + r, "  Progress indication: %d %%\n",
-                                (progress * 100) / 0x10000);
+                    pr = (progress * 100) / 65536;
+                    rem = ((progress * 100) % 65536) / 655;
+                    r += sprintf(b + r, "  Progress indication: %d.%02d%%\n",
+                                 pr, rem);
                     break;
                 case SPC_SK_HARDWARE_ERROR:
                 case SPC_SK_MEDIUM_ERROR:
diff --git a/lib/sg_lib_data.c b/lib/sg_lib_data.c
index ba2093b..a6abcc9 100644
--- a/lib/sg_lib_data.c
+++ b/lib/sg_lib_data.c
@@ -15,7 +15,7 @@
 #endif
 
 
-const char * sg_lib_version_str = "1.63 20101028";    /* spc-4 rev 27 */
+const char * sg_lib_version_str = "1.65 20101205";    /* spc-4 rev 28 */
 
 struct sg_lib_value_name_t sg_lib_normal_opcodes[] = {
     {0, 0, "Test Unit Ready"},
@@ -471,6 +471,7 @@
     {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"},
diff --git a/src/Makefile.am b/src/Makefile.am
index 433133d..b45ff5e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,9 +8,9 @@
 # sg_scan is shared by Linux and Win32
 
 bin_PROGRAMS = \
-	sg_dd sg_emc_trespass sg_format sg_get_config sg_get_lba_status \
-	sg_ident sginfo sg_inq sg_logs sg_luns sg_map26 sg_map \
-	sgm_dd sg_modes sg_opcodes sgp_dd sg_persist sg_prevent \
+	sg_dd sg_decode_sense sg_emc_trespass sg_format sg_get_config \
+	sg_get_lba_status sg_ident sginfo sg_inq sg_logs sg_luns sg_map26 \
+	sg_map sgm_dd sg_modes sg_opcodes sgp_dd sg_persist sg_prevent \
 	sg_raw sg_rbuf sg_rdac sg_read sg_readcap sg_read_block_limits \
 	sg_read_buffer sg_read_long sg_reassign sg_referrals \
 	sg_requests sg_reset sg_rmsn sg_rtpg sg_safte sg_sat_identify \
@@ -30,7 +30,7 @@
 if OS_WIN32_MINGW
 
 bin_PROGRAMS = \
-	sg_format sg_get_config sg_get_lba_status sg_ident \
+	sg_decode_sense sg_format sg_get_config sg_get_lba_status sg_ident \
 	sg_inq sg_logs sg_luns sg_modes sg_opcodes sg_persist \
 	sg_prevent sg_raw sg_rdac sg_readcap sg_read_block_limits \
 	sg_read_buffer sg_read_long sg_reassign sg_referrals sg_requests \
@@ -51,7 +51,7 @@
 if OS_WIN32_CYGWIN
 
 bin_PROGRAMS = \
-	sg_format sg_get_config sg_get_lba_status sg_ident \
+	sg_decode_sense sg_format sg_get_config sg_get_lba_status sg_ident \
 	sg_inq sg_logs sg_luns sg_modes sg_opcodes sg_persist \
 	sg_prevent sg_raw sg_rdac sg_readcap sg_read_block_limits \
 	sg_read_buffer sg_read_long sg_reassign sg_referrals sg_requests \
@@ -72,7 +72,7 @@
 if OS_FREEBSD
 
 bin_PROGRAMS = \
-	sg_format sg_get_config sg_get_lba_status sg_ident \
+	sg_decode_sense sg_format sg_get_config sg_get_lba_status sg_ident \
 	sg_inq sg_logs sg_luns sg_modes sg_opcodes sg_persist \
 	sg_prevent sg_raw sg_rdac sg_readcap sg_read_block_limits \
 	sg_read_buffer sg_read_long sg_reassign sg_referrals sg_requests \
@@ -87,7 +87,7 @@
 if OS_SOLARIS
 
 bin_PROGRAMS = \
-	sg_format sg_get_config sg_get_lba_status sg_ident \
+	sg_decode_sense sg_format sg_get_config sg_get_lba_status sg_ident \
 	sg_inq sg_logs sg_luns sg_modes sg_opcodes sg_persist \
 	sg_prevent sg_raw sg_rdac sg_readcap sg_read_block_limits \
 	sg_read_buffer sg_read_long sg_reassign sg_referrals sg_requests \
@@ -102,7 +102,7 @@
 if OS_OSF
 
 bin_PROGRAMS = \
-	sg_format sg_get_config sg_get_lba_status sg_ident \
+	sg_decode_sense sg_format sg_get_config sg_get_lba_status sg_ident \
 	sg_inq sg_logs sg_luns sg_modes sg_opcodes sg_persist \
 	sg_prevent sg_raw sg_rdac sg_readcap sg_read_block_limits \
 	sg_read_buffer sg_read_long sg_reassign sg_referrals sg_requests \
@@ -120,6 +120,9 @@
 sg_dd_SOURCES =	sg_dd.c
 sg_dd_LDADD = ../lib/libsgutils2.la @os_libs@
 
+sg_decode_sense_SOURCES =	sg_decode_sense.c
+sg_decode_sense_LDADD = ../lib/libsgutils2.la @os_libs@
+
 sg_emc_trespass_SOURCES = sg_emc_trespass.c
 sg_emc_trespass_LDADD = ../lib/libsgutils2.la @os_libs@
 
diff --git a/src/Makefile.in b/src/Makefile.in
index d3bf315..8bd8084 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -34,7 +34,8 @@
 POST_UNINSTALL = :
 build_triplet = @build@
 host_triplet = @host@
-@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@bin_PROGRAMS = sg_format$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@bin_PROGRAMS = sg_decode_sense$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@	sg_format$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@	sg_get_config$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@	sg_get_lba_status$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@	sg_ident$(EXEEXT) \
@@ -74,7 +75,8 @@
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@	sg_write_long$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@	sg_write_same$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_FALSE@@OS_WIN32_MINGW_TRUE@	sg_wr_mode$(EXEEXT)
-@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@bin_PROGRAMS = sg_format$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@bin_PROGRAMS = sg_decode_sense$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@	sg_format$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@	sg_get_config$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@	sg_get_lba_status$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@	sg_ident$(EXEEXT) \
@@ -114,7 +116,8 @@
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@	sg_write_long$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@	sg_write_same$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_FALSE@@OS_WIN32_CYGWIN_TRUE@	sg_wr_mode$(EXEEXT)
-@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@bin_PROGRAMS = sg_format$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@bin_PROGRAMS = sg_decode_sense$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@	sg_format$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@	sg_get_config$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@	sg_get_lba_status$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@	sg_ident$(EXEEXT) \
@@ -153,7 +156,8 @@
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@	sg_write_long$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@	sg_write_same$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_FALSE@@OS_SOLARIS_TRUE@	sg_wr_mode$(EXEEXT)
-@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_TRUE@bin_PROGRAMS = sg_format$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_TRUE@bin_PROGRAMS = sg_decode_sense$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_TRUE@	sg_format$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_TRUE@	sg_get_config$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_TRUE@	sg_get_lba_status$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_TRUE@	sg_ident$(EXEEXT) \
@@ -193,6 +197,7 @@
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_TRUE@	sg_write_same$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_FALSE@@OS_OSF_TRUE@	sg_wr_mode$(EXEEXT)
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@bin_PROGRAMS = sg_dd$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_decode_sense$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_emc_trespass$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_format$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_get_config$(EXEEXT) \
@@ -243,8 +248,8 @@
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_write_long$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_write_same$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_wr_mode$(EXEEXT)
-@OS_FREEBSD_TRUE@bin_PROGRAMS = sg_format$(EXEEXT) \
-@OS_FREEBSD_TRUE@	sg_get_config$(EXEEXT) \
+@OS_FREEBSD_TRUE@bin_PROGRAMS = sg_decode_sense$(EXEEXT) \
+@OS_FREEBSD_TRUE@	sg_format$(EXEEXT) sg_get_config$(EXEEXT) \
 @OS_FREEBSD_TRUE@	sg_get_lba_status$(EXEEXT) sg_ident$(EXEEXT) \
 @OS_FREEBSD_TRUE@	sg_inq$(EXEEXT) sg_logs$(EXEEXT) \
 @OS_FREEBSD_TRUE@	sg_luns$(EXEEXT) sg_modes$(EXEEXT) \
@@ -281,6 +286,9 @@
 am_sg_dd_OBJECTS = sg_dd.$(OBJEXT)
 sg_dd_OBJECTS = $(am_sg_dd_OBJECTS)
 sg_dd_DEPENDENCIES = ../lib/libsgutils2.la
+am_sg_decode_sense_OBJECTS = sg_decode_sense.$(OBJEXT)
+sg_decode_sense_OBJECTS = $(am_sg_decode_sense_OBJECTS)
+sg_decode_sense_DEPENDENCIES = ../lib/libsgutils2.la
 am_sg_emc_trespass_OBJECTS = sg_emc_trespass.$(OBJEXT)
 sg_emc_trespass_OBJECTS = $(am_sg_emc_trespass_OBJECTS)
 sg_emc_trespass_DEPENDENCIES = ../lib/libsgutils2.la
@@ -444,14 +452,14 @@
 LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
 	--mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
 	$(LDFLAGS) -o $@
-SOURCES = $(sg_dd_SOURCES) $(sg_emc_trespass_SOURCES) \
-	$(sg_format_SOURCES) $(sg_get_config_SOURCES) \
-	$(sg_get_lba_status_SOURCES) $(sg_ident_SOURCES) \
-	$(sg_inq_SOURCES) $(sg_logs_SOURCES) $(sg_luns_SOURCES) \
-	$(sg_map_SOURCES) $(sg_map26_SOURCES) $(sg_modes_SOURCES) \
-	$(sg_opcodes_SOURCES) $(sg_persist_SOURCES) \
-	$(sg_prevent_SOURCES) $(sg_raw_SOURCES) $(sg_rbuf_SOURCES) \
-	$(sg_rdac_SOURCES) $(sg_read_SOURCES) \
+SOURCES = $(sg_dd_SOURCES) $(sg_decode_sense_SOURCES) \
+	$(sg_emc_trespass_SOURCES) $(sg_format_SOURCES) \
+	$(sg_get_config_SOURCES) $(sg_get_lba_status_SOURCES) \
+	$(sg_ident_SOURCES) $(sg_inq_SOURCES) $(sg_logs_SOURCES) \
+	$(sg_luns_SOURCES) $(sg_map_SOURCES) $(sg_map26_SOURCES) \
+	$(sg_modes_SOURCES) $(sg_opcodes_SOURCES) \
+	$(sg_persist_SOURCES) $(sg_prevent_SOURCES) $(sg_raw_SOURCES) \
+	$(sg_rbuf_SOURCES) $(sg_rdac_SOURCES) $(sg_read_SOURCES) \
 	$(sg_read_block_limits_SOURCES) $(sg_read_buffer_SOURCES) \
 	$(sg_read_long_SOURCES) $(sg_readcap_SOURCES) \
 	$(sg_reassign_SOURCES) $(sg_referrals_SOURCES) \
@@ -466,14 +474,14 @@
 	$(sg_write_buffer_SOURCES) $(sg_write_long_SOURCES) \
 	$(sg_write_same_SOURCES) $(sginfo_SOURCES) $(sgm_dd_SOURCES) \
 	$(sgp_dd_SOURCES)
-DIST_SOURCES = $(sg_dd_SOURCES) $(sg_emc_trespass_SOURCES) \
-	$(sg_format_SOURCES) $(sg_get_config_SOURCES) \
-	$(sg_get_lba_status_SOURCES) $(sg_ident_SOURCES) \
-	$(sg_inq_SOURCES) $(sg_logs_SOURCES) $(sg_luns_SOURCES) \
-	$(sg_map_SOURCES) $(sg_map26_SOURCES) $(sg_modes_SOURCES) \
-	$(sg_opcodes_SOURCES) $(sg_persist_SOURCES) \
-	$(sg_prevent_SOURCES) $(sg_raw_SOURCES) $(sg_rbuf_SOURCES) \
-	$(sg_rdac_SOURCES) $(sg_read_SOURCES) \
+DIST_SOURCES = $(sg_dd_SOURCES) $(sg_decode_sense_SOURCES) \
+	$(sg_emc_trespass_SOURCES) $(sg_format_SOURCES) \
+	$(sg_get_config_SOURCES) $(sg_get_lba_status_SOURCES) \
+	$(sg_ident_SOURCES) $(sg_inq_SOURCES) $(sg_logs_SOURCES) \
+	$(sg_luns_SOURCES) $(sg_map_SOURCES) $(sg_map26_SOURCES) \
+	$(sg_modes_SOURCES) $(sg_opcodes_SOURCES) \
+	$(sg_persist_SOURCES) $(sg_prevent_SOURCES) $(sg_raw_SOURCES) \
+	$(sg_rbuf_SOURCES) $(sg_rdac_SOURCES) $(sg_read_SOURCES) \
 	$(sg_read_block_limits_SOURCES) $(sg_read_buffer_SOURCES) \
 	$(sg_read_long_SOURCES) $(sg_readcap_SOURCES) \
 	$(sg_reassign_SOURCES) $(sg_referrals_SOURCES) \
@@ -609,6 +617,8 @@
 # AM_CFLAGS = -I ../include -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -W -pedantic -std=c99
 sg_dd_SOURCES = sg_dd.c
 sg_dd_LDADD = ../lib/libsgutils2.la @os_libs@
+sg_decode_sense_SOURCES = sg_decode_sense.c
+sg_decode_sense_LDADD = ../lib/libsgutils2.la @os_libs@
 sg_emc_trespass_SOURCES = sg_emc_trespass.c
 sg_emc_trespass_LDADD = ../lib/libsgutils2.la @os_libs@
 sg_format_SOURCES = sg_format.c
@@ -789,6 +799,9 @@
 sg_dd$(EXEEXT): $(sg_dd_OBJECTS) $(sg_dd_DEPENDENCIES) 
 	@rm -f sg_dd$(EXEEXT)
 	$(LINK) $(sg_dd_OBJECTS) $(sg_dd_LDADD) $(LIBS)
+sg_decode_sense$(EXEEXT): $(sg_decode_sense_OBJECTS) $(sg_decode_sense_DEPENDENCIES) 
+	@rm -f sg_decode_sense$(EXEEXT)
+	$(LINK) $(sg_decode_sense_OBJECTS) $(sg_decode_sense_LDADD) $(LIBS)
 sg_emc_trespass$(EXEEXT): $(sg_emc_trespass_OBJECTS) $(sg_emc_trespass_DEPENDENCIES) 
 	@rm -f sg_emc_trespass$(EXEEXT)
 	$(LINK) $(sg_emc_trespass_OBJECTS) $(sg_emc_trespass_LDADD) $(LIBS)
@@ -947,6 +960,7 @@
 	-rm -f *.tab.c
 
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_dd.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_decode_sense.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_emc_trespass.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_format.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_get_config.Po@am__quote@
diff --git a/src/sg_decode_sense.c b/src/sg_decode_sense.c
new file mode 100644
index 0000000..b068418
--- /dev/null
+++ b/src/sg_decode_sense.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 2010-2011 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 <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+
+
+static char * version_str = "1.00 20101204";
+
+#define MAX_SENSE_LEN (256 + 8) /* max descriptor format currently */
+
+static struct option long_options[] = {
+    {"binary", required_argument, 0, 'b'},
+    {"help", no_argument, 0, 'h'},
+    {"hex", required_argument, 0, 'H'},
+    {"status", required_argument, 0, 's'},
+    {"verbose", no_argument, 0, 'v'},
+    {"version", no_argument, 0, 'V'},
+    {"write", required_argument, 0, 'w'},
+    {0, 0, 0, 0},
+};
+
+struct opts_t {
+    int do_binary;
+    const char * fname;
+    int do_help;
+    int do_hex;
+    int do_status;
+    int sstatus;
+    int do_verbose;
+    int do_version;
+    const char * wfname;
+    unsigned char sense[MAX_SENSE_LEN + 4];
+    int sense_len;
+};
+
+
+static void
+usage()
+{
+  fprintf(stderr, "Usage: "
+          "sg_decode_sense [--binary=FN] [--help] [--hex=FN] [--status=SS]\n"
+          "                       [--verbose] [--version] [--write=WFN] "
+          "H1 H2 H3 ...\n"
+          "  where:\n"
+          "    --binary=FN|-b FN     FN is a file name to read sense "
+          "data in\n"
+          "                          binary from. If FN is '-' then read "
+          "from stdin\n"
+          "    --help|-h             print out usage message\n"
+          "    --hex=FN|-H FN        FN is a file name from which to read "
+          "sense data\n"
+          "                          in ASCII hexadecimal. Interpret '-' "
+          "as stdin\n"
+          "    --status=SS |-s SS    SCSI status value in hex\n"
+          "    --verbose|-v          increase verbosity\n"
+          "    --version|-V          print version string then exit\n"
+          "    --write=WFN |-w WFN    write sense data in binary to WFN, "
+          "create if\n"
+          "                           required else truncate prior to "
+          "writing\n\n"
+          "Decodes SCSI sense data given on the command line as a sequence "
+          "of\nhexadecimal bytes (H1 H2 H3 ...) . Alternatively the sense "
+          "data can\nbe in a binary file or in a file containing ASCII "
+          "hexadecimal.\n"
+          );
+}
+
+static int
+process_cl(struct opts_t *optsp, int argc, char *argv[])
+{
+    int c;
+    unsigned int ul;
+    char * opt;
+    char *endptr;
+    long val;
+
+    while (1) {
+        c = getopt_long(argc, argv, "b:hH:s:vVw:", long_options, NULL);
+        if (c == -1)
+            break;
+
+        switch (c) {
+        case 'b':
+            if (optsp->fname) {
+                fprintf(stderr, "expect only one '--binary=FN' or "
+                        "'--hex=FN' option\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            ++optsp->do_binary;
+            optsp->fname = optarg;
+            break;
+        case 'h':
+        case '?':
+            optsp->do_help = 1;
+            return 0;
+        case 'H':
+            if (optsp->fname) {
+                fprintf(stderr, "expect only one '--binary=FN' or "
+                        "'--hex=FN' option\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            ++optsp->do_hex;
+            optsp->fname = optarg;
+            break;
+        case 's':
+            if (1 != sscanf(optarg, "%x", &ul)) {
+                fprintf(stderr, "'--status=SS' expects a byte value\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            if (ul > 0xff) {
+                fprintf(stderr, "'--status=SS' byte value exceeds FF\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            ++optsp->do_status;
+            optsp->sstatus = ul;
+            break;
+        case 'v':
+            ++optsp->do_verbose;
+            break;
+        case 'V':
+            optsp->do_version = 1;
+            return 0;
+        case 'w':
+            optsp->wfname = optarg;
+            break;
+        default:
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+
+    while (optind < argc) {
+        opt = argv[optind++];
+        val = strtol(opt, &endptr, 16);
+        if (*opt == '\0' || *endptr != '\0' || val < 0x00 || val > 0xff) {
+            fprintf(stderr, "Invalid byte '%s'\n", opt);
+            return SG_LIB_SYNTAX_ERROR;
+        }
+
+        if (optsp->sense_len > MAX_SENSE_LEN) {
+            fprintf(stderr, "sense data too long (max. %d bytes)\n",
+                    MAX_SENSE_LEN);
+            return SG_LIB_SYNTAX_ERROR;
+        }
+        optsp->sense[optsp->sense_len++] = (unsigned char)val;
+    }
+    return 0;
+}
+
+/* Read hex numbers from file ('-' taken as stdin).
+ * There should be either one entry per line, a comma separated list or
+ * space separated list. Everything from and including a '#' on a line
+ * is ignored.  Returns 0 if ok, or 1 if error. */
+static int file2hex_arr(const char * fname, unsigned char * mp_arr,
+                        int * mp_arr_len, int max_arr_len)
+{
+    int fn_len, in_len, k, j, m;
+    unsigned int h;
+    const char * lcp;
+    FILE * fp;
+    char line[512];
+    int off = 0;
+
+    if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len))
+        return 1;
+    fn_len = strlen(fname);
+    if (0 == fn_len)
+        return 1;
+    if ((1 == in_len) && ('-' == fname[0]))        /* read from stdin */
+        fp = stdin;
+    else {
+        fp = fopen(fname, "r");
+        if (NULL == fp) {
+            fprintf(stderr, "Unable to open %s for reading\n", fname);
+            return 1;
+        }
+    }
+
+    for (j = 0; j < 512; ++j) {
+        if (NULL == fgets(line, sizeof(line), fp))
+            break;
+        in_len = strlen(line);
+        if (in_len > 0) {
+            if ('\n' == line[in_len - 1]) {
+                --in_len;
+                line[in_len] = '\0';
+            }
+        }
+        if (0 == in_len)
+            continue;
+        lcp = line;
+        m = strspn(lcp, " \t");
+        if (m == in_len)
+            continue;
+        lcp += m;
+        in_len -= m;
+        if ('#' == *lcp)
+            continue;
+        k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
+        if ((k < in_len) && ('#' != lcp[k])) {
+            fprintf(stderr, "build_mode_page: syntax error at "
+                    "line %d, pos %d\n", j + 1, m + k + 1);
+            goto bad;
+        }
+        for (k = 0; k < 1024; ++k) {
+            if (1 == sscanf(lcp, "%x", &h)) {
+                if (h > 0xff) {
+                    fprintf(stderr, "build_mode_page: hex number "
+                            "larger than 0xff in line %d, pos %d\n",
+                            j + 1, (int)(lcp - line + 1));
+                    goto bad;
+                }
+                if ((off + k) >= max_arr_len) {
+                    fprintf(stderr, "build_mode_page: array length "
+                            "exceeded\n");
+                    goto bad;
+                }
+                mp_arr[off + k] = h;
+                lcp = strpbrk(lcp, " ,\t");
+                if (NULL == lcp)
+                    break;
+                lcp += strspn(lcp, " ,\t");
+                if ('\0' == *lcp)
+                    break;
+            } else {
+                if ('#' == *lcp) {
+                    --k;
+                    break;
+                }
+                fprintf(stderr, "build_mode_page: error in "
+                        "line %d, at pos %d\n", j + 1,
+                        (int)(lcp - line + 1));
+                goto bad;
+            }
+        }
+        off += (k + 1);
+    }
+    *mp_arr_len = off;
+    fclose(fp);
+    return 0;
+bad:
+    fclose(fp);
+    return 1;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+    int ret = 0;
+    size_t s;
+    struct opts_t opts;
+    char b[2048];
+    FILE * fp = NULL;
+
+    memset(&opts, 0, sizeof(opts));
+    memset(b, 0, sizeof(b));
+    ret = process_cl(&opts, argc, argv);
+    if (ret != 0) {
+        usage();
+        return ret;
+    } else if (opts.do_help) {
+        usage();
+        return 0;
+    } else if (opts.do_version) {
+        fprintf(stderr, "version: %s\n", version_str);
+        return 0;
+    }
+
+
+    if (opts.do_status) {
+        sg_get_scsi_status_str(opts.sstatus, sizeof(b) - 1, b);
+        printf("SCSI status: %s\n", b);
+    }
+
+    if ((0 == opts.sense_len) && (! opts.do_binary) && (! opts.do_hex)) {
+        if (opts.do_status)
+            return 0;
+        fprintf(stderr, ">> Need sense data on the command line or in a "
+                "file\n\n");
+        usage();
+        return SG_LIB_SYNTAX_ERROR;
+    }
+    if (opts.sense_len && (opts.do_binary || opts.do_hex)) {
+        fprintf(stderr, ">> Need sense data on command line or in a file, "
+                "not both\n\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
+    if (opts.do_binary && opts.do_hex) {
+        fprintf(stderr, ">> Either a binary file or a ASCII hexadecimal, "
+                "file not both\n\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
+
+    if (opts.do_binary) {
+        fp = fopen(opts.fname, "r");
+        if (NULL == fp) {
+            fprintf(stderr, "unable to open file: %s\n", opts.fname);
+            return SG_LIB_SYNTAX_ERROR;
+        }
+        s = fread(opts.sense, 1, MAX_SENSE_LEN, fp);
+        fclose(fp);
+        if (0 == s) {
+            fprintf(stderr, "read nothing from file: %s\n", opts.fname);
+            return SG_LIB_SYNTAX_ERROR;
+        }
+        opts.sense_len = s;
+    } else if (opts.do_hex) {
+        ret = file2hex_arr(opts.fname, opts.sense, &opts.sense_len,
+                           MAX_SENSE_LEN);
+        if (ret) {
+            fprintf(stderr, "unable to decode ASCII hex from file: %s\n",
+                    opts.fname);
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+
+    if (opts.sense_len) {
+        if (opts.wfname) {
+            if ((fp = fopen(opts.wfname, "w"))) {
+                s = fwrite(opts.sense, 1, opts.sense_len, fp);
+                if ((int)s != opts.sense_len)
+                    fprintf(stderr, "only able to write %d of %d bytes to "
+                            "%s\n", s, opts.sense_len, opts.wfname);
+                fclose(fp);
+            } else {
+                perror("open");
+                fprintf(stderr, "trying to write to %s\n", opts.wfname);
+            }
+        }
+        sg_get_sense_str(NULL, opts.sense, opts.sense_len, opts.do_verbose,
+                         sizeof(b) - 1, b);
+        printf("%s\n", b);
+    }
+
+    return 0;
+}
diff --git a/src/sg_format.c b/src/sg_format.c
index d92d4b6..be41a46 100644
--- a/src/sg_format.c
+++ b/src/sg_format.c
@@ -45,7 +45,7 @@
 #include "sg_cmds_basic.h"
 #include "sg_cmds_extra.h"
 
-static char * version_str = "1.18 20101030";
+static char * version_str = "1.19 20101203";
 
 #define RW_ERROR_RECOVERY_PAGE 1  /* every disk should have one */
 #define FORMAT_DEV_PAGE 3         /* Format Device Mode Page [now obsolete] */
@@ -54,9 +54,10 @@
 #define THIS_MPAGE_EXISTS RW_ERROR_RECOVERY_PAGE
 
 #define SHORT_TIMEOUT           20   /* 20 seconds unless immed=0 ... */
-#define FORMAT_TIMEOUT          (4 * 3600)       /* 4 hours ! */
+#define FORMAT_TIMEOUT          (15 * 3600)       /* 15 hours ! */
+                        /* Seagate ST32000444SS 2TB disk takes 9.5 hours */
 
-#define POLL_DURATION_SECS 30
+#define POLL_DURATION_SECS 60
 
 #if defined(MSC_VER) || defined(__MINGW32__)
 #define HAVE_MS_SLEEP
@@ -168,7 +169,7 @@
 scsi_format(int fd, int fmtpinfo, int cmplst, int pf_usage, int immed,
             int dcrt, int pie, int si, int early, int verbose)
 {
-        int res, need_hdr, progress, verb, fmt_pl_sz, longlist, off;
+        int res, need_hdr, progress, pr, rem, verb, fmt_pl_sz, longlist, off;
         const int SH_FORMAT_HEADER_SZ = 4;
         const int LO_FORMAT_HEADER_SZ = 8;
         const char INIT_PATTERN_DESC_SZ = 4;
@@ -241,10 +242,12 @@
                 progress = -1;
                 res = sg_ll_test_unit_ready_progress(fd, 0, &progress, 0,
                                                      verb);
-                if (progress >= 0)
-                        printf("Format in progress, %d%% done\n",
-                                (progress * 100) / 65536);
-                else
+                if (progress >= 0) {
+                        pr = (progress * 100) / 65536;
+                        rem = ((progress * 100) % 65536) / 655;
+                        printf("Format in progress, %d.%02d%% done\n",
+                               pr, rem);
+                } else
                         break;
         }
         printf("FORMAT Complete\n");
diff --git a/src/sg_inq.c b/src/sg_inq.c
index 619534b..2ed2c9c 100644
--- a/src/sg_inq.c
+++ b/src/sg_inq.c
@@ -66,7 +66,7 @@
  * information [MAINTENANCE IN, service action = 0xc]; see sg_opcodes.
  */
 
-static char * version_str = "0.94 20101030";    /* SPC-4 rev 27 */
+static char * version_str = "0.95 20101116";    /* SPC-4 rev 28 */
 
 
 #define VPD_SUPPORTED_VPDS 0x0
diff --git a/src/sg_inq_data.c b/src/sg_inq_data.c
index 113a901..1e925fc 100644
--- a/src/sg_inq_data.c
+++ b/src/sg_inq_data.c
@@ -23,9 +23,9 @@
 const char * sg_ansi_version_arr[] =
 {
     "no conformance claimed",
-    "SCSI-1",		/* obsolete */
-    "SCSI-2",		/* obsolete */
-    "SPC",		/* withdrawn */
+    "SCSI-1",           /* obsolete */
+    "SCSI-2",           /* obsolete */
+    "SPC",              /* withdrawn */
     "SPC-2",
     "SPC-3",
     "SPC-4",
@@ -39,7 +39,7 @@
     const char * name;
 };
 
-/* table from SPC-4 revision 27 [sorted numerically (from Annex D.8)] */
+/* table from SPC-4 revision 28 [sorted numerically (from Annex D.8)] */
 /* Can also be obtained from : http://www.t10.org/lists/stds.txt  */
 struct sg_version_descriptor sg_version_descriptor_arr[] = {
     {0x0, "Version Descriptor not supported or No standard identified"},
@@ -184,6 +184,7 @@
     {0x4e3, "MMC-6 T10/1836-D revision 2b"},
     {0x4e5, "MMC-6 T10/1836-D revision 02g"},
     {0x500, "ADC-3 (no version claimed)"},
+    {0x502,  "ADC-3 T10/1895-D revision 04"},
     {0x520, "SSC-4 (no version claimed)"},
     {0x560, "OSD-3 (no version claimed)"},
     {0x580, "SES-3 (no version claimed)"},
@@ -215,11 +216,7 @@
     {0x917, "FCP-2 ANSI INCITS 350-2003"},
     {0x918, "FCP-2 T10/1144-D revision 8"},
     {0x920, "SST (no version claimed)"},
-    {0x935, "SST T10/1380-D revision 8b"},
-    {0x940, "SRP (no version claimed)"},
-    {0x954, "SRP T10/1415-D revision 10"},
-    {0x955, "SRP T10/1415-D revision 16a"},
-    {0x95c, "SRP ANSI INCITS 365-2002"},
+    {0x935, "SST T10/1380-D revision 8b"}, {0x940, "SRP (no version claimed)"}, {0x954, "SRP T10/1415-D revision 10"}, {0x955, "SRP T10/1415-D revision 16a"}, {0x95c, "SRP ANSI INCITS 365-2002"},
     {0x960, "iSCSI (no version claimed)"},
     {0x980, "SBP-3 (no version claimed)"},
     {0x982, "SBP-3 T10/1467-D revision 1f"},
@@ -243,6 +240,7 @@
     {0xa27, "ADT-2 T10/1742-D revision 08"},
     {0xa40, "FCP-4 (no version claimed)"},
     {0xa42, "FCP-4 T10/1828-D revision 01"},
+    {0xa44, "FCP-4 T10/1828-D revision 02"},
     {0xaa0, "SPI (no version claimed)"},
     {0xab9, "SPI T10/0855-D revision 15a"},
     {0xaba, "SPI ANSI INCITS 253-1995"},
diff --git a/src/sg_turs.c b/src/sg_turs.c
index 933fb66..f361170 100644
--- a/src/sg_turs.c
+++ b/src/sg_turs.c
@@ -21,7 +21,7 @@
    data transfer (and no REQUEST SENSE command iff the unit is ready)
    then this can be used for timing per SCSI command overheads.
 
- * Copyright (C) 2000-2009 D. Gilbert
+ * Copyright (C) 2000-2010 D. Gilbert
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2, or (at your option)
@@ -29,7 +29,7 @@
 
  */
 
-static char * version_str = "3.27 20090422";
+static char * version_str = "3.28 20101203";
 
 #if defined(MSC_VER) || defined(__MINGW32__)
 #define HAVE_MS_SLEEP
@@ -265,7 +265,7 @@
 
 int main(int argc, char * argv[])
 {
-    int sg_fd, k, res, progress;
+    int sg_fd, k, res, progress, pr, rem;
     int num_errs = 0;
     int reported = 0;
     int ret = 0;
@@ -310,9 +310,11 @@
             if (progress < 0) {
                 ret = res;
                 break;
-            } else
-                printf("Progress indication: %d%% done\n",
-                                (progress * 100) / 65536);
+            } else {
+                pr = (progress * 100) / 65536;
+                rem = ((progress * 100) % 65536) / 655;
+                printf("Progress indication: %d.%02d%% done\n", pr, rem);
+            }
         }
         if (opts.do_number > 1)
             printf("Completed %d Test Unit Ready commands\n",