Load sg3_utils-1.04 into trunk/.


git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@32 6180dd3e-e324-4e3e-922d-17de1ae2f315
diff --git a/CHANGELOG b/CHANGELOG
index a481c75..059c80c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,69 @@
 some description at the top of its ".c" file. "KG" indicates work by
 Kurt Garloff <garloff@suse.de>.
 
+Changelog for sg3_utils-1.04 [20030513]
+  - all remaining utilities in the main directory have man pages [thanks
+    to Eric Schwartz <emschwar@debian.org> for 7 man pages]
+  - add CREDITS file
+  - sg_simple1, sg_simple2, sg_simple3, sg_simple4, sg_simple16 and
+    scsi_inquiry: moved to the examples directory
+  - sg_debug: moved to the archive directory
+  - sg_modes: add '-subp=<n>' for sub page code, suggests 6/10 byte
+    alternative if bad opcode sense received, flip -cpf flag to -pf,
+    add page names for most peripheral types
+  - sg_turs: default '-n=' argument to 1, only when '-n=1' print error
+    message in full
+  - sg_logs: print temperature "<not available>" for 255, '-t' switch
+    for temperature (from either temperature or IE log page)
+  - sg_dd: add '-odir=0|1' switch for O_DIRECT on block devices
+  - sg_start: add '-imm', '-loej' and 'pc=<n>' switches plus man page
+  - sg_readcap: add '-pmi' and 'lba=<n>' switches
+  - open files O_NONBLOCK in sg_inq, sg_modes and sg_logs so they
+    can be used on cd/dvd drivers when there is no disk present
+
+Changelog for sg3_utils-1.03 [20030402]
+  - sg_senddiag: added, allows self tests and listing of diag pages
+  - sg_start: changed to use SG_IO so works on block devices
+  - sg_err: print out some "sense key specific" data [Trent Piepho]
+  - sg_modes: add "-6" switch for force 6 byte MODE SENSE [Trent Piepho]
+  - sg_modes: more information on page codes and controls
+  - sg_inq, sg_modes, sg_logs, sg_senddiag: add man pages
+  - sg_dd: add "append=0|1" switch for glueing together large files
+  - note in README about utilities offered by scsirastools package
+
+Changelog for sg3_utils-1.02 [20030101]
+  - sg_inq: check if cmddt or evpd bits ignored
+  - sg_inq: warn -o=<n> not used for standard INQUIRY
+  - sg_turs: add -t option to time Test Unit Ready commands
+  - sg_errs: (used by most utilities) warn if sense buffer empty
+  - sg_modes: make safe with block SG_IO (bypass SG_GET_SCSI_ID ioctl)
+  - sg_logs: make safe with block SG_IO, self-test page work
+  - sg_dd: add "blksg_io=" switch to experiment with block SG_IO
+  - sg_read: now use SG_IO ioctl rather than sg write/read
+  - sginfo: fix writing parameters, check for block devices that answer
+    sg's ioctls, recognize "scd<n>" device names
+  - sg_map: stop close error report on tape devices
+  - sg_readcap: make safe with block SG_IO
+  - sg_start: make safe with block SG_IO
+  - sg_test_rwbuf: make safe with block SG_IO
+
+Changelog for sg3_utils-1.01 [20020814]
+----------------------------
+  - add raw switch ("-r") to sg_inq [Martin Schwenke]
+
+Changelog for sg3_utils-1.00 [20020728]
+----------------------------
+  - update sg_err [to SPC-3 T10/1416-D Rev 07 3 May 2002]
+  - "sg_inq -cl" now outputs opcode names
+  - add "continue on error" option to sg_dd
+  - add _LARGEFILE64_SOURCE _FILE_OFFSET_BITS=64 defines to Makefile
+  - drop 'gen' argument from sg_dd and friends, allow any file types
+    except scsi tape device file names
+  - treat of=/dev/null as special (skip write). Accept of=. as alias
+    for of=/dev/null
+  - decode various log pages in sg_logs
+  - add 'dio' argument to sgm_dd for testing "zero copy" copies
+
 Changelog for sg3_utils-0.99 [20020317]
 ----------------------------
   - add 'fua' and 'sync' arguments to sg_dd, sgp_dd and sgm_dd
diff --git a/CREDITS b/CREDITS
new file mode 100644
index 0000000..5465efe
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,33 @@
+The author of sg3_utils would like to thank the following people who
+have made contributions:
+
+Andries Brouwer <aebr@win.tue.nl> rewrite of isosize (original written
+	by Joerg Schilling). isosize is now found in the util-linux
+	package and in the archive directory of this package.
+
+Eric Schwartz <emschwar@debian.org> who wrote these man pages:
+	sg_readcap, sg_reset, sg_scan, sg_start, sg_test_rwbuf,
+	sg_turs and sginfo
+
+Eric Youngdale <eric@andante.org> author of scsi_info on which sginfo
+	is based.
+
+F. Jansen: additions to sg_scan
+
+Heiko Eissfeldt <heiko@colossus.escape.de> sg based example programs for
+	the original sg driver
+
+Kurt Garloff <garloff@suse.de> original sg_start and sg_test_rwbuf.
+	Additions to sginfo and sg_map.
+
+Martin Schwenke <martin@meltin.net> added the raw switch "-r" to sg_inq
+
+Peter Allworth <linsol@zeta.org.au> original dd clone design used by
+	sg3_utils's dd variants (e.g. sg_dd).
+
+Trent Piepho <xyzzy@speakeasy.org> print out some "sense key specific" data
+	and "-6" switch for sg_modes
+
+
+Doug Gilbert
+6th May 2003
diff --git a/Makefile b/Makefile
index 16531f1..af37d1d 100644
--- a/Makefile
+++ b/Makefile
@@ -7,27 +7,27 @@
 CC = gcc
 LD = gcc
 
-EXECS = sg_simple1 sg_simple2 sg_simple3 sg_dd sg_debug \
-	sg_scan scsi_inquiry sg_rbuf sginfo sg_readcap \
-	sgp_dd sg_map sg_turs sg_inq sg_test_rwbuf scsi_devfs_scan \
-	sg_start sgm_dd sg_simple4 sg_simple16 sg_read sg_reset \
-	sg_modes sg_logs
+EXECS = sg_dd sgp_dd sgm_dd sg_read sg_map sg_scan sg_rbuf \
+	sginfo sg_readcap sg_turs sg_inq sg_test_rwbuf \
+	scsi_devfs_scan sg_start sg_reset sg_modes sg_logs \
+	sg_senddiag
 
-COMMON = sg_scan scsi_inquiry sginfo sg_readcap sg_start
-
-MAN_PGS = sg_dd.8 sgp_dd.8 sgm_dd.8 sg_read.8 sg_map.8 sg_rbuf.8 
+MAN_PGS = sg_dd.8 sgp_dd.8 sgm_dd.8 sg_read.8 sg_map.8 sg_scan.8 sg_rbuf.8 \
+	sginfo.8 sg_readcap.8 sg_turs.8 sg_inq.8 sg_test_rwbuf.8 \
+	scsi_devfs_scan.8 sg_start.8 sg_reset.8 sg_modes.8 sg_logs.8 \
+	sg_senddiag.8
 MAN_PREF = man8
 
-CFLAGS = -g -O2 -Wall -D_REENTRANT
-# CFLAGS = -g -O2 -Wall -D_REENTRANT -DSG_KERNEL_INCLUDES
-# CFLAGS = -g -O2 -Wall -pedantic -D_REENTRANT
+LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+
+CFLAGS = -g -O2 -Wall -D_REENTRANT $(LARGE_FILE_FLAGS)
+# CFLAGS = -g -O2 -Wall -D_REENTRANT -DSG_KERNEL_INCLUDES $(LARGE_FILE_FLAGS)
+# CFLAGS = -g -O2 -Wall -pedantic -D_REENTRANT $(LARGE_FILE_FLAGS)
 
 LDFLAGS =
 
 all: $(EXECS)
 
-common: $(COMMON)
-
 depend dep:
 	for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
 	done > .depend
@@ -35,37 +35,16 @@
 clean:
 	/bin/rm -f *.o $(EXECS) core .depend
 
-sg_simple1: sg_simple1.o sg_err.o
-	$(LD) -o $@ $(LDFLAGS) $^
-
-sg_simple2: sg_simple2.o
-	$(LD) -o $@ $(LDFLAGS) $^
-
-sg_simple3: sg_simple3.o sg_err.o
-	$(LD) -o $@ $(LDFLAGS) $^
-
-sg_simple4: sg_simple4.o sg_err.o
-	$(LD) -o $@ $(LDFLAGS) $^
-
-sg_simple16: sg_simple16.o sg_err.o
-	$(LD) -o $@ $(LDFLAGS) $^
-
 sg_dd: sg_dd.o sg_err.o llseek.o
 	$(LD) -o $@ $(LDFLAGS) $^
 
-sg_debug: sg_debug.o
-	$(LD) -o $@ $(LDFLAGS) $^
-
 sg_scan: sg_scan.o sg_err.o
 	$(LD) -o $@ $(LDFLAGS) $^ 
 
-scsi_inquiry: scsi_inquiry.o
-	$(LD) -o $@ $(LDFLAGS) $^ 
-
 sginfo: sginfo.o sg_err.o
 	$(LD) -o $@ $(LDFLAGS) $^
 
-sg_start: sg_start.o
+sg_start: sg_start.o sg_err.o
 	$(LD) -o $@ $(LDFLAGS) $^ 
 
 sg_rbuf: sg_rbuf.o sg_err.o
@@ -83,7 +62,7 @@
 sg_map: sg_map.o sg_err.o
 	$(LD) -o $@ $(LDFLAGS) $^
 
-sg_turs: sg_turs.o
+sg_turs: sg_turs.o sg_err.o
 	$(LD) -o $@ $(LDFLAGS) $^
 
 sg_test_rwbuf: sg_test_rwbuf.o sg_err.o
@@ -107,11 +86,15 @@
 sg_logs: sg_logs.o sg_err.o
 	$(LD) -o $@ $(LDFLAGS) $^
 
-install: $(EXECS) $(COMMON)
+sg_senddiag: sg_senddiag.o sg_err.o
+	$(LD) -o $@ $(LDFLAGS) $^
+
+install: $(EXECS)
 	install -d $(INSTDIR)
 	for name in $^; \
 	 do install -s -o root -g root -m 755 $$name $(INSTDIR); \
 	done
+	install -d $(MANDIR)/$(MAN_PREF)
 	for mp in $(MAN_PGS); \
 	 do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
 	 gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
diff --git a/README b/README
index 53a444f..cd1f741 100644
--- a/README
+++ b/README
@@ -8,8 +8,8 @@
 The home site for the Linux sg device drivers is: http://www.torque.net/sg .
 Documentation for the driver can be found at:
 http://www.torque.net/sg/p/sg_v3_ho.html .
-This is written in DocBook and the original sgml (soon to become xml) can
-be found in the same directory with the ".sgml" extension. Postscript and
+This is written in DocBook and the original xml can be found in the 
+same directory with the ".xml" extension. Postscript and
 pdf renderings are also in that directory.
 Older documentation for the sg version 3 driver can be found at:
 http://www.torque.net/sg/p/scsi_generic_v3.txt .
@@ -36,6 +36,13 @@
 is targeted at "v2" and to a lesser extent "v1". The "sg_utils"
 package has essentially the same utilities.
 
+Some sg ioctls (noteably SG_IO) are defined for all block devices
+from lk 2.5.48 . This version of sg3_utils (1.02 and later) allows
+some utilities that use SG_IO to just work for block devices
+(e.g. sg_inq). It guards some others that formerly assumed that any
+device that answers one of sg's ioctls was automatically an sg device
+(e.g. sginfo).
+
 
 Utilities
 ---------
@@ -44,7 +51,7 @@
   1) dd variants: sg_dd, sgp_dd, sgm_dd and sgq_dd
   2) scanning and mapping: sg_scan, sg_map and scsi_devfs_scan
   3) SCSI support: sg_inq, scsi_inquiry, sginfo, sg_readcap, sg_start,
-     sg_modes, sg_logs, and sg_reset
+     sg_modes, sg_logs, sg_senddiag and sg_reset
   4) timing and testing: sg_rbuf, sg_test_rwbuf, sg_read, sg_turs,
      and sg_debug
   5) example programs: sg_simple1, sg_simple2, sg_simple3, sg_simple4
@@ -74,12 +81,13 @@
    dio=<n>       0 or 1, request direct IO (default 0)
    cdbsz=6|10|12|16   allow the command size of SCSI READ and WRITE commands
                       to be specified (default is 10)
-   gen=0|1       either "if" or "of" must be a sg or raw device when "gen=0"
-                 (default). Set to 1 to remove this restriction
    sync=0|1      when 1, do a SYNCHRONIZE CACHE on "of" after the transfer
    		 is complete
    fua=0|1|2|3   when 0, do not set 'force unit access' bit; when 1, set it
    		 on "of"; when 2, set it on "if"; when 3, set it on "if"+"of"
+   time=0|1      times the transfer and calculates a throughput figure when
+   		 1, output sent to stderr. Default is 0
+   coe=0|1       continue on error (only on sg devices) when 1. Default is 0.
 
 All numeric arguments can take multiplier suffixes:
   "c", "C"        * 1
@@ -104,6 +112,8 @@
 be found with a command like "fdisk -ul /dev/sda". The 'dio' argument
 requests direct IO (only functions in 2.4 kernels). A warning is issued 
 if direct IO is requested and /proc/scsi/sg/allow_dio == 0 . 
+"of=/dev/null" causes writes to be skipped, and "of=." is accepted as
+an alias for "of=/dev/null" (convenient shorthand).
 The "sg_dd" command has a "man" page [section 8].
 
 "sgp_dd" uses POSIX threads and attempts to run multiple IO operations
@@ -111,10 +121,8 @@
 1 worker (i.e. single threaded) through to 16 worker threads. This is
 done via the "thr=<n>" option (default 4). Copies from one sg device to
 another can be considerably faster due to this parallelism. There is
-also some speed benefit when raw devices are used. Other enhancements
-compared to "sg_dd" are a "gen=0 | 1" argument that allows general
-copying (i.e. doesn't need to involve sg or raw devices) and that 
-signals are caught. This command has a "man" page [section 8].
+also some speed benefit when raw devices are used. This command has a 
+"man" page [section 8].
 
 "sgm_dd" is very similar to sg_dd but it uses mmap-ed IO on one of its
 given sg device names. If both "if" and "of" are sg devices then mmap-ed
@@ -132,7 +140,8 @@
 "sg_scan" does a SCSI bus scan and prints the results to standard output.
 With no arguments only read permissions are needed on the sg devices
 but if "-i" is given (to do a SCSI Inquiry command on the device) then
-write permissions are also needed.
+write permissions are also needed. This command has a "man" page
+[section 8].
 
 "sg_map" shows the mapping between sg device names and those of the
 sd, sr and st device names. Some devices such as scanners have no
@@ -142,40 +151,52 @@
 "scsi_devfs_scan" is a utility for doing a directory scan on a system
 running devfs to identify SCSI (and optionally IDE) devices. Various
 information (including an INQUIRY) can be listed for each found device.
+ This command has a "man" page [section 8].
 
 
 3) SCSI support
 ---------------
 "sg_inq" is a utility for poking around the INQUIRY command which
-contains much interesting information. It is based on SCSI 3's SPC-1
-document and has additional fields defined in the draft SPC-2 document
-(revision 18 from May 2000). This command is applicable to SCSI 2 (and
-perhaps SCSI 1) devices as well.
+contains much interesting information. It is based on SCSI 3's SPC-2
+document. Has switches to output "command support" and "vital product
+data" pages. It can output in formatted ASCII, hex or binary. This 
+command is applicable to SCSI 2 (and perhaps SCSI 1) devices as well.
+The "sg_inq" command has a "man" page [section 8].
 
 The "scsi_inquiry" program shows the use of the SCSI_IOCTL_SEND_COMMAND
 ioctl to send a SCSI INQUIRY command. That ioctl() is supported by the 
 SCSI sub system mid level and so is common to all sd, sr, st and sg devices. 
+This program has been placed in the "examples" subdirectory.
 
 "sginfo" is a re-porting of the "scsiinfo" program by Eric Youngdale to
 use the sg devices (rather than the sd, sr or st block devices). This
 program outputs "mode sense" information. Amongst other things it outputs
 the full defect list of a disk (which was truncated at 4096 bytes in
-the original).
+the original). The "sginfo" command has a "man" page [section 8].
 
-"sg_readcap" call a READ CAPACITY command on the given device. The file
-descriptor may be any SCSI device (i.e. not just sg devices).
+"sg_readcap" call a READ CAPACITY command on the given device. Now also
+supports "partial medium indicator" (PMI) flag that indicates where the
+next significant delay will be on the media (after a given address).
+The "sg_readcap" command has a "man" page [section 8].
 
 "sg_start" has been provided by Kurt Garloff <garloff@suse.de> for spinning
-up (or down) disks. See README.sg_start .
+up (or down) disks. The "sg_start" command has a "man" page [section 8] and
+see the README.sg_start file.
 
 "sg_modes" and "sg_logs" print out mode sense pages and log sense pages 
-respectively.
+respectively.  The "sg_modes" and "sg_logs" commands have a "man" pages 
+[section 8].
+
+"sg_senddiag" permits various device self tests to be run. It can also
+list the diagnostic pages support by a device. The "sg_senddiag" command 
+has a "man" page [section 8].
 
 "sg_reset" exercises the SCSI device/bus/host reset capability. It is
 supported by the sg driver in lk 2.2.16 and beyond but associated
 SCSI middle level driver changes have not been accepted into the
 standard kernel at this time. Many distributions contain the patch to
-the mid-level that activates this feature.
+the mid-level that activates this feature. The "sg_reset" command has 
+a "man" page [section 8].
 
 
 4) Timing and testing
@@ -189,7 +210,7 @@
 "sg_test_rwbuf" is a program by Kurt Garloff <garloff@suse.de> that has
 the following description:  Program to test the SCSI host adapter by 
 issueing write and read operations on a device's buffer and calculating 
-checksums.
+checksums. The "sg_test_rwbuf" command has a "man" page [section 8].
 
 "sg_read" reads multiple blocks of data starting at the same logical
 address. It can time the transfers (potentially ignoring the first
@@ -202,9 +223,11 @@
 
 "sg_turs" executes a user specified number of TEST UNIT READY commands on
 the given device. This can be used to time SCSI command overhead.
+The "sg_turs" command has a "man" page [section 8].
 
 "sg_debug" is effectively defunct now. The user can instead do:
-$ cat /proc/scsi/sg/debug
+$ cat /proc/scsi/sg/debug . This command has been placed in the
+archive directory.
 
 
 5) Example programs
@@ -225,6 +248,9 @@
 drivers that indicate that they have 16 byte CDB capability (otherwise
 DID_ABORT will appear in the host_status).
 
+All these example programs (plus scsi_inquiry) have been placed in the
+"examples" subdirectory with their own Makefile.
+
 
 6) Miscellaneous
 ----------------
@@ -278,6 +304,15 @@
 The include file path issues are now all addressed in one file called
 "sg_include.h". Please read that file.
 
+scsirastools
+------------
+This package found at http://scsirastools.sourceforge.net contains utilities
+which overlap with the functionality offered in sg3_utils. Utilities of note
+in scsirastools are:
+  - sgdskfl: for loading firmware into SCSI disks
+  - sgmode: get and set mode pages
+  - sgdefects: list primary and grown defect lists
+  - sgdiag to perform format and other test functions
 
 Doug Gilbert
-17th March 2002
+13th May 2003
diff --git a/README.sg_start b/README.sg_start
index 76251cf..8097506 100644
--- a/README.sg_start
+++ b/README.sg_start
@@ -27,3 +27,11 @@
 
 Enjoy!
 					Kurt Garloff <garloff@suse.de>
+
+
+Postscript
+==========
+sg_start has been reworked to allow a block device (e.g. /dev/sda) in
+addition to the sg device name (e.g. /dev/sg0) from lk 2.5.50 onwards.
+sg_start now has more command line options, see its man page.
+	Doug Gilbert <dgilbert@interlog.com> 2003/4/30
diff --git a/archive/Makefile b/archive/Makefile
index 4c1b23e..a19f92e 100644
--- a/archive/Makefile
+++ b/archive/Makefile
@@ -6,7 +6,7 @@
 CC = gcc
 LD = gcc
 
-EXECS = sgq_dd sg_poll sg_bus_xfer sg_hold isosize
+EXECS = sgq_dd sg_poll sg_bus_xfer sg_hold sg_debug isosize
 
 COMMON = isosize
 
@@ -44,6 +44,9 @@
 isosize: isosize.o
 	$(LD) -o $@ $(LDFLAGS) $^
 
+sg_debug: sg_debug.o
+	$(LD) -o $@ $(LDFLAGS) $^
+
 install: $(EXECS) $(COMMON)
 	install -d $(INSTDIR)
 	for name in $^; \
diff --git a/archive/sg_simple_andre.c b/archive/sg_simple_andre.c
deleted file mode 100644
index d163022..0000000
--- a/archive/sg_simple_andre.c
+++ /dev/null
@@ -1,33 +0,0 @@
-#include <scsi/sg.h>
-
-
-#define READ10_REPLY_LEN 512
-#define READ10_CMD_LEN 10
-
-// read 0x102 blocks from block # 0x3040506
-//     [just to to you where the fields are]
-..............
-
-    unsigned char r10CmdBlk [READ10_CMD_LEN] =
-		{0x28, 0, 3, 4, 5, 6, 0, 1, 2, 0};
-    sg_io_hdr_t io_hdr;
-    unsigned char inBuff[READ10_REPLY_LEN];
-    unsigned char sense_buffer[32];
-
-    /* Prepare READ_10 command */
-    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
-    io_hdr.interface_id = 'S';
-    io_hdr.cmd_len = sizeof(r10CmdBlk);
-    io_hdr.mx_sb_len = sizeof(sense_buffer);
-    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
-    io_hdr.dxfer_len = READ10_REPLY_LEN;
-    io_hdr.dxferp = inBuff;
-    io_hdr.cmdp = r10CmdBlk;
-    io_hdr.sbp = sense_buffer;
-    io_hdr.timeout = 20000;     /* 20000 millisecs == 20 seconds */
-
-    if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
-        perror("READ_10 SG_IO ioctl error");
-        .....
-    }
-    // block should now be in 'inBuff'
diff --git a/archive/xsgp_dd.c b/archive/xsgp_dd.c
deleted file mode 100644
index 389ca59..0000000
--- a/archive/xsgp_dd.c
+++ /dev/null
@@ -1,1002 +0,0 @@
-#include <unistd.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#include <errno.h>
-#include <pthread.h>
-#include <signal.h>
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include "sg_include.h"
-#include "sg_err.h"
-
-/* A utility program for the Linux OS SCSI generic ("sg") device driver.
-*  Copyright (C) 1999 D. Gilbert and P. Allworth
-*  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)
-*  any later version.
-
-   This program is a specialization of the Unix "dd" command in which
-   one or both of the given files is a scsi generic device. A block size
-   ('bs') is assumed to be 512 if not given. This program complains if
-   'ibs' or 'obs' are given with some other value than 'bs'.
-   If 'if' is not given or 'if=-' then stdin is assumed. If 'of' is
-   not given or 'of=-' then stdout assumed. The appended multipliers
-   "c, b, k, m" for 1, 512, 1024 and 1048576 respectively are recognized
-   on numeric arguments.
-
-   A non-standard argument "bpt" (blocks per transfer) is added to control
-   the maximum number of blocks in each transfer. The default value is 128.
-   For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16KB
-   in this case) are transferred to or from the sg device in a single SCSI
-   command.
-
-   This version should compile with Linux sg drivers with version numbers
-   >= 30000 . This version uses posix threads.
-
-*/
-
-static char * version_str = "0.791 20000624";
-
-#define DEF_BLOCK_SIZE 512
-#define DEF_BLOCKS_PER_TRANSFER 128
-
-/* #define SG_DEBUG */
-
-#define SENSE_BUFF_LEN 32       /* Arbitrary, could be larger */
-#define DEF_TIMEOUT 8000        /* 8,000 millisecs == 8 seconds */
-#define S_RW_LEN 10             /* Use SCSI READ(10) and WRITE(10) */
-
-#define SGP_READ10 0x28
-#define SGP_WRITE10 0x2a
-#define DEF_NUM_THREADS 4
-#define MAX_NUM_THREADS SG_MAX_QUEUE
-
-
-typedef struct request_collection
-{       /* one instance visible to all threads */
-    int infd;
-    int skip;
-    int in_is_sg;
-    int in_scsi_type;
-    int in_blk;                 /* -\ next block address to read */
-    int in_count;               /*  | blocks remaining for next read */
-    int in_done_count;          /*  | count of completed in blocks */
-    int in_partial;             /*  | */
-    int in_stop;                /*  | */
-    pthread_mutex_t in_mutex;   /* -/ */
-    int outfd;
-    int seek;
-    int out_is_sg;
-    int out_scsi_type;
-    int out_blk;                /* -\ next block address to write */
-    int out_count;              /*  | blocks remaining for next write */
-    int out_done_count;         /*  | count of completed out blocks */
-    int out_partial;            /*  | */
-    int out_stop;               /*  | */
-    pthread_mutex_t out_mutex;  /*  | */
-    pthread_cond_t out_sync_cv; /* -/ hold writes until "in order" */
-    int bs;
-    int bpt;
-    int dio;
-    int dio_incomplete;         /* -\ */
-    int sum_of_resids;          /*  | */
-    pthread_mutex_t aux_mutex;  /* -/ (also serializes some printf()s */
-    int coe;
-    int timeout;
-    int debug;
-} Rq_coll;
-
-typedef struct request_element
-{       /* one instance per worker thread */
-    int infd;
-    int outfd;
-    int wr;
-    int blk;
-    int num_blks;
-    unsigned char * buffp;
-    unsigned char * alloc_bp;
-    sg_io_hdr_t io_hdr;
-    unsigned char cmd[S_RW_LEN];
-    unsigned char sb[SENSE_BUFF_LEN];
-    int bs;
-    int dio;
-    int dio_incomplete;
-    int resid;
-    int in_scsi_type;
-    int out_scsi_type;
-    int timeout;
-    int debug;
-} Rq_elem;
-
-static sigset_t signal_set;
-static pthread_t sig_listen_thread_id;
-
-void sg_in_operation(Rq_coll * clp, Rq_elem * rep);
-void sg_out_operation(Rq_coll * clp, Rq_elem * rep);
-int normal_in_operation(Rq_coll * clp, Rq_elem * rep, int blocks);
-void normal_out_operation(Rq_coll * clp, Rq_elem * rep, int blocks);
-int sg_start_io(Rq_elem * rep);
-int sg_finish_io(int wr, Rq_elem * rep, pthread_mutex_t * a_mutp);
-
-/* Following 2 macros from D.R. Butenhof's POSIX threads book:
-   ISBN 0-201-63392-2 . [Highly recommended book.] */
-#define err_exit(code,text) do { \
-    fprintf(stderr, "%s at \"%s\":%d: %s\n", \
-        text, __FILE__, __LINE__, strerror(code)); \
-    exit(1); \
-    } while (0)
-#define errno_exit(text) do { \
-    fprintf(stderr, "%s at \"%s\":%d: %s\n", \
-        text, __FILE__, __LINE__, strerror(errno)); \
-    exit(1); \
-    } while (0)
-
-
-void usage()
-{
-    fprintf(stderr, "Usage: "
-           "xsgp_dd  [if=<infile>] [skip=<n>] [of=<ofile>] [seek=<n>]\n"
-           "               [bs=<num>] [bpt=<num>] [count=<n>]\n"
-           "               [dio=<n>] [thr=<n>] [coe=<n>] [gen=<n>]\n"
-           "               [deb=<n>] [tmo=<n>] [--version]\n"
-           "            usually either 'if' or 'of' must be a sg device\n"
-           " 'bpt' is blocks_per_transfer (default is 128)\n"
-           " 'dio' is direct IO, 1->attempt, 0->indirect IO (def)\n"
-           " 'thr' is number of threads, must be > 0, default 4, max 16\n"
-           " 'coe' continue on sg error, 0->exit (def), 1->zero + continue\n"
-           " 'gen' 0-> 1 file is sg device(def), 1-> any files allowed\n"
-           " 'tmo' is timeout in millisecs for reads+writes (def 8000 ms)\n"
-           " 'deb' is debug, 0->none (def), > 0->varying degrees of debug\n");
-}
-
-/* Return of 0 -> success, -1 -> failure, 2 -> try again */
-int read_capacity(int sg_fd, int * num_sect, int * sect_sz)
-{
-    int res;
-    unsigned char rcCmdBlk [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-    unsigned char rcBuff[64];
-    unsigned char sense_b[64];
-    sg_io_hdr_t io_hdr;
-
-    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
-    io_hdr.interface_id = 'S';
-    io_hdr.cmd_len = sizeof(rcCmdBlk);
-    io_hdr.mx_sb_len = sizeof(sense_b);
-    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
-    io_hdr.dxfer_len = sizeof(rcBuff);
-    io_hdr.dxferp = rcBuff;
-    io_hdr.cmdp = rcCmdBlk;
-    io_hdr.sbp = sense_b;
-    io_hdr.timeout = DEF_TIMEOUT;
-
-    if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
-        perror("read_capacity (SG_IO) error");
-        return -1;
-    }
-    res = sg_err_category3(&io_hdr);
-    if (SG_ERR_CAT_MEDIA_CHANGED == res)
-        return 2; /* probably have another go ... */
-    else if (SG_ERR_CAT_CLEAN != res) {
-        sg_chk_n_print3("read capacity", &io_hdr);
-        return -1;
-    }
-    *num_sect = 1 + ((rcBuff[0] << 24) | (rcBuff[1] << 16) |
-                (rcBuff[2] << 8) | rcBuff[3]);
-    *sect_sz = (rcBuff[4] << 24) | (rcBuff[5] << 16) |
-               (rcBuff[6] << 8) | rcBuff[7];
-#ifdef SG_DEBUG
-    fprintf(stderr, "number of sectors=%d, sector size=%d\n",
-            *num_sect, *sect_sz);
-#endif
-    return 0;
-}
-
-void * sig_listen_thread(void * v_clp)
-{
-    Rq_coll * clp = (Rq_coll *)v_clp;
-    int sig_number;
-
-    while (1) {
-        sigwait(&signal_set, &sig_number);
-        if (SIGINT == sig_number) {
-            fprintf(stderr, "xsgp_dd interrupted by SIGINT\n");
-            pthread_mutex_lock(&clp->in_mutex);
-            clp->in_stop = 1;
-            pthread_mutex_unlock(&clp->in_mutex);
-            pthread_mutex_lock(&clp->out_mutex);
-            clp->out_stop = 1;
-            pthread_mutex_unlock(&clp->out_mutex);
-            pthread_cond_broadcast(&clp->out_sync_cv);
-        }
-    }
-    return NULL;
-}
-
-void cleanup_in(void * v_clp)
-{
-    Rq_coll * clp = (Rq_coll *)v_clp;
-
-    fprintf(stderr, "thread cancelled while in mutex held\n");
-    clp->in_stop = 1;
-    pthread_mutex_unlock(&clp->in_mutex);
-    pthread_mutex_lock(&clp->out_mutex);
-    clp->out_stop = 1;
-    pthread_mutex_unlock(&clp->out_mutex);
-    pthread_cond_broadcast(&clp->out_sync_cv);
-}
-
-void cleanup_out(void * v_clp)
-{
-    Rq_coll * clp = (Rq_coll *)v_clp;
-
-    fprintf(stderr, "thread cancelled while out mutex held\n");
-    clp->out_stop = 1;
-    pthread_mutex_unlock(&clp->out_mutex);
-    pthread_mutex_lock(&clp->in_mutex);
-    clp->in_stop = 1;
-    pthread_mutex_unlock(&clp->in_mutex);
-    pthread_cond_broadcast(&clp->out_sync_cv);
-}
-
-void * read_write_thread(void * v_clp)
-{
-    Rq_coll * clp = (Rq_coll *)v_clp;
-    Rq_elem rel;
-    Rq_elem * rep = &rel;
-    int off = 0;
-    int sz = clp->bpt * clp->bs;
-    int stop_after_write = 0;
-    int seek_skip =  clp->seek - clp->skip;
-    int blocks, status;
-
-    memset(rep, 0, sizeof(Rq_elem));
-    if (clp->dio) {     /* this makes dio work better, will disappear */
-        off = getpagesize();
-        sz += off;
-    }
-    if (NULL == (rep->alloc_bp = malloc(sz)))
-        err_exit(ENOMEM, "out of memory creating user buffers\n");
-    rep->buffp = rep->alloc_bp + off;
-    /* Follow clp members are constant during lifetime of thread */
-    rep->bs = clp->bs;
-    rep->dio = clp->dio;
-    rep->infd = clp->infd;
-    rep->outfd = clp->outfd;
-    rep->timeout = clp->timeout;
-    rep->debug = clp->debug;
-    rep->in_scsi_type = clp->in_scsi_type;
-    rep->out_scsi_type = clp->out_scsi_type;
-
-    while(1) {
-        status = pthread_mutex_lock(&clp->in_mutex);
-        if (0 != status) err_exit(status, "lock in_mutex");
-        if (clp->in_stop || (clp->in_count <= 0)) {
-            /* no more to do, exit loop then thread */
-            status = pthread_mutex_unlock(&clp->in_mutex);
-            if (0 != status) err_exit(status, "unlock in_mutex");
-            break;
-        }
-        blocks = (clp->in_count > clp->bpt) ? clp->bpt : clp->in_count;
-        rep->wr = 0;
-        rep->blk = clp->in_blk;
-        rep->num_blks = blocks;
-        clp->in_blk += blocks;
-        clp->in_count -= blocks;
-
-        pthread_cleanup_push(cleanup_in, (void *)clp);
-        if (clp->in_is_sg)
-            sg_in_operation(clp, rep); /* lets go of in_mutex mid operation */
-        else
-            stop_after_write = normal_in_operation(clp, rep, blocks);
-        pthread_cleanup_pop(0);
-
-        status = pthread_mutex_lock(&clp->out_mutex);
-        if (0 != status) err_exit(status, "lock out_mutex");
-        while ((! clp->out_stop) && ((rep->blk + seek_skip) != clp->out_blk)) {
-            /* if write would be out of sequence then wait */
-            pthread_cleanup_push(cleanup_out, (void *)clp);
-            status = pthread_cond_wait(&clp->out_sync_cv, &clp->out_mutex);
-            if (0 != status) err_exit(status, "cond out_sync_cv");
-            pthread_cleanup_pop(0);
-        }
-
-        if (clp->out_stop || (clp->out_count <= 0)) {
-            if (! clp->out_stop)
-                clp->out_stop = 1;
-            status = pthread_mutex_unlock(&clp->out_mutex);
-            if (0 != status) err_exit(status, "unlock out_mutex");
-            break;
-        }
-        if (stop_after_write)
-            clp->out_stop = 1;
-        rep->wr = 1;
-        rep->blk = clp->out_blk;
-        rep->num_blks = blocks;
-        clp->out_blk += blocks;
-        clp->out_count -= blocks;
-
-        pthread_cleanup_push(cleanup_out, (void *)clp);
-        if (clp->out_is_sg)
-            sg_out_operation(clp, rep); /* releases out_mutex mid operation */
-        else
-            normal_out_operation(clp, rep, blocks);
-        pthread_cleanup_pop(0);
-
-        if (stop_after_write)
-            break;
-        pthread_cond_broadcast(&clp->out_sync_cv);
-    } /* end of while loop */
-    if (rep->alloc_bp) free(rep->alloc_bp);
-    status = pthread_mutex_lock(&clp->in_mutex);
-    if (0 != status) err_exit(status, "lock in_mutex");
-    if (! clp->in_stop)
-        clp->in_stop = 1;  /* flag other workers to stop */
-    status = pthread_mutex_unlock(&clp->in_mutex);
-    if (0 != status) err_exit(status, "unlock in_mutex");
-    pthread_cond_broadcast(&clp->out_sync_cv);
-    return stop_after_write ? NULL : v_clp;
-}
-
-int normal_in_operation(Rq_coll * clp, Rq_elem * rep, int blocks)
-{
-    int res, status;
-    int stop_after_write = 0;
-    char ebuff[80];
-
-    /* enters holding in_mutex */
-    while (((res = read(clp->infd, rep->buffp,
-                        blocks * clp->bs)) < 0) && (EINTR == errno))
-        ;
-    if (res < 0) {
-        sprintf(ebuff, "xsgp_dd: reading, in_blk=%d ", rep->blk);
-        errno_exit(ebuff);
-    }
-    if (res < blocks * clp->bs) {
-        int o_blocks = blocks;
-        stop_after_write = 1;
-        blocks = res / clp->bs;
-        if ((res % clp->bs) > 0) {
-            blocks++;
-            clp->in_partial++;
-        }
-        /* Reverse out + re-apply blocks on clp */
-        clp->in_blk -= o_blocks;
-        clp->in_count += o_blocks;
-        rep->num_blks = blocks;
-        clp->in_blk += blocks;
-        clp->in_count -= blocks;
-    }
-    clp->in_done_count -= blocks;
-    status = pthread_mutex_unlock(&clp->in_mutex);
-    if (0 != status) err_exit(status, "unlock in_mutex");
-    return stop_after_write;
-}
-
-void normal_out_operation(Rq_coll * clp, Rq_elem * rep, int blocks)
-{
-    int res, status;
-    char ebuff[80];
-
-    /* enters holding out_mutex */
-    while (((res = write(clp->outfd, rep->buffp,
-                 rep->num_blks * clp->bs)) < 0) && (EINTR == errno))
-        ;
-    if (res < 0) {
-        sprintf(ebuff, "xsgp_dd: output, out_blk=%d ", rep->blk);
-        errno_exit(ebuff);
-    }
-    if (res < blocks * clp->bs) {
-        blocks = res / clp->bs;
-        if ((res % clp->bs) > 0) {
-            blocks++;
-            clp->out_partial++;
-        }
-        rep->num_blks = blocks;
-    }
-    clp->out_done_count -= blocks;
-    status = pthread_mutex_unlock(&clp->out_mutex);
-    if (0 != status) err_exit(status, "unlock out_mutex");
-}
-
-void sg_in_operation(Rq_coll * clp, Rq_elem * rep)
-{
-    int res;
-    int status;
-
-    /* enters holding in_mutex */
-    while (1) {
-        res = sg_start_io(rep);
-        if (1 == res)
-            err_exit(ENOMEM, "sg starting in command");
-        else if (res < 0) {
-            fprintf(stderr, "xsgp_dd inputting from sg failed, blk=%d\n",
-                    rep->blk);
-            errno_exit("sg starting in command 2");
-        }
-        /* Now release in mutex to let other reads run in parallel */
-        status = pthread_mutex_unlock(&clp->in_mutex);
-        if (0 != status) err_exit(status, "unlock in_mutex");
-
-        res = sg_finish_io(rep->wr, rep, &clp->aux_mutex);
-        if (res < 0) {
-            if (clp->coe) {
-                memset(rep->buffp, 0, rep->num_blks * rep->bs);
-                fprintf(stderr, ">> substituted zeros for in blk=%d for "
-                        "%d bytes\n", rep->blk, rep->num_blks * rep->bs);
-            }
-            else {
-                fprintf(stderr, "error finishing sg in command\n");
-                pthread_mutex_lock(&clp->in_mutex);
-                clp->in_stop = 1;
-                pthread_mutex_unlock(&clp->in_mutex);
-                pthread_mutex_lock(&clp->out_mutex);
-                clp->out_stop = 1;
-                pthread_mutex_unlock(&clp->out_mutex);
-                return;
-            }
-        }
-        if (res <= 0) { /* looks good, going to return */
-            if (rep->dio_incomplete || rep->resid) {
-                status = pthread_mutex_lock(&clp->aux_mutex);
-                if (0 != status) err_exit(status, "lock aux_mutex");
-                clp->dio_incomplete += rep->dio_incomplete;
-                clp->sum_of_resids += rep->resid;
-                status = pthread_mutex_unlock(&clp->aux_mutex);
-                if (0 != status) err_exit(status, "unlock aux_mutex");
-            }
-            status = pthread_mutex_lock(&clp->in_mutex);
-            if (0 != status) err_exit(status, "lock in_mutex");
-            clp->in_done_count -= rep->num_blks;
-            status = pthread_mutex_unlock(&clp->in_mutex);
-            if (0 != status) err_exit(status, "unlock in_mutex");
-            return;
-        }
-        /* else assume 1 == res so try again with same addr, count info */
-        /* now re-acquire read mutex for balance */
-        /* N.B. This re-read could now be out of read sequence */
-        status = pthread_mutex_lock(&clp->in_mutex);
-        if (0 != status) err_exit(status, "lock in_mutex");
-    }
-}
-
-void sg_out_operation(Rq_coll * clp, Rq_elem * rep)
-{
-    int res;
-    int status;
-
-    /* enters holding out_mutex */
-    while (1) {
-        res = sg_start_io(rep);
-        if (1 == res)
-            err_exit(ENOMEM, "sg starting out command");
-        else if (res < 0) {
-            fprintf(stderr, "xsgp_dd outputting from sg failed, blk=%d\n",
-                    rep->blk);
-            errno_exit("sg starting out command 2");
-        }
-        /* Now release in mutex to let other reads run in parallel */
-        status = pthread_mutex_unlock(&clp->out_mutex);
-        if (0 != status) err_exit(status, "unlock out_mutex");
-
-        res = sg_finish_io(rep->wr, rep, &clp->aux_mutex);
-        if (res < 0) {
-            if (clp->coe)
-                fprintf(stderr, ">> ignored error for out blk=%d for "
-                        "%d bytes\n", rep->blk, rep->num_blks * rep->bs);
-            else {
-                fprintf(stderr, "error finishing sg out command\n");
-                pthread_mutex_lock(&clp->in_mutex);
-                clp->in_stop = 1;
-                pthread_mutex_unlock(&clp->in_mutex);
-                pthread_mutex_lock(&clp->out_mutex);
-                clp->out_stop = 1;
-                pthread_mutex_unlock(&clp->out_mutex);
-                return;
-            }
-        }
-        if (res <= 0) {
-            if (rep->dio_incomplete || rep->resid) {
-                status = pthread_mutex_lock(&clp->aux_mutex);
-                if (0 != status) err_exit(status, "lock aux_mutex");
-                clp->dio_incomplete += rep->dio_incomplete;
-                clp->sum_of_resids += rep->resid;
-                status = pthread_mutex_unlock(&clp->aux_mutex);
-                if (0 != status) err_exit(status, "unlock aux_mutex");
-            }
-            status = pthread_mutex_lock(&clp->out_mutex);
-            if (0 != status) err_exit(status, "lock out_mutex");
-            clp->out_done_count -= rep->num_blks;
-            status = pthread_mutex_unlock(&clp->out_mutex);
-            if (0 != status) err_exit(status, "unlock out_mutex");
-            return;
-        }
-        /* else assume 1 == res so try again with same addr, count info */
-        /* now re-acquire out mutex for balance */
-        /* N.B. This re-write could now be out of write sequence */
-        status = pthread_mutex_lock(&clp->out_mutex);
-        if (0 != status) err_exit(status, "lock out_mutex");
-    }
-}
-
-int sg_start_io(Rq_elem * rep)
-{
-    sg_io_hdr_t * hp = &rep->io_hdr;
-    int res;
-
-    memset(rep->cmd, 0, sizeof(rep->cmd));
-    rep->cmd[0] = rep->wr ? SGP_WRITE10 : SGP_READ10;
-    rep->cmd[2] = (unsigned char)((rep->blk >> 24) & 0xFF);
-    rep->cmd[3] = (unsigned char)((rep->blk >> 16) & 0xFF);
-    rep->cmd[4] = (unsigned char)((rep->blk >> 8) & 0xFF);
-    rep->cmd[5] = (unsigned char)(rep->blk & 0xFF);
-    rep->cmd[7] = (unsigned char)((rep->num_blks >> 8) & 0xff);
-    rep->cmd[8] = (unsigned char)(rep->num_blks & 0xff);
-    memset(hp, 0, sizeof(sg_io_hdr_t));
-    hp->interface_id = 'S';
-    hp->cmd_len = sizeof(rep->cmd);
-    hp->cmdp = rep->cmd;
-    hp->dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
-    hp->dxfer_len = rep->bs * rep->num_blks;
-    hp->dxferp = rep->buffp;
-    hp->mx_sb_len = sizeof(rep->sb);
-    hp->sbp = rep->sb;
-    hp->timeout = rep->timeout;
-    hp->usr_ptr = rep;
-    hp->pack_id = rep->blk;
-    if (rep->dio)
-        hp->flags |= SG_FLAG_DIRECT_IO;
-    if (rep->debug > 8) {
-        fprintf(stderr, "sg_start_io: SCSI %s, blk=%d num_blks=%d\n",
-               rep->wr ? "WRITE" : "READ", rep->blk, rep->num_blks);
-        sg_print_command(hp->cmdp);
-        fprintf(stderr, "dir=%d, len=%d, dxfrp=%p, cmd_len=%d\n",
-                hp->dxfer_direction, hp->dxfer_len, hp->dxferp, hp->cmd_len);
-    }
-
-    while (((res = write(rep->wr ? rep->outfd : rep->infd, hp,
-                         sizeof(sg_io_hdr_t))) < 0) && (EINTR == errno))
-        ;
-    if (res < 0) {
-        if (ENOMEM == errno)
-            return 1;
-        return res;
-    }
-    return 0;
-}
-
-/* -1 -> unrecoverable error, 0 -> successful, 1 -> try again */
-int sg_finish_io(int wr, Rq_elem * rep, pthread_mutex_t * a_mutp)
-{
-    int res, status;
-    sg_io_hdr_t io_hdr;
-    sg_io_hdr_t * hp;
-#if 0
-    static int testing = 0;     /* thread dubious! */
-#endif
-
-    memset(&io_hdr, 0 , sizeof(sg_io_hdr_t));
-    /* FORCE_PACK_ID active set only read packet with matching pack_id */
-    io_hdr.interface_id = 'S';
-    io_hdr.dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
-    io_hdr.pack_id = rep->blk;
-
-    while (((res = read(wr ? rep->outfd : rep->infd, &io_hdr,
-                        sizeof(sg_io_hdr_t))) < 0) && (EINTR == errno))
-        ;
-    if (res < 0) {
-        perror("finishing io on sg device, error");
-        return -1;
-    }
-    if (rep != (Rq_elem *)io_hdr.usr_ptr)
-        err_exit(0, "sg_finish_io: bad usr_ptr, request-response mismatch\n");
-    memcpy(&rep->io_hdr, &io_hdr, sizeof(sg_io_hdr_t));
-    hp = &rep->io_hdr;
-
-    switch (sg_err_category3(hp)) {
-        case SG_ERR_CAT_CLEAN:
-            break;
-        case SG_ERR_CAT_RECOVERED:
-            fprintf(stderr, "Recovered error on block=%d, num=%d\n",
-                    rep->blk, rep->num_blks);
-            break;
-        case SG_ERR_CAT_MEDIA_CHANGED:
-            return 1;
-        default:
-            {
-                char ebuff[64];
-                sprintf(ebuff, "%s blk=%d", rep->wr ? "writing": "reading",
-                        rep->blk);
-                status = pthread_mutex_lock(a_mutp);
-                if (0 != status) err_exit(status, "lock aux_mutex");
-                sg_chk_n_print3(ebuff, hp);
-                status = pthread_mutex_unlock(a_mutp);
-                if (0 != status) err_exit(status, "unlock aux_mutex");
-                return -1;
-            }
-    }
-#if 0
-    if (0 == (++testing % 100)) return -1;
-#endif
-    if (rep->dio &&
-        ((hp->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
-        rep->dio_incomplete = 1; /* count dios done as indirect IO */
-    else
-        rep->dio_incomplete = 0;
-    rep->resid = hp->resid;
-    if (rep->debug > 8)
-        fprintf(stderr, "sg_finish_io: completed %s\n", wr ? "WRITE" : "READ");
-    return 0;
-}
-
-int sg_prepare(int fd, int bs, int bpt, int * scsi_typep)
-{
-    int res, t;
-
-    res = ioctl(fd, SG_GET_VERSION_NUM, &t);
-    if ((res < 0) || (t < 30000)) {
-        fprintf(stderr, "xsgp_dd: sg driver prior to 3.x.y\n");
-        return 1;
-    }
-    res = 0;
-    t = bs * bpt;
-    res = ioctl(fd, SG_SET_RESERVED_SIZE, &t);
-    if (res < 0)
-        perror("xsgp_dd: SG_SET_RESERVED_SIZE error");
-    t = 1;
-    res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t);
-    if (res < 0)
-        perror("xsgp_dd: SG_SET_FORCE_PACK_ID error");
-    if (scsi_typep) {
-        struct sg_scsi_id info;
-
-        res = ioctl(fd, SG_GET_SCSI_ID, &info);
-        if (res < 0)
-            perror("xsgp_dd: SG_SET_SCSI_ID error");
-        *scsi_typep = info.scsi_type;
-    }
-    return 0;
-}
-
-int get_num(char * buf)
-{
-    int res, num;
-    char c, cc;
-
-    res = sscanf(buf, "%d%c", &num, &c);
-    if (0 == res)
-        return -1;
-    else if (1 == res)
-        return num;
-    else {
-        cc = (char)toupper(c);
-        if ('B' == cc)
-            return num * 512;
-        else if ('C' == cc)
-            return num;
-        else if ('K' == cc)
-            return num * 1024;
-        else if ('M' == cc)
-            return num * 1024 * 1024;
-        else {
-            fprintf(stderr, "unrecognized multiplier\n");
-            return -1;
-        }
-    }
-}
-
-
-int main(int argc, char * argv[])
-{
-    int skip = 0;
-    int seek = 0;
-    int ibs = 0;
-    int obs = 0;
-    int count = -1;
-    char str[512];
-    char * key;
-    char * buf;
-    char inf[512];
-    char outf[512];
-    int res, k;
-    int in_num_sect = 0;
-    int out_num_sect = 0;
-    int num_threads = DEF_NUM_THREADS;
-    pthread_t threads[MAX_NUM_THREADS];
-    int gen = 0;
-    int in_sect_sz, out_sect_sz, status;
-    void * vp;
-    char ebuff[256];
-    Rq_coll rcoll;
-
-    memset(&rcoll, 0, sizeof(Rq_coll));
-    rcoll.bpt = DEF_BLOCKS_PER_TRANSFER;
-    rcoll.timeout = DEF_TIMEOUT;
-    inf[0] = '\0';
-    outf[0] = '\0';
-    if (argc < 2) {
-        usage();
-        return 1;
-    }
-
-    for(k = 1; k < argc; k++) {
-        if (argv[k])
-            strcpy(str, argv[k]);
-        else
-            continue;
-        for(key = str, buf = key; *buf && *buf != '=';)
-            buf++;
-        if (*buf)
-            *buf++ = '\0';
-        if (strcmp(key,"if") == 0)
-            strcpy(inf, buf);
-        else if (strcmp(key,"of") == 0)
-            strcpy(outf, buf);
-        else if (0 == strcmp(key,"ibs"))
-            ibs = get_num(buf);
-        else if (0 == strcmp(key,"obs"))
-            obs = get_num(buf);
-        else if (0 == strcmp(key,"bs"))
-            rcoll.bs = get_num(buf);
-        else if (0 == strcmp(key,"bpt"))
-            rcoll.bpt = get_num(buf);
-        else if (0 == strcmp(key,"skip"))
-            skip = get_num(buf);
-        else if (0 == strcmp(key,"seek"))
-            seek = get_num(buf);
-        else if (0 == strcmp(key,"count"))
-            count = get_num(buf);
-        else if (0 == strcmp(key,"dio"))
-            rcoll.dio = get_num(buf);
-        else if (0 == strcmp(key,"thr"))
-            num_threads = get_num(buf);
-        else if (0 == strcmp(key,"coe"))
-            rcoll.coe = get_num(buf);
-        else if (0 == strcmp(key,"gen"))
-            gen = get_num(buf);
-        else if (0 == strcmp(key,"tmo"))
-            rcoll.timeout = get_num(buf);
-        else if (0 == strncmp(key,"deb", 3))
-            rcoll.debug = get_num(buf);
-        else if (0 == strncmp(key, "--vers", 6)) {
-            printf("xsgp_dd for sg version 3 driver: %s\n", version_str);
-            return 0;
-        }
-        else {
-            fprintf(stderr, "Unrecognized argument '%s'\n", key);
-            usage();
-            return 1;
-        }
-    }
-    if (rcoll.bs <= 0) {
-        rcoll.bs = DEF_BLOCK_SIZE;
-        fprintf(stderr, "Assume default 'bs' (block size) of %d bytes\n",
-                rcoll.bs);
-    }
-    if ((ibs && (ibs != rcoll.bs)) || (obs && (obs != rcoll.bs))) {
-        fprintf(stderr, "If 'ibs' or 'obs' given must be same as 'bs'\n");
-        usage();
-        return 1;
-    }
-    if ((skip < 0) || (seek < 0)) {
-        fprintf(stderr, "skip and seek cannot be negative\n");
-        return 1;
-    }
-    if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) {
-        fprintf(stderr, "too few or too many threads requested\n");
-        usage();
-        return 1;
-    }
-    if (rcoll.debug)
-        fprintf(stderr, "xsgp_dd: if=%s skip=%d of=%s seek=%d count=%d\n",
-               inf, skip, outf, seek, count);
-    rcoll.infd = STDIN_FILENO;
-    rcoll.outfd = STDOUT_FILENO;
-    if (inf[0] && ('-' != inf[0])) {
-        if ((rcoll.infd = open(inf, O_RDONLY)) < 0) {
-            sprintf(ebuff, "xsgp_dd: could not open %s for reading", inf);
-            perror(ebuff);
-            return 1;
-        }
-        if (ioctl(rcoll.infd, SG_GET_TIMEOUT, 0) < 0) {
-            rcoll.in_is_sg = 0;
-            if (skip > 0) {
-                off_t offset = skip;
-
-                offset *= rcoll.bs;       /* could overflow here! */
-                if (lseek(rcoll.infd, offset, SEEK_SET) < 0) {
-                    sprintf(ebuff,
-                "xsgp_dd: couldn't skip to required position on %s", inf);
-                    perror(ebuff);
-                    return 1;
-                }
-            }
-        }
-        else { /* looks like sg device so close then re-open it RW */
-            close(rcoll.infd);
-            if ((rcoll.infd = open(inf, O_RDWR)) < 0) {
-                fprintf(stderr, "If %s is a sg device, need read+write "
-                        "permissions, even to read from it!\n", inf);
-                return 1;
-            }
-            rcoll.in_is_sg = 1;
-            if (sg_prepare(rcoll.infd, rcoll.bs, rcoll.bpt,
-                           &rcoll.in_scsi_type))
-                return 1;
-        }
-    }
-    if (outf[0] && ('-' != outf[0])) {
-        if ((rcoll.outfd = open(outf, O_RDWR)) >= 0) {
-            if (ioctl(rcoll.outfd, SG_GET_TIMEOUT, 0) < 0) {
-                /* not a scsi generic device so now try and open RDONLY */
-                close(rcoll.outfd);
-            }
-            else {
-                rcoll.out_is_sg = 1;
-                if (sg_prepare(rcoll.outfd, rcoll.bs, rcoll.bpt,
-                               &rcoll.out_scsi_type))
-                    return 1;
-            }
-        }
-        if (! rcoll.out_is_sg) {
-            if ((rcoll.outfd = open(outf, O_WRONLY | O_CREAT, 0666)) < 0) {
-                sprintf(ebuff,
-                        "xsgp_dd: could not open %s for writing", outf);
-                perror(ebuff);
-                return 1;
-            }
-            else if (seek > 0) {
-                off_t offset = seek;
-
-                offset *= rcoll.bs;       /* could overflow here! */
-                if (lseek(rcoll.outfd, offset, SEEK_SET) < 0) {
-                    sprintf(ebuff,
-                "xsgp_dd: couldn't seek to required position on %s", outf);
-                    perror(ebuff);
-                    return 1;
-                }
-            }
-        }
-    }
-    if ((STDIN_FILENO == rcoll.infd) && (STDOUT_FILENO == rcoll.outfd)) {
-        fprintf(stderr, "Disallow both if and of to be stdin and stdout");
-        return 1;
-    }
-    if (! (rcoll.in_is_sg || rcoll.out_is_sg || gen)) {
-        fprintf(stderr, "Either 'if' or 'of' must be a scsi generic device\n");
-        return 1;
-    }
-    if (0 == count)
-        return 0;
-    else if (count < 0) {
-        if (rcoll.in_is_sg) {
-            res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz);
-            if (2 == res) {
-                fprintf(stderr, "Unit attention, media changed(in), repeat\n");
-                res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz);
-            }
-            if (0 != res) {
-                fprintf(stderr, "Unable to read capacity on %s\n", inf);
-                in_num_sect = -1;
-            }
-            else {
-                if (in_num_sect > skip)
-                    in_num_sect -= skip;
-            }
-        }
-        if (rcoll.out_is_sg) {
-            res = read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz);
-            if (2 == res) {
-                fprintf(stderr, "Unit attention, media changed(out), repeat\n");
-                res = read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz);
-            }
-            if (0 != res) {
-                fprintf(stderr, "Unable to read capacity on %s\n", outf);
-                out_num_sect = -1;
-            }
-            else {
-                if (out_num_sect > seek)
-                    out_num_sect -= seek;
-            }
-        }
-        if (in_num_sect > 0) {
-            if (out_num_sect > 0)
-                count = (in_num_sect > out_num_sect) ? out_num_sect :
-                                                       in_num_sect;
-            else
-                count = in_num_sect;
-        }
-        else
-            count = out_num_sect;
-    }
-    if (rcoll.debug > 1)
-        fprintf(stderr, "Start of loop, count=%d, in_num_sect=%d, "
-                "out_num_sect=%d\n", count, in_num_sect, out_num_sect);
-
-    rcoll.in_count = count;
-    rcoll.in_done_count = count;
-    rcoll.skip = skip;
-    rcoll.in_blk = skip;
-    rcoll.out_count = count;
-    rcoll.out_done_count = count;
-    rcoll.seek = seek;
-    rcoll.out_blk = seek;
-    status = pthread_mutex_init(&rcoll.in_mutex, NULL);
-    if (0 != status) err_exit(status, "init in_mutex");
-    status = pthread_mutex_init(&rcoll.out_mutex, NULL);
-    if (0 != status) err_exit(status, "init out_mutex");
-    status = pthread_mutex_init(&rcoll.aux_mutex, NULL);
-    if (0 != status) err_exit(status, "init aux_mutex");
-    status = pthread_cond_init(&rcoll.out_sync_cv, NULL);
-    if (0 != status) err_exit(status, "init out_sync_cv");
-
-    sigemptyset(&signal_set);
-    sigaddset(&signal_set, SIGINT);
-    status = pthread_sigmask(SIG_BLOCK, &signal_set, NULL);
-    if (0 != status) err_exit(status, "pthread_sigmask");
-    status = pthread_create(&sig_listen_thread_id, NULL,
-                            sig_listen_thread, (void *)&rcoll);
-    if (0 != status) err_exit(status, "pthread_create, sig...");
-
-/* vvvvvvvvvvv  Start worker threads  vvvvvvvvvvvvvvvvvvvvvvvv */
-    if ((rcoll.out_done_count > 0) && (num_threads > 0)) {
-        /* Run 1 work thread to shake down infant retryable stuff */
-        status = pthread_create(&threads[0], NULL, read_write_thread,
-                                (void *)&rcoll);
-        if (0 != status) err_exit(status, "pthread_create");
-        if (rcoll.debug)
-            fprintf(stderr, "Starting worker thread k=0\n");
-
-        status = pthread_mutex_lock(&rcoll.out_mutex);
-        if (0 != status) err_exit(status, "lock out_mutex");
-        /* wait for any broadcast */
-        pthread_cleanup_push(cleanup_out, (void *)&rcoll);
-        status = pthread_cond_wait(&rcoll.out_sync_cv, &rcoll.out_mutex);
-        if (0 != status) err_exit(status, "cond out_sync_cv");
-        pthread_cleanup_pop(0);
-        status = pthread_mutex_unlock(&rcoll.out_mutex);
-        if (0 != status) err_exit(status, "unlock out_mutex");
-
-        /* now start the rest of the threads */
-        for (k = 1; k < num_threads; ++k) {
-            status = pthread_create(&threads[k], NULL, read_write_thread,
-                                    (void *)&rcoll);
-            if (0 != status) err_exit(status, "pthread_create");
-            if (rcoll.debug)
-                fprintf(stderr, "Starting worker thread k=%d\n", k);
-        }
-
-        /* now wait for worker threads to finish */
-        for (k = 0; k < num_threads; ++k) {
-            status = pthread_join(threads[k], &vp);
-            if (0 != status) err_exit(status, "pthread_join");
-            if (rcoll.debug)
-                fprintf(stderr, "Worker thread k=%d terminated\n", k);
-        }
-    }
-
-    status = pthread_cancel(sig_listen_thread_id);
-    if (0 != status) err_exit(status, "pthread_cancel");
-    if (STDIN_FILENO != rcoll.infd)
-        close(rcoll.infd);
-    if (STDOUT_FILENO != rcoll.outfd)
-        close(rcoll.outfd);
-    if (0 != rcoll.out_count)
-        fprintf(stderr, ">>>> Some error occurred, remaining blocks=%d\n",
-               rcoll.out_count);
-    fprintf(stderr, "%d+%d records in\n", count - rcoll.in_done_count,
-           rcoll.in_partial);
-    fprintf(stderr, "%d+%d records out\n", count - rcoll.out_done_count,
-           rcoll.out_partial);
-    if (rcoll.dio_incomplete)
-        fprintf(stderr, ">> Direct IO requested but incomplete %d times\n",
-               rcoll.dio_incomplete);
-    if (rcoll.sum_of_resids)
-        fprintf(stderr, ">> Non-zero sum of residual counts=%d\n",
-               rcoll.sum_of_resids);
-    return 0;
-}
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 0000000..5004377
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,73 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/man
+
+CC = gcc
+LD = gcc
+
+EXECS = sg_simple1 sg_simple2 sg_simple3 sg_simple4 sg_simple16 \
+	scsi_inquiry
+
+MAN_PGS = 
+MAN_PREF = man8
+
+LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+
+CFLAGS = -g -O2 -Wall -D_REENTRANT $(LARGE_FILE_FLAGS)
+# CFLAGS = -g -O2 -Wall -D_REENTRANT -DSG_KERNEL_INCLUDES $(LARGE_FILE_FLAGS)
+# CFLAGS = -g -O2 -Wall -pedantic -D_REENTRANT $(LARGE_FILE_FLAGS)
+
+LDFLAGS =
+
+all: $(EXECS)
+
+depend dep:
+	for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
+	done > .depend
+
+clean:
+	/bin/rm -f *.o $(EXECS) core .depend
+
+sg_simple1: sg_simple1.o ../sg_err.o
+	$(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple2: sg_simple2.o
+	$(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple3: sg_simple3.o ../sg_err.o
+	$(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple4: sg_simple4.o ../sg_err.o
+	$(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple16: sg_simple16.o ../sg_err.o
+	$(LD) -o $@ $(LDFLAGS) $^
+
+scsi_inquiry: scsi_inquiry.o
+	$(LD) -o $@ $(LDFLAGS) $^ 
+
+install: $(EXECS)
+	install -d $(INSTDIR)
+	for name in $^; \
+	 do install -s -o root -g root -m 755 $$name $(INSTDIR); \
+	done
+	install -d $(MANDIR)/$(MAN_PREF)
+	for mp in $(MAN_PGS); \
+	 do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
+	 gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
+	done
+
+uninstall:
+	dists="$(EXECS)"; \
+	for name in $$dists; do \
+	 rm -f $(INSTDIR)/$$name; \
+	done
+	for mp in $(MAN_PGS); do \
+	 rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+	done
+
+ifeq (.depend,$(wildcard .depend))
+include .depend
+endif
diff --git a/examples/sg_err.h b/examples/sg_err.h
new file mode 100644
index 0000000..317767b
--- /dev/null
+++ b/examples/sg_err.h
@@ -0,0 +1,162 @@
+#ifndef SG_ERR_H
+#define SG_ERR_H
+
+/* Feel free to copy and modify this GPL-ed code into your applications. */
+
+/* Version 0.89 (20030313) 
+*/
+
+
+/* Some of the following error/status codes are exchanged between the
+   various layers of the SCSI sub-system in Linux and should never
+   reach the user. They are placed here for completeness. What appears
+   here is copied from drivers/scsi/scsi.h which is not visible in
+   the user space. */
+
+#ifndef SCSI_CHECK_CONDITION
+/* Following are the "true" SCSI status codes. Linux has traditionally
+   used a 1 bit right and masked version of these. So now CHECK_CONDITION
+   and friends (in <scsi/scsi.h>) are deprecated. */
+#define SCSI_CHECK_CONDITION 0x2
+#define SCSI_CONDITION_MET 0x4
+#define SCSI_BUSY 0x8
+#define SCSI_IMMEDIATE 0x10
+#define SCSI_IMMEDIATE_CONDITION_MET 0x14
+#define SCSI_RESERVATION_CONFLICT 0x18
+#define SCSI_COMMAND_TERMINATED 0x22
+#define SCSI_TASK_SET_FULL 0x28
+#define SCSI_ACA_ACTIVE 0x30
+#define SCSI_TASK_ABORTED 0x40
+#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
+
+/* These defines are to isolate applictaions from kernel define changes */
+#define SG_ERR_DID_OK           DID_OK
+#define SG_ERR_DID_NO_CONNECT   DID_NO_CONNECT
+#define SG_ERR_DID_BUS_BUSY     DID_BUS_BUSY
+#define SG_ERR_DID_TIME_OUT     DID_TIME_OUT
+#define SG_ERR_DID_BAD_TARGET   DID_BAD_TARGET
+#define SG_ERR_DID_ABORT        DID_ABORT
+#define SG_ERR_DID_PARITY       DID_PARITY
+#define SG_ERR_DID_ERROR        DID_ERROR
+#define SG_ERR_DID_RESET        DID_RESET
+#define SG_ERR_DID_BAD_INTR     DID_BAD_INTR
+#define SG_ERR_DID_PASSTHROUGH  DID_PASSTHROUGH
+#define SG_ERR_DID_SOFT_ERROR   DID_SOFT_ERROR
+
+/* 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 */
+
+/* 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 applictaions from kernel define changes */
+#define SG_ERR_DRIVER_OK        DRIVER_OK
+#define SG_ERR_DRIVER_BUSY      DRIVER_BUSY
+#define SG_ERR_DRIVER_SOFT      DRIVER_SOFT
+#define SG_ERR_DRIVER_MEDIA     DRIVER_MEDIA
+#define SG_ERR_DRIVER_ERROR     DRIVER_ERROR
+#define SG_ERR_DRIVER_INVALID   DRIVER_INVALID
+#define SG_ERR_DRIVER_TIMEOUT   DRIVER_TIMEOUT
+#define SG_ERR_DRIVER_HARD      DRIVER_HARD
+#define SG_ERR_DRIVER_SENSE     DRIVER_SENSE
+#define SG_ERR_SUGGEST_RETRY    SUGGEST_RETRY
+#define SG_ERR_SUGGEST_ABORT    SUGGEST_ABORT
+#define SG_ERR_SUGGEST_REMAP    SUGGEST_REMAP
+#define SG_ERR_SUGGEST_DIE      SUGGEST_DIE
+#define SG_ERR_SUGGEST_SENSE    SUGGEST_SENSE
+#define SG_ERR_SUGGEST_IS_OK    SUGGEST_IS_OK
+#define SG_ERR_DRIVER_MASK      DRIVER_MASK
+#define SG_ERR_SUGGEST_MASK     SUGGEST_MASK
+
+
+
+/* The following "print" functions send ACSII to stdout */
+extern void sg_print_command(const unsigned char * command);
+extern void sg_print_sense(const char * leadin,
+                           const unsigned char * sense_buffer, int sb_len);
+extern void sg_print_status(int masked_status);
+extern void sg_print_scsi_status(int scsi_status);
+extern void sg_print_host_status(int host_status);
+extern void sg_print_driver_status(int driver_status);
+
+/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings
+   else it prints to standard output and returns 0. */
+extern 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);
+
+/* The following function declaration is for the sg version 3 driver. 
+   Only version 3 sg_err.c defines it. */
+struct sg_io_hdr;
+extern int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp);
+
+
+/* The following "category" function returns one of the following */
+#define SG_ERR_CAT_CLEAN 0      /* No errors or other information */
+#define SG_ERR_CAT_MEDIA_CHANGED 1 /* interpreted from sense buffer */
+#define SG_ERR_CAT_RESET 2      /* interpreted from sense buffer */
+#define SG_ERR_CAT_TIMEOUT 3
+#define SG_ERR_CAT_RECOVERED 4  /* Successful command after recovered err */
+#define SG_ERR_CAT_SENSE 98     /* Something else is in the sense buffer */
+#define SG_ERR_CAT_OTHER 99     /* Some other error/warning has occurred */
+
+extern int sg_err_category(int masked_status, int host_status,
+               int driver_status, const unsigned char * sense_buffer,
+               int sb_len);
+
+extern 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. 
+   Only version 3 sg_err.c defines it. */
+extern int sg_err_category3(struct sg_io_hdr * hp);
+
+/* Returns length of SCSI command given the opcode (first byte) */
+extern int sg_get_command_size(unsigned char opcode);
+
+extern void sg_get_command_name(unsigned char opcode, int buff_len, 
+				char * buff);
+
+#endif
diff --git a/examples/sg_include.h b/examples/sg_include.h
new file mode 100644
index 0000000..5baca3c
--- /dev/null
+++ b/examples/sg_include.h
@@ -0,0 +1,42 @@
+#ifdef SG_KERNEL_INCLUDES
+  #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
+
+/*
+  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
+*/
diff --git a/scsi_devfs_scan.8 b/scsi_devfs_scan.8
new file mode 100644
index 0000000..c371b24
--- /dev/null
+++ b/scsi_devfs_scan.8
@@ -0,0 +1,42 @@
+.TH SG_DEVFS_SCAN "8" "April 2003" "sg3_utils-1.03" SG3_UTILS
+.SH NAME
+sg_devfs_scan \- Scan SCSI devices within a devfs tree
+.SH SYNOPSIS
+.B sg_devfs_scan
+[\fI-d <dir>\fR] 
+[\fI-i\fR] 
+[\fI-ide\fR] 
+[\fI-l [-x]\fR]
+[\fI-q\fR]
+<\fIgeneric device\fR>
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+scsi_devfs_scan is a utility for doing a directory scan on a system
+running devfs to identify SCSI (and optionally IDE) devices. Various
+information (including an INQUIRY) can be listed for each found device.
+.TP
+-d <dir>
+location of devfs [default: /dev ]
+.TP
+-i
+show INQUIRY data for each SCSI device
+.TP
+-ide
+show scan of IDE devices after SCSI ones
+.TP
+-l
+show device file names in leaf directory
+.TP
+-x
+add (major,minor) information to '-l'
+.TP
+-q
+just output host, bus, target, lun numbers
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2001 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/scsi_devfs_scan.c b/scsi_devfs_scan.c
index ec11f38..20f273d 100644
--- a/scsi_devfs_scan.c
+++ b/scsi_devfs_scan.c
@@ -23,18 +23,19 @@
       This program scans the /dev directory structure looking for the
       devfs "primary" scsi (and optionally IDE) device names.
 
-   Version 0.12 20020228
+   Version 0.13 20030430
 */
 
 void usage()
 {
-    printf("Usage: 'scsi_devfs_scan [-ide] [-i] [-d <dir>] [-f] [-x]'\n");
-    printf("    where: -ide show scan of IDE devices after SCSI ones\n");
+    printf("Usage: 'scsi_devfs_scan [-d <dir>] [-i] [-ide] [-l [-x]] "
+	   "[-q]'\n");
+    printf("    where: -d <dir> location of devfs [default: /dev ]\n");
     printf("           -i   show INQUIRY data for each SCSI device\n");
-    printf("           -d <dir> location of devfs [default: /dev ]\n");
+    printf("           -ide show scan of IDE devices after SCSI devices\n");
     printf("           -l   show device file names in leaf directory\n");
-    printf("           -x   add (major,minor) information to '-l'\n");
     printf("           -q   just output host, bus, target, lun numbers\n");
+    printf("           -x   add (major,minor) information to '-l'\n");
 }
 
 #define NAME_LEN_MAX 256
diff --git a/sg3_utils.spec b/sg3_utils.spec
index 9d3d264..71c232f 100644
--- a/sg3_utils.spec
+++ b/sg3_utils.spec
@@ -6,20 +6,25 @@
 
 Summary: Utils for Linux's SCSI generic driver devices + raw devices
 Name: sg3_utils
-Version: 1.03
+Version: 1.04
 Release: 1
 Packager: dgilbert@interlog.com
 License: GPL
 Group: Utilities/System
-Source: ftp://www.torque.net/sg/p/sg3_utils-1.03.tgz
+Source: ftp://www.torque.net/sg/p/sg3_utils-1.04.tgz
 Url: http://www.torque.net/sg/u_index.html
 Provides: sg_utils
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root/
 
 %description
-Collection of small useful tools, that are based on the Linux SCSI generic
-(sg) interface and give info on the SCSI bus, copy data,... The utilities
-to copy data are based on the Unix dd command and are called sg_dd, sgp_dd
-and sgm_dd. These 3 commands can also act on raw devices (e.g. /dev/raw/raw1).
+Collection of tools for SCSI devices that use the Linux SCSI generic (sg)
+interface. Includes utilities to copy data based on "dd" syntax
+and semantics (called sg_dd, sgp_dd and sgm_dd); check INQUIRY data and
+associated pages (sg_inq); check mode and log pages (sg_modes and sg_logs);
+spin up and down disks (sg_start); do self tests (sg_senddiag); and various
+other functions. See the README and CHANGELOG files. Requires the lk 2.4
+series or better. [In the lk 2.5 development series many of these utilities
+can be used on the primary block device name (e.g. /dev/sda).]
 
 Warning: Some of these tools access the internals of your system and the
 incorrect usage of them may render your system inoperable.
@@ -38,27 +43,23 @@
 make
 
 %install
-make install INSTDIR=%{_bindir} MANDIR=%{_mandir}
+if [ "$RPM_BUILD_ROOT" != "/" ]; then
+        rm -rf $RPM_BUILD_ROOT
+fi
+make install INSTDIR=$RPM_BUILD_ROOT/usr/bin MANDIR=$RPM_BUILD_ROOT/usr/share/man
 
 %clean
 rm -rf $RPM_BUILD_ROOT
 
 %files
 %defattr(-,root,root)
-%attr(-,root,root) %doc README README.sg_start CHANGELOG INSTALL
+%attr(-,root,root) %doc CREDITS README README.sg_start CHANGELOG INSTALL
 %attr(755,root,root) %{_bindir}/sg_dd
-%attr(755,root,root) %{_bindir}/sg_debug
 %attr(755,root,root) %{_bindir}/sg_inq
 %attr(755,root,root) %{_bindir}/sg_scan
 %attr(755,root,root) %{_bindir}/sg_rbuf
 %attr(755,root,root) %{_bindir}/sginfo
-%attr(755,root,root) %{_bindir}/sg_simple1
-%attr(755,root,root) %{_bindir}/sg_simple2
-%attr(755,root,root) %{_bindir}/sg_simple3
-%attr(755,root,root) %{_bindir}/sg_simple4
-%attr(755,root,root) %{_bindir}/sg_simple16
 %attr(755,root,root) %{_bindir}/sg_readcap
-%attr(755,root,root) %{_bindir}/scsi_inquiry
 %attr(755,root,root) %{_bindir}/sgp_dd
 %attr(755,root,root) %{_bindir}/sg_map
 %attr(755,root,root) %{_bindir}/sg_turs
@@ -76,14 +77,25 @@
 %attr(-,root,root) %doc %{_mandir}/man8/sgm_dd.8.gz
 %attr(-,root,root) %doc %{_mandir}/man8/sg_read.8.gz
 %attr(-,root,root) %doc %{_mandir}/man8/sg_map.8.gz
+%attr(-,root,root) %doc %{_mandir}/man8/sg_scan.8.gz
 %attr(-,root,root) %doc %{_mandir}/man8/sg_rbuf.8.gz
+%attr(-,root,root) %doc %{_mandir}/man8/sginfo.8.gz
+%attr(-,root,root) %doc %{_mandir}/man8/sg_readcap.8.gz
+%attr(-,root,root) %doc %{_mandir}/man8/sg_turs.8.gz
 %attr(-,root,root) %doc %{_mandir}/man8/sg_inq.8.gz
+%attr(-,root,root) %doc %{_mandir}/man8/sg_test_rwbuf.8.gz
+%attr(-,root,root) %doc %{_mandir}/man8/scsi_devfs_scan.8.gz
+%attr(-,root,root) %doc %{_mandir}/man8/sg_start.8.gz
+%attr(-,root,root) %doc %{_mandir}/man8/sg_reset.8.gz
 %attr(-,root,root) %doc %{_mandir}/man8/sg_modes.8.gz
 %attr(-,root,root) %doc %{_mandir}/man8/sg_logs.8.gz
 %attr(-,root,root) %doc %{_mandir}/man8/sg_senddiag.8.gz
  
 
 %changelog
+* Tue May 13 2003 - dgilbert@interlog.com
+- default sg_turs '-n=' to 1, sg_logs gets '-t' for temperature, CREDITS
+  * sg3_utils-1.04
 * Wed Apr 02 2003 - dgilbert@interlog.com
 - 6 byte CDBs for sg_modes, sg_start on block devs, sg_senddiag, man pages
   * sg3_utils-1.03
diff --git a/sg_dd.8 b/sg_dd.8
index d32dace..2e0e56f 100644
--- a/sg_dd.8
+++ b/sg_dd.8
@@ -1,20 +1,41 @@
-.TH SG_DD "8" "March 2002" "sg3_utils-0.99" SG3_UTILS
+.TH SG_DD "8" "April 2003" "sg3_utils-1.04" SG3_UTILS
 .SH NAME
 sg_dd \- copies data to and from sg and raw devices
 .SH SYNOPSIS
 .B sg_dd
-[\fIOPTION\fR]...
+[\fIappend=0|1\fR] [\fIblk_sgio=0|1\fR] [\fIbpt=<n>\fR] [\fIbs=<n>\fR]
+[\fIcdbsz=6|10|12|16\fR] [\fIcoe=0|1\fR] [\fIcount=<n>\fR] [dio=0|1]
+[\fIfua=0|1|2|3\fR] [\fIibs=<n>\fR] [\fIif=<ifile>\fR] [\fIobs=<n>\fR]
+[\fIodir=0|1\fR] [\fIof=<ofile>\fR] [\fIseek=<n>\fR] [\fIskip=<n>\fR]
+[\fIsync=0|1\fR] [\fItime=0|1\fR] [\fI--version\fR]
 .SH DESCRIPTION
 .\" Add any additional description here
 .PP
-Copy data to and from Linux SCSI generic (sg) and raw devices.
+Copy data to and from Linux SCSI generic (sg) devices, raw devices and 
+those block devices that support the SG_IO ioctl (which are only found
+in the lk 2.5 series). Data may be copied to and from normal files
+as well.
 Similar syntax and semantics to 
 .B dd(1) 
 but does not perform any conversions.
 .TP
+append=0 | 1
+when set to 1 the output will be appended to the normal file given
+to the "of=<name>" argument. Appending only takes place to normal files:
+not pipes nor raw files nor sg devices. Error message produced if
+append=1 and seek=<n> where <n> > 0. Default is 0 which starts
+output at offset  of a normal file (subject to the "seek=" argument).
+.TP
+blk_sgio=0 | 1
+when set to 0 block devices (e.g. /dev/sda) are treated like
+normal files. When set to 1 the block device is assumed to accept the
+SG_IO ioctl; this is only appropriate for kernels later than lk 2.5.48 .
+Default is 0.
+.TP
 bpt=BLOCKS
 each IO transaction will be made using this number of blocks (or less if 
-near the end of count). Default is 128.
+near the end of count). Default is 128. So for an sg file and bs=512 each
+SCSI command conveys 64KB of data by default.
 .TP
 bs=BYTES
 this
@@ -29,6 +50,12 @@
 size of SCSI READ and/or WRITE commands issued on sg device names.
 Default is 10 byte SCSI command blocks
 .TP
+coe=0 | 1
+set to 1 for continue on error: if reading assume zeros read, if writing 
+then ignore and continue. Only applies to errors on sg devices (e.g. 
+errors on normal files will stop sg_dd). Error messages are still sent to
+stderr.  Default is 0 for do not continue on error
+.TP
 count=BLOCKS
 copy this number of blocks. Default is minimum number that sg devices
 return from READ CAPACITY (if that works) or 0
@@ -45,10 +72,6 @@
 6 byte SCSI READ and WRITE commands (cdbsz=6) do not support the fua bit.
 Only active for sg device file names
 .TP
-gen=0 | 1
-the default action (when "gen=0") is to require that either "if" or "of"
-is a sg or raw device name. To remove this restriction set "gen=1"
-.TP
 ibs=BYTES
 if given must be the same as bs
 .TP
@@ -58,8 +81,18 @@
 obs=BYTES
 if given must be the same as bs
 .TP
+odir=0 | 1
+when set to one opens block devices (e.g. /dev/sda) with the O_DIRECT
+flag. User memory buffers are aligned to the page size when set. The
+default is 0 (i.e. the O_DIRECT flag is not used). The blk_sgio flag
+takes precedence if it is also set. Has no effect on sg, normal or raw
+files.
+.TP
 of=FILE
-write to FILE instead of stdout. A file name of - is taken to be stdout
+write to FILE instead of stdout. A file name of - is taken to be stdout.
+If FILE is /dev/null then no actual writes are performed. If FILE is .
+(period) then it is treated the same way as /dev/null (this is a
+shorthand notation)
 .TP
 seek=BLOCKS
 skip BLOCKS bs-sized blocks at start of output
@@ -78,8 +111,6 @@
 --version
 outputs version number information and exits
 .PP
-Either the input file or the output file must be a sg or raw device,
-unless "gen=1".
 A raw device must be bound to a block device prior to using sg_dd.
 See
 .B raw(8)
@@ -146,7 +177,7 @@
    sg_dd if=/dev/sg0 of=/dev/null bs=512 count=2m time=1
 .PP
 On completion this will output a line like:
-"time to transfer data was 26.020794 secs, 41.26 MB/sec". The "MB/sec"
+"time to transfer data was 18.779506 secs, 57.18 MB/sec". The "MB/sec"
 in this case is 1,000,000 bytes per second.
 .SH NOTE
 For sg devices this command issues READ_10 and WRITE_10 SCSI commands which
@@ -164,14 +195,17 @@
 .SH "REPORTING BUGS"
 Report bugs to <dgilbert@interlog.com>.
 .SH COPYRIGHT
-Copyright \(co 2000-2002 Douglas Gilbert
+Copyright \(co 2000-2003 Douglas Gilbert
 .br
 This software is distributed under the GPL version 2. There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 .SH "SEE ALSO"
 A POSIX threads version of this command called
 .B sgp_dd
-is in the sg3_utils package. The lmbench package contains
+is in the sg3_utils package. Another version from that package is called
+.B sgm_dd
+and it uses memory mapped IO to speed transfers from sg devices.
+The lmbench package contains
 .B lmdd
 which is also interesting.
 .B raw(8), dd(1)
diff --git a/sg_dd.c b/sg_dd.c
index c10ad58..82ac1af 100644
--- a/sg_dd.c
+++ b/sg_dd.c
@@ -1,4 +1,5 @@
 #define _XOPEN_SOURCE 500
+#define _GNU_SOURCE
 
 #include <unistd.h>
 #include <fcntl.h>
@@ -14,13 +15,12 @@
 #include <sys/sysmacros.h>
 #include <sys/time.h> 
 #include <linux/major.h>
-typedef unsigned char u_char;	/* horrible, for scsi.h */
 #include "sg_include.h"
 #include "sg_err.h"
 #include "llseek.h"
 
 /* A utility program for the Linux OS SCSI generic ("sg") device driver.
-*  Copyright (C) 1999 - 2002 D. Gilbert and P. Allworth
+*  Copyright (C) 1999 - 2003 D. Gilbert and P. Allworth
 *  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)
@@ -47,7 +47,7 @@
    >= 30000 .
 */
 
-static char * version_str = "5.20 20020316";
+static char * version_str = "5.24 20030410";
 
 
 #define DEF_BLOCK_SIZE 512
@@ -61,15 +61,21 @@
 
 #define SENSE_BUFF_LEN 32       /* Arbitrary, could be larger */
 #define READ_CAP_REPLY_LEN 8
-#define DEF_TIMEOUT 40000       /* 40,000 millisecs == 40 seconds */
+#define DEF_TIMEOUT 60000       /* 60,000 millisecs == 60 seconds */
 
 #ifndef RAW_MAJOR
 #define RAW_MAJOR 255	/*unlikey value */
 #endif 
 
-#define FT_OTHER 0		/* filetype other than sg or raw device */
-#define FT_SG 1			/* filetype is sg char device */
-#define FT_RAW 2		/* filetype is raw char device */
+#define FT_OTHER 1		/* filetype is probably normal */
+#define FT_SG 2			/* filetype is sg char device or supports
+				   SG_IO ioctl */
+#define FT_RAW 4		/* filetype is raw char device */
+#define FT_DEV_NULL 8		/* either "/dev/null" or "." as filename */
+#define FT_ST 16		/* filetype is st char device (tape) */
+#define FT_BLOCK 32		/* filetype is block device */
+
+#define DEV_NULL_MINOR_NUM 3
 
 static int sum_of_resids = 0;
 
@@ -78,6 +84,7 @@
 static int in_partial = 0;
 static int out_full = 0;
 static int out_partial = 0;
+static int do_coe = 0;
 
 static const char * proc_allow_dio = "/proc/scsi/sg/allow_dio";
 
@@ -125,34 +132,48 @@
 int dd_filetype(const char * filename)
 {
     struct stat st;
+    size_t len = strlen(filename);
 
+    if ((1 == len) && ('.' == filename[0]))
+    	return FT_DEV_NULL;
     if (stat(filename, &st) < 0)
 	return FT_OTHER;
     if (S_ISCHR(st.st_mode)) {
+	if ((MEM_MAJOR == major(st.st_rdev)) && 
+	    (DEV_NULL_MINOR_NUM == minor(st.st_rdev)))
+	    return FT_DEV_NULL;
 	if (RAW_MAJOR == major(st.st_rdev))
 	    return FT_RAW;
-	else if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+	if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
 	    return FT_SG;
+	if (SCSI_TAPE_MAJOR == major(st.st_rdev))
+	    return FT_ST;
     }
+    else if (S_ISBLK(st.st_mode))
+	return FT_BLOCK;
     return FT_OTHER;
 }
 
 void usage()
 {
     fprintf(stderr, "Usage: "
-           "sg_dd  [if=<infile>] [skip=<n>] [of=<ofile>] [seek=<n>] "
-           "[bs=<num>]\n"
-           "              [bpt=<num>] [count=<n>] [time=0|1]"
-           " [dio=0|1] [sync=0|1]\n"
-	   "              [cdbsz=<6|10|12|16>] [fua=0|1|2|3] [gen=0|1]"
-	   " [--version]\n"
+           "sg_dd  [if=<infile>] [skip=<n>] [of=<ofile>] [seek=<n> | "
+           "append=0|1]\n"
+           "              [bs=<num>] [bpt=<num>] [count=<n>] [time=0|1]"
+           " [dio=0|1]\n"
+	   "              [sync=0|1] [cdbsz=6|10|12|16] [fua=0|1|2|3]"
+	   " [coe=0|1]\n"
+	   "              [odir=0|1] [blk_sgio=0|1] [--version]\n"
+           " 'append' 1->append output to normal <ofile>, (default is 0)\n"
            " 'bpt' is blocks_per_transfer (default is 128)\n"
            " 'dio' is direct IO, 1->attempt, 0->indirect IO (def)\n"
-           " 'gen' 0->either 'if' or 'of' must be sg or raw device(def)\n"
+	   " 'coe' 1->continue on sg error, 0->exit on error (def)\n"
            " 'time' 0->no timing(def), 1->time plus calculate throughput\n"
            " 'fua' force unit access: 0->don't(def), 1->of, 2->if, 3->of+if\n"
+           " 'odir' 1->use O_DIRECT when opening block dev, 0->don't(def)\n"
            " 'sync' 0->no sync(def), 1->SYNCHRONIZE CACHE on of after xfer\n"
-           " 'cdbsz' size of SCSI READ or WRITE command (default is 10)\n");
+           " 'cdbsz' size of SCSI READ or WRITE command (default is 10)\n"
+           " 'blk_sgio' 0->block device use normal I/O(def), 1->use SG_IO\n");
 }
 
 /* Return of 0 -> success, -1 -> failure, 2 -> try again */
@@ -324,7 +345,6 @@
     unsigned char rdCmd[MAX_SCSI_CDBSZ];
     unsigned char senseBuff[SENSE_BUFF_LEN];
     sg_io_hdr_t io_hdr;
-    int res;
 
     if (sg_build_scsi_cdb(rdCmd, cdbsz, blocks, from_block, 0, fua, 0)) {
     	fprintf(stderr, ME "bad rd cdb build, from_block=%d, blocks=%d\n",
@@ -346,21 +366,10 @@
     if (diop && *diop)
         io_hdr.flags |= SG_FLAG_DIRECT_IO;
 
-    while (((res = write(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
-           (EINTR == errno))
-        ;
-    if (res < 0) {
+    if (ioctl(sg_fd, SG_IO, &io_hdr)) {
         if (ENOMEM == errno)
             return 1;
-        perror("reading (wr) on sg device, error");
-        return -1;
-    }
-
-    while (((res = read(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
-           (EINTR == errno))
-        ;
-    if (res < 0) {
-        perror("reading (rd) on sg device, error");
+        perror("reading (SG_IO) on sg device, error");
         return -1;
     }
     switch (sg_err_category3(&io_hdr)) {
@@ -374,7 +383,14 @@
         return 2;
     default:
         sg_chk_n_print3("reading", &io_hdr);
-        return -1;
+	if (do_coe) {
+	    memset(buff, 0, bs * blocks);
+	    fprintf(stderr, ">> unable to read at blk=%d for "
+                        "%d bytes, use zeros\n", from_block, bs * blocks);
+	    return 0; /* fudge success */
+	}
+	else
+	    return -1;
     }
     if (diop && *diop && 
         ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
@@ -394,7 +410,6 @@
     unsigned char wrCmd[MAX_SCSI_CDBSZ];
     unsigned char senseBuff[SENSE_BUFF_LEN];
     sg_io_hdr_t io_hdr;
-    int res;
 
     if (sg_build_scsi_cdb(wrCmd, cdbsz, blocks, to_block, 1, fua, 0)) {
     	fprintf(stderr, ME "bad wr cdb build, to_block=%d, blocks=%d\n",
@@ -416,21 +431,10 @@
     if (diop && *diop)
         io_hdr.flags |= SG_FLAG_DIRECT_IO;
 
-    while (((res = write(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
-           (EINTR == errno))
-        ;
-    if (res < 0) {
+    if (ioctl(sg_fd, SG_IO, &io_hdr)) {
         if (ENOMEM == errno)
             return 1;
-        perror("writing (wr) on sg device, error");
-        return -1;
-    }
-
-    while (((res = read(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
-           (EINTR == errno))
-        ;
-    if (res < 0) {
-        perror("writing (rd) on sg device, error");
+        perror("writing (SG_IO) on sg device, error");
         return -1;
     }
     switch (sg_err_category3(&io_hdr)) {
@@ -444,7 +448,13 @@
         return 2;
     default:
         sg_chk_n_print3("writing", &io_hdr);
-        return -1;
+	if (do_coe) {
+	    fprintf(stderr, ">> ignored errors for out blk=%d for "
+		    "%d bytes\n", to_block, bs * blocks);
+	    return 0; /* fudge success */
+	}
+	else
+	    return -1;
     }
     if (diop && *diop && 
         ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
@@ -512,10 +522,12 @@
     int dio = 0;
     int dio_incomplete = 0;
     int do_time = 0;
-    int do_gen = 0;
+    int do_odir = 0;
     int scsi_cdbsz = DEF_SCSI_CDBSZ;
     int fua_mode = 0;
     int do_sync = 0;
+    int do_blk_sgio = 0;
+    int do_append = 0;
     int res, k, t, buf_sz, dio_tmp;
     int infd, outfd, blocks;
     unsigned char * wrkBuff;
@@ -566,16 +578,22 @@
             dd_count = get_num(buf);
         else if (0 == strcmp(key,"dio"))
             dio = get_num(buf);
+        else if (0 == strcmp(key,"coe"))
+            do_coe = get_num(buf);
         else if (0 == strcmp(key,"time"))
             do_time = get_num(buf);
-        else if (0 == strcmp(key,"gen"))
-            do_gen = get_num(buf);
         else if (0 == strcmp(key,"cdbsz"))
             scsi_cdbsz = get_num(buf);
         else if (0 == strcmp(key,"fua"))
             fua_mode = get_num(buf);
         else if (0 == strcmp(key,"sync"))
             do_sync = get_num(buf);
+        else if (0 == strcmp(key,"odir"))
+            do_odir = get_num(buf);
+        else if (0 == strcmp(key,"blk_sgio"))
+            do_blk_sgio = get_num(buf);
+        else if (0 == strncmp(key,"app", 3))
+            do_append = get_num(buf);
         else if (0 == strncmp(key, "--vers", 6)) {
             fprintf(stderr, ME "for Linux sg version 3 driver: %s\n",
                     version_str);
@@ -600,6 +618,10 @@
         fprintf(stderr, "skip and seek cannot be negative\n");
         return 1;
     }
+    if ((do_append > 0) && (seek > 0)) {
+        fprintf(stderr, "Can't use both append and seek switches\n");
+        return 1;
+    }
 #ifdef SG_DEBUG
     fprintf(stderr, ME "if=%s skip=%d of=%s seek=%d count=%d\n",
            inf, skip, outf, seek, dd_count);
@@ -614,7 +636,14 @@
     if (inf[0] && ('-' != inf[0])) {
 	in_type = dd_filetype(inf);
 
-	if (FT_SG == in_type) {
+	if ((FT_BLOCK & in_type) && do_blk_sgio)
+	    in_type |= FT_SG;
+
+	if (FT_ST == in_type) {
+	    fprintf(stderr, ME "unable to use scsi tape device %s\n", inf);
+	    return 1;
+	}
+	else if (FT_SG & in_type) {
 	    if ((infd = open(inf, O_RDWR)) < 0) {
                 snprintf(ebuff, EBUFF_SZ,
 			 ME "could not open %s for sg reading", inf);
@@ -627,12 +656,20 @@
                 perror(ME "SG_SET_RESERVED_SIZE error");
             res = ioctl(infd, SG_GET_VERSION_NUM, &t);
             if ((res < 0) || (t < 30000)) {
-                fprintf(stderr, ME "sg driver prior to 3.x.y\n");
+		if (FT_BLOCK & in_type)
+		    fprintf(stderr, ME "SG_IO unsupported on this block"
+				    " device\n");
+		else
+		    fprintf(stderr, ME "sg driver prior to 3.x.y\n");
                 return 1;
             }
         }
-        if (FT_SG != in_type) {
-            if ((infd = open(inf, O_RDONLY)) < 0) {
+        else {
+            if (do_odir && (FT_BLOCK == in_type))
+	        infd = open(inf, O_RDONLY | O_DIRECT);
+	    else
+	        infd = open(inf, O_RDONLY);
+            if (infd < 0) {
                 snprintf(ebuff, EBUFF_SZ,
 			 ME "could not open %s for reading", inf);
                 perror(ebuff);
@@ -655,7 +692,14 @@
     if (outf[0] && ('-' != outf[0])) {
 	out_type = dd_filetype(outf);
 
-	if (FT_SG == out_type) {
+	if ((FT_BLOCK & out_type) && do_blk_sgio)
+	    out_type |= FT_SG;
+
+	if (FT_ST == out_type) {
+	    fprintf(stderr, ME "unable to use scsi tape device %s\n", outf);
+	    return 1;
+	}
+	else if (FT_SG & out_type) {
 	    if ((outfd = open(outf, O_RDWR)) < 0) {
                 snprintf(ebuff, EBUFF_SZ,
 			 ME "could not open %s for sg writing", outf);
@@ -672,9 +716,17 @@
                 return 1;
             }
         }
+	else if (FT_DEV_NULL & out_type)
+	    outfd = -1;	/* don't bother opening */
 	else {
-	    if (FT_OTHER == out_type) {
-		if ((outfd = open(outf, O_WRONLY | O_CREAT, 0666)) < 0) {
+	    if (FT_RAW != out_type) {
+		int flags = O_WRONLY | O_CREAT;
+
+	        if (do_odir && (FT_BLOCK == out_type))
+		    flags |= O_DIRECT;
+		else if (do_append)
+		    flags |= O_APPEND;
+		if ((outfd = open(outf, flags, 0666)) < 0) {
 		    snprintf(ebuff, EBUFF_SZ,
 			    ME "could not open %s for writing", outf);
 		    perror(ebuff);
@@ -707,13 +759,9 @@
 		"Can't have both 'if' as stdin _and_ 'of' as stdout\n");
         return 1;
     }
-    if ((FT_OTHER == in_type) && (FT_OTHER == out_type) && !do_gen) {
-        fprintf(stderr, "Both 'if' and 'of' can't be ordinary files\n");
-        fprintf(stderr, "If you really want this set 'gen=1'\n");
-        return 1;
-    }
+
     if (dd_count < 0) {
-        if (FT_SG == in_type) {
+        if (FT_SG & in_type) {
             res = read_capacity(infd, &in_num_sect, &in_sect_sz);
             if (2 == res) {
                 fprintf(stderr, 
@@ -725,19 +773,11 @@
                 in_num_sect = -1;
             }
             else {
-#if 0
-                if (0 == in_sect_sz)
-                    in_sect_sz = bs;
-                else if (in_sect_sz > bs)
-                    in_num_sect *=  (in_sect_sz / bs);
-                else if (in_sect_sz < bs)
-                    in_num_sect /=  (bs / in_sect_sz);
-#endif
                 if (in_num_sect > skip)
                     in_num_sect -= skip;
             }
         }
-        if (FT_SG == out_type) {
+        if (FT_SG & out_type) {
             res = read_capacity(outfd, &out_num_sect, &out_sect_sz);
             if (2 == res) {
                 fprintf(stderr, 
@@ -773,7 +813,7 @@
         return 1;
     }
 
-    if (dio || (FT_RAW == in_type) || (FT_RAW == out_type)) {
+    if (dio || do_odir || (FT_RAW == in_type) || (FT_RAW == out_type)) {
 	size_t psz = getpagesize();
 	wrkBuff = malloc(bs * bpt + psz);
 	if (0 == wrkBuff) {
@@ -806,7 +846,7 @@
 
     while (dd_count > 0) {
         blocks = (dd_count > blocks_per) ? blocks_per : dd_count;
-        if (FT_SG == in_type) {
+        if (FT_SG & in_type) {
 	    int fua = fua_mode & 2;
 
             dio_tmp = dio;
@@ -860,7 +900,7 @@
             in_full += blocks;
         }
 
-        if (FT_SG == out_type) {
+        if (FT_SG & out_type) {
 	    int fua = fua_mode & 1;
 
             dio_tmp = dio;
@@ -894,6 +934,8 @@
                     dio_incomplete++;
             }
         }
+	else if (FT_DEV_NULL & out_type)
+	    out_full += blocks; /* act as if written out without error */
         else {
 	    while (((res = write(outfd, wrkPos, blocks * bs)) < 0)
 		   && (EINTR == errno))
@@ -941,7 +983,7 @@
             printf("\n");
     }
     if (do_sync) {
-	if (FT_SG == out_type) {
+	if (FT_SG & out_type) {
 	    fprintf(stderr, ">> Synchronizing cache on %s\n", outf);
             res = sync_cache(outfd);
             if (2 == res) {
@@ -956,7 +998,7 @@
     free(wrkBuff);
     if (STDIN_FILENO != infd)
         close(infd);
-    if (STDOUT_FILENO != outfd)
+    if ((STDOUT_FILENO != outfd) && (FT_DEV_NULL != out_type))
         close(outfd);
     res = 0;
     if (0 != dd_count) {
diff --git a/sg_err.c b/sg_err.c
index 416c2f7..dbb78de 100644
--- a/sg_err.c
+++ b/sg_err.c
@@ -1,5 +1,6 @@
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include "sg_include.h"
 #include "sg_err.h"
 
@@ -9,7 +10,7 @@
 *         Copyright (C) 1993, 1994, 1995 Eric Youngdale
 
 * The rest of this is:
-*  Copyright (C) 1999 - 2001 D. Gilbert
+*  Copyright (C) 1999 - 2003 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
@@ -19,15 +20,16 @@
 *  ASCII values for a number of symbolic constants, printing functions, etc.
 *
 *  Some of the tables have been updated for SCSI 2.
+*  Additions for SCSI 3+ (SPC-3 T10/1416-D Rev 07 3 May 2002)
 *
-*  Version 0.84 (20010115)
-*      Change output from stdout to stderr
+*  Version 0.89 (20030313)
+*      sense key specific field (bytes 15-17) decoding [Trent Piepho]
 */
 
 #define OUTP stderr
 
 static const unsigned char scsi_command_size[8] = { 6, 10, 10, 12,
-                                                   12, 12, 10, 10 };
+                                                   16, 12, 10, 10 };
 
 #define COMMAND_SIZE(opcode) scsi_command_size[((opcode) >> 5) & 7]
 
@@ -46,8 +48,8 @@
 
 
 static const char *group_1_commands[] = {
-/* 20-22 */  unknown, unknown, unknown,
-/* 23-28 */ unknown, "Define window parameters", "Read Capacity",
+/* 20-23 */  unknown, unknown, unknown, "Read Format capacities",
+/* 24-28 */ "Set window", "Read Capacity",
             unknown, unknown, "Read (10)",
 /* 29-2d */ "Read Generation", "Write (10)", "Seek (10)", "Erase",
             "Read updated block",
@@ -62,33 +64,54 @@
 static const char *group_2_commands[] = {
 /* 40-41 */ "Change Definition", "Write Same",
 /* 42-48 */ "Read sub-channel", "Read TOC", "Read header",
-            "Play audio (10)", unknown, "Play audio msf",
+            "Play audio (10)", "Get configuration", "Play audio msf",
             "Play audio track/index",
-/* 49-4f */ "Play track relative (10)", unknown, "Pause/resume",
-            "Log Select", "Log Sense", unknown, unknown,
-/* 50-55 */ unknown, unknown, unknown, unknown, unknown, "Mode Select (10)",
-/* 56-5b */ unknown, unknown, unknown, unknown, "Mode Sense (10)", unknown,
-/* 5c-5f */ unknown, unknown, unknown,
+/* 49-4f */ "Play track relative (10)", "Get event status notification",
+            "Pause/resume", "Log Select", "Log Sense", "Stop play/scan",
+            unknown,
+/* 50-55 */ "Xdwrite", "Xpwrite, Read disk info", "Xdread, Read track info",
+            "Reserve track", "Send OPC onfo", "Mode Select (10)",
+/* 56-5b */ "Reserve (10)", "Release (10)", "Repair track", "Read master cue",
+            "Mode Sense (10)", "Close track/session",
+/* 5c-5f */ "Read buffer capacity", "Send cue sheet", "Persistent reserve in",
+            "Persistent reserve out",
 };
 
+/* The following are 16 byte commands in group 4 */
+static const char *group_4_commands[] = {
+/* 80-84 */ "Xdwrite (16)", "Rebuild (16)", "Regenerate (16)", "Extended copy",
+            "Receive copy results",
+/* 85-89 */ "Memory Export In (16)", "Access control in", "Access control out",
+            "Read (16)", "Memory Export Out (16)",
+/* 8a-8f */ "Write (16)", unknown, "Read attributes", "Write attributes",
+            "Write and verify (16)", "Verify (16)",
+/* 90-94 */ "Pre-fetch (16)", "Synchronize cache (16)",
+            "Lock/unlock cache (16)", "Write same (16)", unknown,
+/* 95-99 */ unknown, unknown, unknown, unknown, unknown,
+/* 9a-9f */ unknown, unknown, unknown, unknown, "Service action in",
+            "Service action out",
+};
 
 /* The following are 12 byte commands in group 5 */
 static const char *group_5_commands[] = {
-/* a0-a5 */ unknown, unknown, unknown, unknown, unknown,
-            "Move medium/play audio(12)",
-/* a6-a9 */ "Exchange medium", unknown, "Read(12)", "Play track relative(12)",
-/* aa-ae */ "Write(12)", unknown, "Erase(12)", unknown,
-            "Write and verify(12)",
+/* a0-a5 */ "Report luns", "Blank", "Send event", "Maintenance (in)",
+            "Maintenance (out)", "Move medium/play audio(12)",
+/* a6-a9 */ "Exchange medium", "Move medium attached", "Read(12)",
+            "Play track relative(12)",
+/* aa-ae */ "Write(12)", unknown, "Erase(12), Get Performance",
+            "Read DVD structure", "Write and verify(12)",
 /* af-b1 */ "Verify(12)", "Search data high(12)", "Search data equal(12)",
-/* b2-b4 */ "Search data low(12)", "Set limits(12)", unknown,
-/* b5-b6 */ "Request volume element address", "Send volume tag",
-/* b7-b9 */ "Read defect data(12)", "Read element status", unknown,
-/* ba-bf */ unknown, unknown, unknown, unknown, unknown, unknown,
+/* b2-b4 */ "Search data low(12)", "Set limits(12)",
+            "Read element status attached",
+/* b5-b6 */ "Request volume element address", "Send volume tag, set streaming",
+/* b7-b9 */ "Read defect data(12)", "Read element status", "Read CD msf",
+/* ba-bc */ "Redundancy group (in), Scan",
+            "Redundancy group (out), Set cd-rom speed", "Spare (in), Play cd",
+/* bd-bf */ "Spare (out), Mechanism status", "Volume set (in), Read cd",
+            "Volume set (out), Send DVD structure",
 };
 
 
-
-
 #define group(opcode) (((opcode) >> 5) & 7)
 
 #define RESERVED_GROUP  0
@@ -96,7 +119,7 @@
 
 static const char **commands[] = {
     group_0_commands, group_1_commands, group_2_commands,
-    (const char **) RESERVED_GROUP, (const char **) RESERVED_GROUP,
+    (const char **) RESERVED_GROUP, group_4_commands,
     group_5_commands, (const char **) VENDOR_GROUP,
     (const char **) VENDOR_GROUP
 };
@@ -106,55 +129,96 @@
 
 static void print_opcode(int opcode) {
     const char **table = commands[ group(opcode) ];
+
     switch ((unsigned long) table) {
     case RESERVED_GROUP:
-        fprintf(OUTP, "%s(0x%02x) ", reserved, opcode);
+        fprintf(OUTP, "%s(0x%02x)", reserved, opcode);
         break;
     case VENDOR_GROUP:
-        fprintf(OUTP, "%s(0x%02x) ", vendor, opcode);
+        fprintf(OUTP, "%s(0x%02x)", vendor, opcode);
         break;
     default:
-        if (table[opcode & 0x1f] != unknown)
-            fprintf(OUTP, "%s ",table[opcode & 0x1f]);
-        else
-            fprintf(OUTP, "%s(0x%02x) ", unknown, opcode);
+	fprintf(OUTP, "%s",table[opcode & 0x1f]);
         break;
     }
 }
 
 void sg_print_command (const unsigned char * command) {
-    int i,s;
+    int k, s;
     print_opcode(command[0]);
-    for ( i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i)
-        fprintf(OUTP, "%02x ", command[i]);
-    fprintf(OUTP, "\n");
+    fprintf(OUTP, " [");
+    for (k = 0, s = COMMAND_SIZE(command[0]); k < s; ++k)
+        fprintf(OUTP, "%02x ", command[k]);
+    fprintf(OUTP, "]\n");
 }
 
-static const char * statuses[] = {
-/* 0-4 */ "Good", "Check Condition", "Condition Met", unknown, "Busy",
-/* 5-9 */ unknown, unknown, unknown, "Intermediate", unknown,
-/* a-c */ "Intermediate-Condition Met", unknown, "Reservation Conflict",
-/* d-10 */ unknown, unknown, unknown, unknown,
-/* 11-14 */ "Command Terminated", unknown, unknown, "Queue Full",
-/* 15-1a */ unknown, unknown, unknown,  unknown, unknown, unknown,
-/* 1b-1f */ unknown, unknown, unknown,  unknown, unknown,
-};
+void sg_print_status(int masked_status) 
+{
+    int scsi_status = (masked_status << 1) & 0x7e;
 
-void sg_print_status (int masked_status) {
-    /* status = (status >> 1) & 0xf; */ /* already done */
-    fprintf(OUTP, "%s ",statuses[masked_status]);
+    sg_print_scsi_status(scsi_status);
 }
 
-#define D 0x001  /* DIRECT ACCESS DEVICE (disk) */
-#define T 0x002  /* SEQUENTIAL ACCESS DEVICE (tape) */
-#define L 0x004  /* PRINTER DEVICE */
-#define P 0x008  /* PROCESSOR DEVICE */
-#define W 0x010  /* WRITE ONCE READ MULTIPLE DEVICE */
-#define R 0x020  /* READ ONLY (CD-ROM) DEVICE */
-#define S 0x040  /* SCANNER DEVICE */
-#define O 0x080  /* OPTICAL MEMORY DEVICE */
-#define M 0x100  /* MEDIA CHANGER DEVICE */
-#define C 0x200  /* COMMUNICATION DEVICE */
+void sg_print_scsi_status(int scsi_status) 
+{
+    const char * ccp;
+
+    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"; break;
+        case 0x14: ccp = "Intermediate-Condition Met"; 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: ccp = "Unknown status"; break;
+    }
+    fprintf(OUTP, "%s ", ccp);
+}
+
+/* In brackets is the related SCSI document (see www.t10.org) with the */
+/* peripheral device type after the colon */
+/* No programmatic use is made of these flags currently */
+#define D 0x0001  /* DIRECT ACCESS DEVICE (disk) [SBC-2: 0] */
+#define T 0x0002  /* SEQUENTIAL ACCESS DEVICE (tape) [SSC: 1] */
+#define L 0x0004  /* PRINTER DEVICE [SSC: 2] */
+#define P 0x0008  /* PROCESSOR DEVICE [SPC-2: 3] */
+#define W 0x0010  /* WRITE ONCE READ MULTIPLE DEVICE [SBC-2: 4] */
+#define R 0x0020  /* CD/DVD DEVICE [MMC-2: 5] */
+#define S 0x0040  /* SCANNER DEVICE [SCSI-2 (obsolete): 6] */
+#define O 0x0080  /* OPTICAL MEMORY DEVICE [SBC-2: 7] */
+#define M 0x0100  /* MEDIA CHANGER DEVICE [SMC-2: 8] */
+#define C 0x0200  /* COMMUNICATION DEVICE [SCSI-2 (obsolete): 9] */
+#define A 0x0400  /* ARRAY STORAGE [SCC-2: 12] */
+#define E 0x0800  /* ENCLOSURE SERVICES DEVICE [SES: 13] */
+#define B 0x1000  /* SIMPLIFIED DIRECT ACCESS DEVICE [RBC: 14] */
+#define K 0x2000  /* OPTICAL CARD READER/WRITER DEVICE [OCRW: 15] */
+
+#define SC_ALL_DEVS ( D|T|L|P|W|R|S|O|M|C|A|E|B|K )
+
+/* oft used strings are encoded using ASCII codes 0x1 to 0x1f . */
+/* This is to save space. This encoding should be UTF-8 and */
+/* UTF-16 friendly. */
+#define SC_AUDIO_PLAY_OPERATION "\x1"
+#define SC_LOGICAL_UNIT "\x2"
+#define SC_NOT_READY "\x3"
+#define SC_OPERATION "\x4"
+#define SC_IN_PROGRESS "\x5"
+#define SC_HARDWARE_IF "\x6"
+#define SC_CONTROLLER_IF "\x7"
+#define SC_DATA_CHANNEL_IF "\x8"
+#define SC_SERVO_IF "\x9"
+#define SC_SPINDLE_IF "\xa"
+#define SC_FIRMWARE_IF "\xb"
+#define SC_RECOVERED_DATA "\xc"
+#define SC_ERROR_RATE_TOO_HIGH "\xd"
+#define SC_TIMES_TOO_HIGH "\xe"
+
 
 struct error_info{
     unsigned char code1, code2;
@@ -179,135 +243,284 @@
 
 static struct error_info additional[] =
 {
+  {0x00,0x00,SC_ALL_DEVS,"No additional sense information"},
   {0x00,0x01,T,"Filemark detected"},
   {0x00,0x02,T|S,"End-of-partition/medium detected"},
   {0x00,0x03,T,"Setmark detected"},
   {0x00,0x04,T|S,"Beginning-of-partition/medium detected"},
-  {0x00,0x05,T|S,"End-of-data detected"},
-  {0x00,0x06,D|T|L|P|W|R|S|O|M|C,"I/O process terminated"},
-  {0x00,0x11,R,"Audio play operation in progress"},
-  {0x00,0x12,R,"Audio play operation paused"},
-  {0x00,0x13,R,"Audio play operation successfully completed"},
-  {0x00,0x14,R,"Audio play operation stopped due to error"},
+  {0x00,0x05,T|L|S,"End-of-data detected"},
+  {0x00,0x06,SC_ALL_DEVS,"I/O process terminated"},
+  {0x00,0x11,R,SC_AUDIO_PLAY_OPERATION SC_IN_PROGRESS},
+  {0x00,0x12,R,SC_AUDIO_PLAY_OPERATION "paused"},
+  {0x00,0x13,R,SC_AUDIO_PLAY_OPERATION "successfully completed"},
+  {0x00,0x14,R,SC_AUDIO_PLAY_OPERATION "stopped due to error"},
   {0x00,0x15,R,"No current audio status to return"},
-  {0x01,0x00,D|W|O,"No index/sector signal"},
-  {0x02,0x00,D|W|R|O|M,"No seek complete"},
-  {0x03,0x00,D|T|L|W|S|O,"Peripheral device write fault"},
+  {0x00,0x16,SC_ALL_DEVS,SC_OPERATION SC_IN_PROGRESS},
+  {0x00,0x17,D|T|L|W|R|S|O|M|A|E|B|K,"Cleaning requested"},
+  {0x00,0x18,T,"Erase" SC_OPERATION SC_IN_PROGRESS},
+  {0x00,0x19,T,"Locate" SC_OPERATION SC_IN_PROGRESS},
+  {0x00,0x1a,T,"Rewind" SC_OPERATION SC_IN_PROGRESS},
+  {0x00,0x1b,T,"Set capacity" SC_OPERATION SC_IN_PROGRESS},
+  {0x00,0x1c,T,"Verify" SC_OPERATION SC_IN_PROGRESS},
+  {0x01,0x00,D|W|O|B|K,"No index/sector signal"},
+  {0x02,0x00,D|W|R|O|M|B|K,"No seek complete"},
+  {0x03,0x00,D|T|L|W|S|O|B|K,"Peripheral device write fault"},
   {0x03,0x01,T,"No write current"},
   {0x03,0x02,T,"Excessive write errors"},
-  {0x04,0x00,D|T|L|P|W|R|S|O|M|C,
-     "Logical unit not ready, cause not reportable"},
-  {0x04,0x01,D|T|L|P|W|R|S|O|M|C,
-     "Logical unit is in process of becoming ready"},
-  {0x04,0x02,D|T|L|P|W|R|S|O|M|C,
-     "Logical unit not ready, initializing command required"},
-  {0x04,0x03,D|T|L|P|W|R|S|O|M|C,
-     "Logical unit not ready, manual intervention required"},
-  {0x04,0x04,D|T|L|O,"Logical unit not ready, format in progress"},
-  {0x05,0x00,D|T|L|W|R|S|O|M|C,"Logical unit does not respond to selection"},
-  {0x06,0x00,D|W|R|O|M,"No reference position found"},
-  {0x07,0x00,D|T|L|W|R|S|O|M,"Multiple peripheral devices selected"},
-  {0x08,0x00,D|T|L|W|R|S|O|M|C,"Logical unit communication failure"},
-  {0x08,0x01,D|T|L|W|R|S|O|M|C,"Logical unit communication time-out"},
-  {0x08,0x02,D|T|L|W|R|S|O|M|C,"Logical unit communication parity error"},
-  {0x09,0x00,D|T|W|R|O,"Track following error"},
-  {0x09,0x01,W|R|O,"Tracking servo failure"},
-  {0x09,0x02,W|R|O,"Focus servo failure"},
+  {0x04,0x00,SC_ALL_DEVS,SC_LOGICAL_UNIT SC_NOT_READY "cause not reportable"},
+  {0x04,0x01,SC_ALL_DEVS,SC_LOGICAL_UNIT "is" SC_IN_PROGRESS 
+  		"of becoming ready"},
+  {0x04,0x02,SC_ALL_DEVS,SC_LOGICAL_UNIT SC_NOT_READY 
+  		"initializing cmd. required"},
+  {0x04,0x03,SC_ALL_DEVS,SC_LOGICAL_UNIT SC_NOT_READY 
+  		"manual intervention required"},
+  {0x04,0x04,D|T|L|R|O|B,SC_LOGICAL_UNIT SC_NOT_READY "format" SC_IN_PROGRESS},
+  {0x04,0x05,D|T|W|O|M|C|A|B|K,SC_LOGICAL_UNIT SC_NOT_READY 
+  		"rebuild" SC_IN_PROGRESS},
+  {0x04,0x06,D|T|W|O|M|C|A|B|K,SC_LOGICAL_UNIT SC_NOT_READY 
+  		"recalculation" SC_IN_PROGRESS},
+  {0x04,0x07,SC_ALL_DEVS,SC_LOGICAL_UNIT SC_NOT_READY 
+  		SC_OPERATION SC_IN_PROGRESS},
+  {0x04,0x08,R,SC_LOGICAL_UNIT SC_NOT_READY "long write" SC_IN_PROGRESS},
+  {0x04,0x09,SC_ALL_DEVS,SC_LOGICAL_UNIT SC_NOT_READY "self-test" 
+		SC_IN_PROGRESS},
+  {0x04,0x0a,SC_ALL_DEVS,SC_LOGICAL_UNIT 
+  		"not accessible, asymmetric access state transition"},
+  {0x04,0x0b,SC_ALL_DEVS,SC_LOGICAL_UNIT 
+  		"not accessible, target port in standby state"},
+  {0x04,0x0c,SC_ALL_DEVS,SC_LOGICAL_UNIT 
+  		"not accessible, target port in unavailable state"},
+  {0x04,0x10,SC_ALL_DEVS,SC_LOGICAL_UNIT SC_NOT_READY
+  		"auxiliary memory not accessible"},
+  {0x05,0x00,D|T|L|W|R|S|O|M|C|A|E|B|K,SC_LOGICAL_UNIT 
+  		"does not respond to selection"},
+  {0x06,0x00,D|W|R|O|M|B|K,"No reference position found"},
+  {0x07,0x00,D|T|L|W|R|S|O|M|B|K,"Multiple peripheral devices selected"},
+  {0x08,0x00,D|T|L|W|R|S|O|M|C|A|E|B|K,SC_LOGICAL_UNIT "communication failure"},
+  {0x08,0x01,D|T|L|W|R|S|O|M|C|A|E|B|K,SC_LOGICAL_UNIT 
+  		"communication time-out"},
+  {0x08,0x02,D|T|L|W|R|S|O|M|C|A|E|B|K,SC_LOGICAL_UNIT 
+  		"communication parity error"},
+  {0x08,0x03,D|T|R|O|M|B|K,SC_LOGICAL_UNIT 
+  		"communication CRC error (Ultra-DMA/32)"},
+  {0x08,0x04,D|T|L|P|W|R|S|O|C|K,"Unreachable copy target"},
+  {0x09,0x00,D|T|W|R|O|B,"Track following error"},
+  {0x09,0x01,W|R|O|K,"Tracking servo failure"},
+  {0x09,0x02,W|R|O|K,"Focus servo failure"},
   {0x09,0x03,W|R|O,"Spindle servo failure"},
-  {0x0A,0x00,D|T|L|P|W|R|S|O|M|C,"Error log overflow"},
-  {0x0C,0x00,T|S,"Write error"},
-  {0x0C,0x01,D|W|O,"Write error recovered with auto reallocation"},
-  {0x0C,0x02,D|W|O,"Write error - auto reallocation failed"},
-  {0x10,0x00,D|W|O,"Id crc or ecc error"},
-  {0x11,0x00,D|T|W|R|S|O,"Unrecovered read error"},
-  {0x11,0x01,D|T|W|S|O,"Read retries exhausted"},
-  {0x11,0x02,D|T|W|S|O,"Error too long to correct"},
-  {0x11,0x03,D|T|W|S|O,"Multiple read errors"},
-  {0x11,0x04,D|W|O,"Unrecovered read error - auto reallocate failed"},
-  {0x11,0x05,W|R|O,"L-ec uncorrectable error"},
-  {0x11,0x06,W|R|O,"Circ unrecovered error"},
-  {0x11,0x07,W|O,"Data resynchronization error"},
+  {0x09,0x04,D|T|W|R|O|B,"Head select fault"},
+  {0x0A,0x00,SC_ALL_DEVS,"Error log overflow"},
+  {0x0B,0x00,SC_ALL_DEVS,"Warning"},
+  {0x0B,0x01,SC_ALL_DEVS,"Warning - specified temperature exceeded"},
+  {0x0B,0x02,SC_ALL_DEVS,"Warning - enclosure degraded"},
+  {0x0C,0x00,T|R|S,"Write error"},
+  {0x0C,0x01,K,"Write error - recovered with auto reallocation"},
+  {0x0C,0x02,D|W|O|B|K,"Write error - auto reallocation failed"},
+  {0x0C,0x03,D|W|O|B|K,"Write error - recommend reassignment"},
+  {0x0C,0x04,D|T|W|O|B,"Compression check miscompare error"},
+  {0x0C,0x05,D|T|W|O|B,"Data expansion occurred during compression"},
+  {0x0C,0x06,D|T|W|O|B,"Block not compressible"},
+  {0x0C,0x07,R,"Write error - recovery needed"},
+  {0x0C,0x08,R,"Write error - recovery failed"},
+  {0x0C,0x09,R,"Write error - loss of streaming"},
+  {0x0C,0x0A,R,"Write error - padding blocks added"},
+  {0x0C,0x0B,D|T|W|R|O|M|B,"Auxiliary memory write error"},
+  {0x0C,0x0C,SC_ALL_DEVS,"Write error - unexpected unsolicited data"},
+  {0x0C,0x0D,SC_ALL_DEVS,"Write error - not enough unsolicited data"},
+  {0x0D,0x00,D|T|L|P|W|R|S|O|C|A|K,
+		"Error detected by third party temporary initiator"},
+  {0x0D,0x01,D|T|L|P|W|R|S|O|C|A|K, "Third party device failure"},
+  {0x0D,0x02,D|T|L|P|W|R|S|O|C|A|K, "Copy target device not reachable"},
+  {0x0D,0x03,D|T|L|P|W|R|S|O|C|A|K, "Incorrect copy target device"},
+  {0x0D,0x04,D|T|L|P|W|R|S|O|C|A|K, "Copy target device underrun"},
+  {0x0D,0x05,D|T|L|P|W|R|S|O|C|A|K, "Copy target device overrun"},
+  {0x10,0x00,D|W|O|B|K,"Id CRC or ECC error"},
+  {0x11,0x00,D|T|W|R|S|O|B|K,"Unrecovered read error"},
+  {0x11,0x01,D|T|W|R|S|O|B|K,"Read retries exhausted"},
+  {0x11,0x02,D|T|W|R|S|O|B|K,"Error too long to correct"},
+  {0x11,0x03,D|T|W|S|O|B|K,"Multiple read errors"},
+  {0x11,0x04,D|W|O|B|K,"Unrecovered read error - auto reallocate failed"},
+  {0x11,0x05,W|R|O|B,"L-EC uncorrectable error"},
+  {0x11,0x06,W|R|O|B,"CIRC unrecovered error"},
+  {0x11,0x07,W|O|B,"Data re-synchronization error"},
   {0x11,0x08,T,"Incomplete block read"},
   {0x11,0x09,T,"No gap found"},
-  {0x11,0x0A,D|T|O,"Miscorrected error"},
-  {0x11,0x0B,D|W|O,"Unrecovered read error - recommend reassignment"},
-  {0x11,0x0C,D|W|O,"Unrecovered read error - recommend rewrite the data"},
-  {0x12,0x00,D|W|O,"Address mark not found for id field"},
-  {0x13,0x00,D|W|O,"Address mark not found for data field"},
-  {0x14,0x00,D|T|L|W|R|S|O,"Recorded entity not found"},
-  {0x14,0x01,D|T|W|R|O,"Record not found"},
+  {0x11,0x0A,D|T|O|B|K,"Miscorrected error"},
+  {0x11,0x0B,D|W|O|B|K,"Unrecovered read error - recommend reassignment"},
+  {0x11,0x0C,D|W|O|B|K,"Unrecovered read error - recommend rewrite the data"},
+  {0x11,0x0D,D|T|W|R|O|B,"De-compression CRC error"},
+  {0x11,0x0E,D|T|W|R|O|B,"Cannot decompress using declared algorithm"},
+  {0x11,0x0F,R,"Error reading UPC/EAN number"},
+  {0x11,0x10,R,"Error reading ISRC number"},
+  {0x11,0x11,R,"Read error - loss of streaming"},
+  {0x11,0x12,D|T|W|R|O|M|B,"Auxiliary memory read error"},
+  {0x11,0x13,SC_ALL_DEVS,"Read error - failed retransmission request"},
+  {0x12,0x00,D|W|O|B|K,"Address mark not found for id field"},
+  {0x13,0x00,D|W|O|B|K,"Address mark not found for data field"},
+  {0x14,0x00,D|T|L|W|R|S|O|B|K,"Recorded entity not found"},
+  {0x14,0x01,D|T|W|R|O|B|K,"Record not found"},
   {0x14,0x02,T,"Filemark or setmark not found"},
   {0x14,0x03,T,"End-of-data not found"},
   {0x14,0x04,T,"Block sequence error"},
-  {0x15,0x00,D|T|L|W|R|S|O|M,"Random positioning error"},
-  {0x15,0x01,D|T|L|W|R|S|O|M,"Mechanical positioning error"},
-  {0x15,0x02,D|T|W|R|O,"Positioning error detected by read of medium"},
-  {0x16,0x00,D|W|O,"Data synchronization mark error"},
-  {0x17,0x00,D|T|W|R|S|O,"Recovered data with no error correction applied"},
-  {0x17,0x01,D|T|W|R|S|O,"Recovered data with retries"},
-  {0x17,0x02,D|T|W|R|O,"Recovered data with positive head offset"},
-  {0x17,0x03,D|T|W|R|O,"Recovered data with negative head offset"},
-  {0x17,0x04,W|R|O,"Recovered data with retries and/or circ applied"},
-  {0x17,0x05,D|W|R|O,"Recovered data using previous sector id"},
-  {0x17,0x06,D|W|O,"Recovered data without ecc - data auto-reallocated"},
-  {0x17,0x07,D|W|O,"Recovered data without ecc - recommend reassignment"},
-  {0x18,0x00,D|T|W|R|O,"Recovered data with error correction applied"},
-  {0x18,0x01,D|W|R|O,"Recovered data with error correction and retries applied"},
-  {0x18,0x02,D|W|R|O,"Recovered data - data auto-reallocated"},
-  {0x18,0x03,R,"Recovered data with circ"},
-  {0x18,0x04,R,"Recovered data with lec"},
-  {0x18,0x05,D|W|R|O,"Recovered data - recommend reassignment"},
-  {0x19,0x00,D|O,"Defect list error"},
-  {0x19,0x01,D|O,"Defect list not available"},
-  {0x19,0x02,D|O,"Defect list error in primary list"},
-  {0x19,0x03,D|O,"Defect list error in grown list"},
-  {0x1A,0x00,D|T|L|P|W|R|S|O|M|C,"Parameter list length error"},
-  {0x1B,0x00,D|T|L|P|W|R|S|O|M|C,"Synchronous data transfer error"},
-  {0x1C,0x00,D|O,"Defect list not found"},
-  {0x1C,0x01,D|O,"Primary defect list not found"},
-  {0x1C,0x02,D|O,"Grown defect list not found"},
-  {0x1D,0x00,D|W|O,"Miscompare during verify operation"},
-  {0x1E,0x00,D|W|O,"Recovered id with ecc correction"},
-  {0x20,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid command operation code"},
-  {0x21,0x00,D|T|W|R|O|M,"Logical block address out of range"},
-  {0x21,0x01,M,"Invalid element address"},
-  {0x22,0x00,D,"Illegal function (should use 20 00, 24 00, or 26 00)"},
-  {0x24,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid field in cdb"},
-  {0x25,0x00,D|T|L|P|W|R|S|O|M|C,"Logical unit not supported"},
-  {0x26,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid field in parameter list"},
-  {0x26,0x01,D|T|L|P|W|R|S|O|M|C,"Parameter not supported"},
-  {0x26,0x02,D|T|L|P|W|R|S|O|M|C,"Parameter value invalid"},
-  {0x26,0x03,D|T|L|P|W|R|S|O|M|C,"Threshold parameters not supported"},
-  {0x27,0x00,D|T|W|O,"Write protected"},
-  {0x28,0x00,D|T|L|P|W|R|S|O|M|C,"Not ready to ready transition (medium may have changed)"},
-  {0x28,0x01,M,"Import or export element accessed"},
-  {0x29,0x00,D|T|L|P|W|R|S|O|M|C,"Power on, reset, or bus device reset occurred"},
-  {0x2A,0x00,D|T|L|W|R|S|O|M|C,"Parameters changed"},
-  {0x2A,0x01,D|T|L|W|R|S|O|M|C,"Mode parameters changed"},
-  {0x2A,0x02,D|T|L|W|R|S|O|M|C,"Log parameters changed"},
-  {0x2B,0x00,D|T|L|P|W|R|S|O|C,"Copy cannot execute since host cannot disconnect"},
-  {0x2C,0x00,D|T|L|P|W|R|S|O|M|C,"Command sequence error"},
+  {0x14,0x05,D|T|W|O|B|K,"Record not found - recommend reassignment"},
+  {0x14,0x06,D|T|W|O|B|K,"Record not found - data auto-reallocated"},
+  {0x14,0x07,T,"Locate" SC_OPERATION " failure"},
+  {0x15,0x00,D|T|L|W|R|S|O|M|B|K,"Random positioning error"},
+  {0x15,0x01,D|T|L|W|R|S|O|M|B|K,"Mechanical positioning error"},
+  {0x15,0x02,D|T|W|R|O|B|K,"Positioning error detected by read of medium"},
+  {0x16,0x00,D|W|O|B|K,"Data synchronization mark error"},
+  {0x16,0x01,D|W|O|B|K,"Data sync error - data rewritten"},
+  {0x16,0x02,D|W|O|B|K,"Data sync error - recommend rewrite"},
+  {0x16,0x03,D|W|O|B|K,"Data sync error - data auto-reallocated"},
+  {0x16,0x04,D|W|O|B|K,"Data sync error - recommend reassignment"},
+  {0x17,0x00,D|T|W|R|S|O|B|K,SC_RECOVERED_DATA 
+		"with no error correction applied"},
+  {0x17,0x01,D|T|W|R|S|O|B|K,SC_RECOVERED_DATA "with retries"},
+  {0x17,0x02,D|T|W|R|O|B|K,SC_RECOVERED_DATA "with positive head offset"},
+  {0x17,0x03,D|T|W|R|O|B|K,SC_RECOVERED_DATA "with negative head offset"},
+  {0x17,0x04,W|R|O|B,SC_RECOVERED_DATA "with retries and/or circ applied"},
+  {0x17,0x05,D|W|R|O|B|K,SC_RECOVERED_DATA "using previous sector id"},
+  {0x17,0x06,D|W|O|B|K,SC_RECOVERED_DATA "without ecc - data auto-reallocated"},
+  {0x17,0x07,D|W|R|O|B|K,SC_RECOVERED_DATA 
+		"without ecc - recommend reassignment"},
+  {0x17,0x08,D|W|R|O|B|K,SC_RECOVERED_DATA "without ecc - recommend rewrite"},
+  {0x17,0x09,D|W|R|O|B|K,SC_RECOVERED_DATA "without ecc - data rewritten"},
+  {0x18,0x00,D|T|W|R|O|B|K,SC_RECOVERED_DATA "with error correction applied"},
+  {0x18,0x01,D|W|R|O|B|K,SC_RECOVERED_DATA 
+		"with error corr. & retries applied"},
+  {0x18,0x02,D|W|R|O|B|K,SC_RECOVERED_DATA "- data auto-reallocated"},
+  {0x18,0x03,R,SC_RECOVERED_DATA "with CIRC"},
+  {0x18,0x04,R,SC_RECOVERED_DATA "with L-EC"},
+  {0x18,0x05,D|W|R|O|B|K,SC_RECOVERED_DATA "- recommend reassignment"},
+  {0x18,0x06,D|W|R|O|B|K,SC_RECOVERED_DATA "- recommend rewrite"},
+  {0x18,0x07,D|W|O|B|K,SC_RECOVERED_DATA "with ecc - data rewritten"},
+  {0x18,0x08,R,SC_RECOVERED_DATA "with linking"},
+  {0x19,0x00,D|O|K,"Defect list error"},
+  {0x19,0x01,D|O|K,"Defect list not available"},
+  {0x19,0x02,D|O|K,"Defect list error in primary list"},
+  {0x19,0x03,D|O|K,"Defect list error in grown list"},
+  {0x1A,0x00,SC_ALL_DEVS,"Parameter list length error"},
+  {0x1B,0x00,SC_ALL_DEVS,"Synchronous data transfer error"},
+  {0x1C,0x00,D|O|B|K,"Defect list not found"},
+  {0x1C,0x01,D|O|B|K,"Primary defect list not found"},
+  {0x1C,0x02,D|O|B|K,"Grown defect list not found"},
+  {0x1D,0x00,D|T|W|R|O|B|K,"Miscompare during verify" SC_OPERATION},
+  {0x1E,0x00,D|W|O|B|K,"Recovered id with ecc correction"},
+  {0x1F,0x00,D|O|K,"Partial defect list transfer"},
+  {0x20,0x00,SC_ALL_DEVS,"Invalid command" SC_OPERATION " code"},
+  {0x20,0x01,D|T|P|W|R|O|M|A|E|B|K,
+  	"Access denied - initiator pending-enrolled"},
+  {0x20,0x02,D|T|P|W|R|O|M|A|E|B|K,"Access denied - no access rights"},
+  {0x20,0x03,D|T|P|W|R|O|M|A|E|B|K,"Access denied - no mgmt id key"},
+  {0x20,0x04,T,"Illegal command while in write capable state"},
+  {0x20,0x05,T,"Obsolete"},
+  {0x20,0x06,T,"Illegal command while in explicit address mode"},
+  {0x20,0x07,T,"Illegal command while in implicit address mode"},
+  {0x20,0x08,D|T|P|W|R|O|M|A|E|B|K,"Access denied - enrollment conflict"},
+  {0x20,0x09,D|T|P|W|R|O|M|A|E|B|K,"Access denied - invalid LU identifier"},
+  {0x20,0x0A,D|T|P|W|R|O|M|A|E|B|K,"Access denied - invalid proxy token"},
+  {0x20,0x0B,D|T|P|W|R|O|M|A|E|B|K,"Access denied - ACL LUN conflict"},
+  {0x21,0x00,D|T|W|R|O|M|B|K,"Logical block address out of range"},
+  {0x21,0x01,D|T|W|R|O|M|B|K,"Invalid element address"},
+  {0x21,0x02,R,"Invalid address for write"},
+  {0x22,0x00,D,"Illegal function (use 20 00,24 00,or 26 00)"},
+  {0x24,0x00,SC_ALL_DEVS,"Invalid field in cdb"},
+  {0x24,0x01,SC_ALL_DEVS,"CDB decryption error"},
+  {0x25,0x00,SC_ALL_DEVS,SC_LOGICAL_UNIT "not supported"},
+  {0x26,0x00,SC_ALL_DEVS,"Invalid field in parameter list"},
+  {0x26,0x01,SC_ALL_DEVS,"Parameter not supported"},
+  {0x26,0x02,SC_ALL_DEVS,"Parameter value invalid"},
+  {0x26,0x03,D|T|L|P|W|R|S|O|M|C|A|E|K,"Threshold parameters not supported"},
+  {0x26,0x04,SC_ALL_DEVS,"Invalid release of persistent reservation"},
+  {0x26,0x05,D|T|L|P|W|R|S|O|M|C|A|B|K,"Data decryption error"},
+  {0x26,0x06,D|T|L|P|W|R|S|O|C|K,"Too many target descriptors"},
+  {0x26,0x07,D|T|L|P|W|R|S|O|C|K,"Unsupported target descriptor type code"},
+  {0x26,0x08,D|T|L|P|W|R|S|O|C|K,"Too many segment descriptors"},
+  {0x26,0x09,D|T|L|P|W|R|S|O|C|K,"Unsupported segment descriptor type code"},
+  {0x26,0x0A,D|T|L|P|W|R|S|O|C|K,"Unexpected inexact segment"},
+  {0x26,0x0B,D|T|L|P|W|R|S|O|C|K,"Inline data length exceeded"},
+  {0x26,0x0C,D|T|L|P|W|R|S|O|C|K,
+		"Invalid" SC_OPERATION " for copy source or destination"},
+  {0x26,0x0D,D|T|L|P|W|R|S|O|C|K,"Copy segment granularity violation"},
+  {0x27,0x00,D|T|W|R|O|B|K,"Write protected"},
+  {0x27,0x01,D|T|W|R|O|B|K,"Hardware write protected"},
+  {0x27,0x02,D|T|W|R|O|B|K,SC_LOGICAL_UNIT "software write protected"},
+  {0x27,0x03,T|R,"Associated write protect"},
+  {0x27,0x04,T|R,"Persistent write protect"},
+  {0x27,0x05,T|R,"Permanent write protect"},
+  {0x27,0x06,R,"Conditional write protect"},
+  {0x28,0x00,SC_ALL_DEVS,"Not ready to ready change, medium may have changed"},
+  {0x28,0x01,D|T|W|R|O|M|B,"Import or export element accessed"},
+  {0x29,0x00,SC_ALL_DEVS,"Power on,reset,or bus device reset occurred"},
+  {0x29,0x01,SC_ALL_DEVS,"Power on occurred"},
+  {0x29,0x02,SC_ALL_DEVS,"Scsi bus reset occurred"},
+  {0x29,0x03,SC_ALL_DEVS,"Bus device reset function occurred"},
+  {0x29,0x04,SC_ALL_DEVS,"Device internal reset"},
+  {0x29,0x05,SC_ALL_DEVS,"Transceiver mode changed to single-ended"},
+  {0x29,0x06,SC_ALL_DEVS,"Transceiver mode changed to lvd"},
+  {0x29,0x07,SC_ALL_DEVS,"I_T nexus loss occurred"},
+  {0x2A,0x00,D|T|L|W|R|S|O|M|C|A|E|B|K,"Parameters changed"},
+  {0x2A,0x01,D|T|L|W|R|S|O|M|C|A|E|B|K,"Mode parameters changed"},
+  {0x2A,0x02,D|T|L|W|R|S|O|M|C|A|E|K,"Log parameters changed"},
+  {0x2A,0x03,D|T|L|P|W|R|S|O|M|C|A|E|K,"Reservations preempted"},
+  {0x2A,0x04,D|T|L|P|W|R|S|O|M|C|A|E,"Reservations released"},
+  {0x2A,0x05,D|T|L|P|W|R|S|O|M|C|A|E,"Registrations preempted"},
+  {0x2A,0x06,SC_ALL_DEVS,"Asymmetric access state changed"},
+  {0x2A,0x07,SC_ALL_DEVS,"Implicit asymmetric access state transition failed"},
+  {0x2B,0x00,D|T|L|P|W|R|S|O|C|K,
+		"Copy cannot execute since host cannot disconnect"},
+  {0x2C,0x00,SC_ALL_DEVS,"Command sequence error"},
   {0x2C,0x01,S,"Too many windows specified"},
   {0x2C,0x02,S,"Invalid combination of windows specified"},
+  {0x2C,0x03,R,"Current program area is not empty"},
+  {0x2C,0x04,R,"Current program area is empty"},
+  {0x2C,0x05,B,"Illegal power condition request"},
+  {0x2C,0x06,R,"Persistent prevent conflict"},
+  {0x2C,0x07,SC_ALL_DEVS,"Previous busy status"},
+  {0x2C,0x08,SC_ALL_DEVS,"Previous task set full status"},
+  {0x2C,0x09,D|T|L|P|W|R|S|O|M|E|B|K,"Previous reservation conflict status"},
   {0x2D,0x00,T,"Overwrite error on update in place"},
-  {0x2F,0x00,D|T|L|P|W|R|S|O|M|C,"Commands cleared by another initiator"},
-  {0x30,0x00,D|T|W|R|O|M,"Incompatible medium installed"},
-  {0x30,0x01,D|T|W|R|O,"Cannot read medium - unknown format"},
-  {0x30,0x02,D|T|W|R|O,"Cannot read medium - incompatible format"},
-  {0x30,0x03,D|T,"Cleaning cartridge installed"},
-  {0x31,0x00,D|T|W|O,"Medium format corrupted"},
-  {0x31,0x01,D|L|O,"Format command failed"},
-  {0x32,0x00,D|W|O,"No defect spare location available"},
-  {0x32,0x01,D|W|O,"Defect list update failure"},
+  {0x2F,0x00,SC_ALL_DEVS,"Commands cleared by another initiator"},
+  {0x30,0x00,D|T|W|R|O|M|B|K,"Incompatible medium installed"},
+  {0x30,0x01,D|T|W|R|O|B|K,"Cannot read medium - unknown format"},
+  {0x30,0x02,D|T|W|R|O|B|K,"Cannot read medium - incompatible format"},
+  {0x30,0x03,D|T|R|K,"Cleaning cartridge installed"},
+  {0x30,0x04,D|T|W|R|O|B|K,"Cannot write medium - unknown format"},
+  {0x30,0x05,D|T|W|R|O|B|K,"Cannot write medium - incompatible format"},
+  {0x30,0x06,D|T|W|R|O|B,"Cannot format medium - incompatible medium"},
+  {0x30,0x07,D|T|L|W|R|S|O|M|A|E|B|K,"Cleaning failure"},
+  {0x30,0x08,R,"Cannot write - application code mismatch"},
+  {0x30,0x09,R,"Current session not fixated for append"},
+  {0x30,0x10,R,"Medium not formatted"}, /* should ascq be 0xa ?? */
+  {0x31,0x00,D|T|W|R|O|B|K,"Medium format corrupted"},
+  {0x31,0x01,D|L|R|O|B,"Format command failed"},
+  {0x31,0x02,R,"Zoned formatting failed due to spare linking"},
+  {0x32,0x00,D|W|O|B|K,"No defect spare location available"},
+  {0x32,0x01,D|W|O|B|K,"Defect list update failure"},
   {0x33,0x00,T,"Tape length error"},
-  {0x36,0x00,L,"Ribbon, ink, or toner failure"},
-  {0x37,0x00,D|T|L|W|R|S|O|M|C,"Rounded parameter"},
-  {0x39,0x00,D|T|L|W|R|S|O|M|C,"Saving parameters not supported"},
-  {0x3A,0x00,D|T|L|W|R|S|O|M,"Medium not present"},
+  {0x34,0x00,SC_ALL_DEVS,"Enclosure failure"},
+  {0x35,0x00,SC_ALL_DEVS,"Enclosure services failure"},
+  {0x35,0x01,SC_ALL_DEVS,"Unsupported enclosure function"},
+  {0x35,0x02,SC_ALL_DEVS,"Enclosure services unavailable"},
+  {0x35,0x03,SC_ALL_DEVS,"Enclosure services transfer failure"},
+  {0x35,0x04,SC_ALL_DEVS,"Enclosure services transfer refused"},
+  {0x36,0x00,L,"Ribbon,ink,or toner failure"},
+  {0x37,0x00,D|T|L|W|R|S|O|M|C|A|E|B|K,"Rounded parameter"},
+  {0x38,0x00,B,"Event status notification"},
+  {0x38,0x02,B,"Esn - power management class event"},
+  {0x38,0x04,B,"Esn - media class event"},
+  {0x38,0x06,B,"Esn - device busy class event"},
+  {0x39,0x00,D|T|L|W|R|S|O|M|C|A|E|K,"Saving parameters not supported"},
+  {0x3A,0x00,D|T|L|W|R|S|O|M|B|K,"Medium not present"},
+  {0x3A,0x01,D|T|W|R|O|M|B|K,"Medium not present - tray closed"},
+  {0x3A,0x02,D|T|W|R|O|M|B|K,"Medium not present - tray open"},
+  {0x3A,0x03,D|T|W|R|O|M|B,"Medium not present - loadable"},
+  {0x3A,0x04,D|T|W|R|O|M|B,
+		"Medium not present - medium auxiliary memory accessible"},
   {0x3B,0x00,T|L,"Sequential positioning error"},
   {0x3B,0x01,T,"Tape position error at beginning-of-medium"},
   {0x3B,0x02,T,"Tape position error at end-of-medium"},
-  {0x3B,0x03,L,"Tape or electronic vertical forms unit not ready"},
+  {0x3B,0x03,L,"Tape or electronic vertical forms unit " SC_NOT_READY},
   {0x3B,0x04,L,"Slew failure"},
   {0x3B,0x05,L,"Paper jam"},
   {0x3B,0x06,L,"Failed to sense top-of-form"},
@@ -316,62 +529,273 @@
   {0x3B,0x09,S,"Read past end of medium"},
   {0x3B,0x0A,S,"Read past beginning of medium"},
   {0x3B,0x0B,S,"Position past end of medium"},
-  {0x3B,0x0C,S,"Position past beginning of medium"},
-  {0x3B,0x0D,M,"Medium destination element full"},
-  {0x3B,0x0E,M,"Medium source element empty"},
-  {0x3D,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid bits in identify message"},
-  {0x3E,0x00,D|T|L|P|W|R|S|O|M|C,"Logical unit has not self-configured yet"},
-  {0x3F,0x00,D|T|L|P|W|R|S|O|M|C,"Target operating conditions have changed"},
-  {0x3F,0x01,D|T|L|P|W|R|S|O|M|C,"Microcode has been changed"},
-  {0x3F,0x02,D|T|L|P|W|R|S|O|M|C,"Changed operating definition"},
-  {0x3F,0x03,D|T|L|P|W|R|S|O|M|C,"Inquiry data has changed"},
-  {0x43,0x00,D|T|L|P|W|R|S|O|M|C,"Message error"},
-  {0x44,0x00,D|T|L|P|W|R|S|O|M|C,"Internal target failure"},
-  {0x45,0x00,D|T|L|P|W|R|S|O|M|C,"Select or reselect failure"},
-  {0x46,0x00,D|T|L|P|W|R|S|O|M|C,"Unsuccessful soft reset"},
-  {0x47,0x00,D|T|L|P|W|R|S|O|M|C,"Scsi parity error"},
-  {0x48,0x00,D|T|L|P|W|R|S|O|M|C,"Initiator detected error message received"},
-  {0x49,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid message error"},
-  {0x4A,0x00,D|T|L|P|W|R|S|O|M|C,"Command phase error"},
-  {0x4B,0x00,D|T|L|P|W|R|S|O|M|C,"Data phase error"},
-  {0x4C,0x00,D|T|L|P|W|R|S|O|M|C,"Logical unit failed self-configuration"},
-  {0x4E,0x00,D|T|L|P|W|R|S|O|M|C,"Overlapped commands attempted"},
+  {0x3B,0x0C,T|S,"Position past beginning of medium"},
+  {0x3B,0x0D,D|T|W|R|O|M|B|K,"Medium destination element full"},
+  {0x3B,0x0E,D|T|W|R|O|M|B|K,"Medium source element empty"},
+  {0x3B,0x0F,R,"End of medium reached"},
+  {0x3B,0x11,D|T|W|R|O|M|B|K,"Medium magazine not accessible"},
+  {0x3B,0x12,D|T|W|R|O|M|B|K,"Medium magazine removed"},
+  {0x3B,0x13,D|T|W|R|O|M|B|K,"Medium magazine inserted"},
+  {0x3B,0x14,D|T|W|R|O|M|B|K,"Medium magazine locked"},
+  {0x3B,0x15,D|T|W|R|O|M|B|K,"Medium magazine unlocked"},
+  {0x3B,0x16,R,"Mechanical positioning or changer error"},
+  {0x3D,0x00,D|T|L|P|W|R|S|O|M|C|A|E|K,"Invalid bits in identify message"},
+  {0x3E,0x00,SC_ALL_DEVS,SC_LOGICAL_UNIT "has not self-configured yet"},
+  {0x3E,0x01,SC_ALL_DEVS,SC_LOGICAL_UNIT "failure"},
+  {0x3E,0x02,SC_ALL_DEVS,"Timeout on logical unit"},
+  {0x3E,0x03,SC_ALL_DEVS,SC_LOGICAL_UNIT "failed self-test"},
+  {0x3E,0x04,SC_ALL_DEVS,SC_LOGICAL_UNIT "unable to update self-test log"},
+  {0x3F,0x00,SC_ALL_DEVS,"Target operating conditions have changed"},
+  {0x3F,0x01,SC_ALL_DEVS,"Microcode has been changed"},
+  {0x3F,0x02,D|T|L|P|W|R|S|O|M|C|B|K,"Changed operating definition"},
+  {0x3F,0x03,SC_ALL_DEVS,"Inquiry data has changed"},
+  {0x3F,0x04,D|T|W|R|O|M|C|A|E|B|K,"Component device attached"},
+  {0x3F,0x05,D|T|W|R|O|M|C|A|E|B|K,"Device identifier changed"},
+  {0x3F,0x06,D|T|W|R|O|M|C|A|E|B,"Redundancy group created or modified"},
+  {0x3F,0x07,D|T|W|R|O|M|C|A|E|B,"Redundancy group deleted"},
+  {0x3F,0x08,D|T|W|R|O|M|C|A|E|B,"Spare created or modified"},
+  {0x3F,0x09,D|T|W|R|O|M|C|A|E|B,"Spare deleted"},
+  {0x3F,0x0A,D|T|W|R|O|M|C|A|E|B|K,"Volume set created or modified"},
+  {0x3F,0x0B,D|T|W|R|O|M|C|A|E|B|K,"Volume set deleted"},
+  {0x3F,0x0C,D|T|W|R|O|M|C|A|E|B|K,"Volume set deassigned"},
+  {0x3F,0x0D,D|T|W|R|O|M|C|A|E|B|K,"Volume set reassigned"},
+  {0x3F,0x0E,D|T|L|P|W|R|S|O|M|C|A|E,"Reported luns data has changed"},
+  {0x3F,0x10,D|T|W|R|O|M|B,"Medium loadable"},
+  {0x3F,0x11,D|T|W|R|O|M|B,"Medium auxiliary memory accessible"},
+  {0x40,0x00,D,"Ram failure (should use 40 nn)"},
+  /*
+   * FIXME(eric) - need a way to represent wildcards here.
+   */
+  {0x40,0x00,SC_ALL_DEVS,"Diagnostic failure on component nn (80h-ffh)"},
+  {0x41,0x00,D,"Data path failure (should use 40 nn)"},
+  {0x42,0x00,D,"Power-on or self-test failure (should use 40 nn)"},
+  {0x43,0x00,SC_ALL_DEVS,"Message error"},
+  {0x44,0x00,SC_ALL_DEVS,"Internal target failure"},
+  {0x45,0x00,SC_ALL_DEVS,"Select or reselect failure"},
+  {0x46,0x00,D|T|L|P|W|R|S|O|M|C|B|K,"Unsuccessful soft reset"},
+  {0x47,0x00,SC_ALL_DEVS,"Scsi parity error"},
+  {0x47,0x01,SC_ALL_DEVS,"Data phase CRC error detected"},
+  {0x47,0x02,SC_ALL_DEVS,"Scsi parity error detected during st data phase"},
+  {0x47,0x03,SC_ALL_DEVS,"Information unit CRC error detected"},
+  {0x47,0x04,SC_ALL_DEVS,"Asynchronous information protection error detected"},
+  {0x47,0x05,SC_ALL_DEVS,"Protocol service CRC error"},
+  {0x48,0x00,SC_ALL_DEVS,"Initiator detected error message received"},
+  {0x49,0x00,SC_ALL_DEVS,"Invalid message error"},
+  {0x4A,0x00,SC_ALL_DEVS,"Command phase error"},
+  {0x4B,0x00,SC_ALL_DEVS,"Data phase error"},
+  {0x4C,0x00,SC_ALL_DEVS,SC_LOGICAL_UNIT "failed self-configuration"},
+  /*
+   * FIXME(eric) - need a way to represent wildcards here.
+   */
+  {0x4D,0x00,SC_ALL_DEVS,"Tagged overlapped commands (nn = queue tag)"},
+  {0x4E,0x00,SC_ALL_DEVS,"Overlapped commands attempted"},
   {0x50,0x00,T,"Write append error"},
   {0x50,0x01,T,"Write append position error"},
   {0x50,0x02,T,"Position error related to timing"},
-  {0x51,0x00,T|O,"Erase failure"},
+  {0x51,0x00,T|R|O,"Erase failure"},
   {0x52,0x00,T,"Cartridge fault"},
-  {0x53,0x00,D|T|L|W|R|S|O|M,"Media load or eject failed"},
+  {0x53,0x00,D|T|L|W|R|S|O|M|B|K,"Media load or eject failed"},
   {0x53,0x01,T,"Unload tape failure"},
-  {0x53,0x02,D|T|W|R|O|M,"Medium removal prevented"},
+  {0x53,0x02,D|T|W|R|O|M|B|K,"Medium removal prevented"},
   {0x54,0x00,P,"Scsi to host system interface failure"},
   {0x55,0x00,P,"System resource failure"},
+  {0x55,0x01,D|O|B|K,"System buffer full"},
+  {0x55,0x02,D|T|L|P|W|R|S|O|M|A|E|K,"Insufficient reservation resources"},
+  {0x55,0x03,D|T|L|P|W|R|S|O|M|C|A|E,"Insufficient resources"},
+  {0x55,0x04,D|T|L|P|W|R|S|O|M|A|E,"Insufficient registration resources"},
+  {0x55,0x05,D|T|P|W|R|O|M|A|E|B|K,"Insufficient access control resources"},
+  {0x55,0x06,D|T|W|R|O|M|B,"Auxiliary memory out of space"},
   {0x57,0x00,R,"Unable to recover table-of-contents"},
   {0x58,0x00,O,"Generation does not exist"},
   {0x59,0x00,O,"Updated block read"},
-  {0x5A,0x00,D|T|L|P|W|R|S|O|M,"Operator request or state change input (unspecified)"},
-  {0x5A,0x01,D|T|W|R|O|M,"Operator medium removal request"},
-  {0x5A,0x02,D|T|W|O,"Operator selected write protect"},
-  {0x5A,0x03,D|T|W|O,"Operator selected write permit"},
-  {0x5B,0x00,D|T|L|P|W|R|S|O|M,"Log exception"},
-  {0x5B,0x01,D|T|L|P|W|R|S|O|M,"Threshold condition met"},
-  {0x5B,0x02,D|T|L|P|W|R|S|O|M,"Log counter at maximum"},
-  {0x5B,0x03,D|T|L|P|W|R|S|O|M,"Log list codes exhausted"},
+  {0x5A,0x00,D|T|L|P|W|R|S|O|M|B|K,"Operator request or state change input"},
+  {0x5A,0x01,D|T|W|R|O|M|B|K,"Operator medium removal request"},
+  {0x5A,0x02,D|T|W|R|O|A|B|K,"Operator selected write protect"},
+  {0x5A,0x03,D|T|W|R|O|A|B|K,"Operator selected write permit"},
+  {0x5B,0x00,D|T|L|P|W|R|S|O|M|K,"Log exception"},
+  {0x5B,0x01,D|T|L|P|W|R|S|O|M|K,"Threshold condition met"},
+  {0x5B,0x02,D|T|L|P|W|R|S|O|M|K,"Log counter at maximum"},
+  {0x5B,0x03,D|T|L|P|W|R|S|O|M|K,"Log list codes exhausted"},
   {0x5C,0x00,D|O,"Rpl status change"},
   {0x5C,0x01,D|O,"Spindles synchronized"},
   {0x5C,0x02,D|O,"Spindles not synchronized"},
+  {0x5D,0x00,SC_ALL_DEVS,"Failure prediction threshold exceeded"},
+  {0x5D,0x01,R|B,"Media failure prediction threshold exceeded"},
+  {0x5D,0x02,R,SC_LOGICAL_UNIT "failure prediction threshold exceeded"},
+  {0x5D,0x03,R,"spare area exhaustion prediction threshold exceeded"},
+	/* large series of "impending failure" messages */
+  {0x5D,0x10,D|B,SC_HARDWARE_IF "general hard drive failure"},
+  {0x5D,0x11,D|B,SC_HARDWARE_IF "drive" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x12,D|B,SC_HARDWARE_IF "data" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x13,D|B,SC_HARDWARE_IF "seek" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x14,D|B,SC_HARDWARE_IF "too many block reassigns"},
+  {0x5D,0x15,D|B,SC_HARDWARE_IF "access" SC_TIMES_TOO_HIGH },
+  {0x5D,0x16,D|B,SC_HARDWARE_IF "start unit" SC_TIMES_TOO_HIGH },
+  {0x5D,0x17,D|B,SC_HARDWARE_IF "channel parametrics"},
+  {0x5D,0x18,D|B,SC_HARDWARE_IF "controller detected"},
+  {0x5D,0x19,D|B,SC_HARDWARE_IF "throughput performance"},
+  {0x5D,0x1A,D|B,SC_HARDWARE_IF "seek time performance"},
+  {0x5D,0x1B,D|B,SC_HARDWARE_IF "spin-up retry count"},
+  {0x5D,0x1C,D|B,SC_HARDWARE_IF "drive calibration retry count"},
+  {0x5D,0x20,D|B,SC_CONTROLLER_IF "general hard drive failure"},
+  {0x5D,0x21,D|B,SC_CONTROLLER_IF "drive" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x22,D|B,SC_CONTROLLER_IF "data" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x23,D|B,SC_CONTROLLER_IF "seek" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x24,D|B,SC_CONTROLLER_IF "too many block reassigns"},
+  {0x5D,0x25,D|B,SC_CONTROLLER_IF "access" SC_TIMES_TOO_HIGH },
+  {0x5D,0x26,D|B,SC_CONTROLLER_IF "start unit" SC_TIMES_TOO_HIGH },
+  {0x5D,0x27,D|B,SC_CONTROLLER_IF "channel parametrics"},
+  {0x5D,0x28,D|B,SC_CONTROLLER_IF "controller detected"},
+  {0x5D,0x29,D|B,SC_CONTROLLER_IF "throughput performance"},
+  {0x5D,0x2A,D|B,SC_CONTROLLER_IF "seek time performance"},
+  {0x5D,0x2B,D|B,SC_CONTROLLER_IF "spin-up retry count"},
+  {0x5D,0x2C,D|B,SC_CONTROLLER_IF "drive calibration retry count"},
+  {0x5D,0x30,D|B,SC_DATA_CHANNEL_IF "general hard drive failure"},
+  {0x5D,0x31,D|B,SC_DATA_CHANNEL_IF "drive" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x32,D|B,SC_DATA_CHANNEL_IF "data" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x33,D|B,SC_DATA_CHANNEL_IF "seek" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x34,D|B,SC_DATA_CHANNEL_IF "too many block reassigns"},
+  {0x5D,0x35,D|B,SC_DATA_CHANNEL_IF "access" SC_TIMES_TOO_HIGH },
+  {0x5D,0x36,D|B,SC_DATA_CHANNEL_IF "start unit" SC_TIMES_TOO_HIGH },
+  {0x5D,0x37,D|B,SC_DATA_CHANNEL_IF "channel parametrics"},
+  {0x5D,0x38,D|B,SC_DATA_CHANNEL_IF "controller detected"},
+  {0x5D,0x39,D|B,SC_DATA_CHANNEL_IF "throughput performance"},
+  {0x5D,0x3A,D|B,SC_DATA_CHANNEL_IF "seek time performance"},
+  {0x5D,0x3B,D|B,SC_DATA_CHANNEL_IF "spin-up retry count"},
+  {0x5D,0x3C,D|B,SC_DATA_CHANNEL_IF "drive calibration retry count"},
+  {0x5D,0x40,D|B,SC_SERVO_IF "general hard drive failure"},
+  {0x5D,0x41,D|B,SC_SERVO_IF "drive" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x42,D|B,SC_SERVO_IF "data" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x43,D|B,SC_SERVO_IF "seek" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x44,D|B,SC_SERVO_IF "too many block reassigns"},
+  {0x5D,0x45,D|B,SC_SERVO_IF "access" SC_TIMES_TOO_HIGH },
+  {0x5D,0x46,D|B,SC_SERVO_IF "start unit" SC_TIMES_TOO_HIGH },
+  {0x5D,0x47,D|B,SC_SERVO_IF "channel parametrics"},
+  {0x5D,0x48,D|B,SC_SERVO_IF "controller detected"},
+  {0x5D,0x49,D|B,SC_SERVO_IF "throughput performance"},
+  {0x5D,0x4A,D|B,SC_SERVO_IF "seek time performance"},
+  {0x5D,0x4B,D|B,SC_SERVO_IF "spin-up retry count"},
+  {0x5D,0x4C,D|B,SC_SERVO_IF "drive calibration retry count"},
+  {0x5D,0x50,D|B,SC_SPINDLE_IF "general hard drive failure"},
+  {0x5D,0x51,D|B,SC_SPINDLE_IF "drive" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x52,D|B,SC_SPINDLE_IF "data" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x53,D|B,SC_SPINDLE_IF "seek" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x54,D|B,SC_SPINDLE_IF "too many block reassigns"},
+  {0x5D,0x55,D|B,SC_SPINDLE_IF "access" SC_TIMES_TOO_HIGH },
+  {0x5D,0x56,D|B,SC_SPINDLE_IF "start unit" SC_TIMES_TOO_HIGH },
+  {0x5D,0x57,D|B,SC_SPINDLE_IF "channel parametrics"},
+  {0x5D,0x58,D|B,SC_SPINDLE_IF "controller detected"},
+  {0x5D,0x59,D|B,SC_SPINDLE_IF "throughput performance"},
+  {0x5D,0x5A,D|B,SC_SPINDLE_IF "seek time performance"},
+  {0x5D,0x5B,D|B,SC_SPINDLE_IF "spin-up retry count"},
+  {0x5D,0x5C,D|B,SC_SPINDLE_IF "drive calibration retry count"},
+  {0x5D,0x60,D|B,SC_FIRMWARE_IF "general hard drive failure"},
+  {0x5D,0x61,D|B,SC_FIRMWARE_IF "drive" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x62,D|B,SC_FIRMWARE_IF "data" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x63,D|B,SC_FIRMWARE_IF "seek" SC_ERROR_RATE_TOO_HIGH },
+  {0x5D,0x64,D|B,SC_FIRMWARE_IF "too many block reassigns"},
+  {0x5D,0x65,D|B,SC_FIRMWARE_IF "access" SC_TIMES_TOO_HIGH },
+  {0x5D,0x66,D|B,SC_FIRMWARE_IF "start unit" SC_TIMES_TOO_HIGH },
+  {0x5D,0x67,D|B,SC_FIRMWARE_IF "channel parametrics"},
+  {0x5D,0x68,D|B,SC_FIRMWARE_IF "controller detected"},
+  {0x5D,0x69,D|B,SC_FIRMWARE_IF "throughput performance"},
+  {0x5D,0x6A,D|B,SC_FIRMWARE_IF "seek time performance"},
+  {0x5D,0x6B,D|B,SC_FIRMWARE_IF "spin-up retry count"},
+  {0x5D,0x6C,D|B,SC_FIRMWARE_IF "drive calibration retry count"},
+  {0x5D,0xFF,SC_ALL_DEVS,"Failure prediction threshold exceeded (false)"},
+  {0x5E,0x00,D|T|L|P|W|R|S|O|C|A|K,"Low power condition on"},
+  {0x5E,0x01,D|T|L|P|W|R|S|O|C|A|K,"Idle condition activated by timer"},
+  {0x5E,0x02,D|T|L|P|W|R|S|O|C|A|K,"Standby condition activated by timer"},
+  {0x5E,0x03,D|T|L|P|W|R|S|O|C|A|K,"Idle condition activated by command"},
+  {0x5E,0x04,D|T|L|P|W|R|S|O|C|A|K,"Standby condition activated by command"},
+  {0x5E,0x41,B,"Power state change to active"},
+  {0x5E,0x42,B,"Power state change to idle"},
+  {0x5E,0x43,B,"Power state change to standby"},
+  {0x5E,0x45,B,"Power state change to sleep"},
+  {0x5E,0x47,B|K,"Power state change to device control"},
   {0x60,0x00,S,"Lamp failure"},
   {0x61,0x00,S,"Video acquisition error"},
   {0x61,0x01,S,"Unable to acquire video"},
   {0x61,0x02,S,"Out of focus"},
   {0x62,0x00,S,"Scan head positioning error"},
   {0x63,0x00,R,"End of user area encountered on this track"},
+  {0x63,0x01,R,"Packet does not fit in available space"},
   {0x64,0x00,R,"Illegal mode for this track"},
+  {0x64,0x01,R,"Invalid packet size"},
+  {0x65,0x00,SC_ALL_DEVS,"Voltage fault"},
+  {0x66,0x00,S,"Automatic document feeder cover up"},
+  {0x66,0x01,S,"Automatic document feeder lift up"},
+  {0x66,0x02,S,"Document jam in automatic document feeder"},
+  {0x66,0x03,S,"Document miss feed automatic in document feeder"},
+  {0x67,0x00,A,"Configuration failure"},
+  {0x67,0x01,A,"Configuration of incapable logical units failed"},
+  {0x67,0x02,A,"Add logical unit failed"},
+  {0x67,0x03,A,"Modification of logical unit failed"},
+  {0x67,0x04,A,"Exchange of logical unit failed"},
+  {0x67,0x05,A,"Remove of logical unit failed"},
+  {0x67,0x06,A,"Attachment of logical unit failed"},
+  {0x67,0x07,A,"Creation of logical unit failed"},
+  {0x67,0x08,A,"Assign failure occurred"},
+  {0x67,0x09,A,"Multiply assigned logical unit"},
+  {0x67,0x0A,SC_ALL_DEVS,"Set target port groups command failed"},
+  {0x68,0x00,A,SC_LOGICAL_UNIT "not configured"},
+  {0x69,0x00,A,"Data loss on logical unit"},
+  {0x69,0x01,A,"Multiple logical unit failures"},
+  {0x69,0x02,A,"Parity/data mismatch"},
+  {0x6A,0x00,A,"Informational,refer to log"},
+  {0x6B,0x00,A,"State change has occurred"},
+  {0x6B,0x01,A,"Redundancy level got better"},
+  {0x6B,0x02,A,"Redundancy level got worse"},
+  {0x6C,0x00,A,"Rebuild failure occurred"},
+  {0x6D,0x00,A,"Recalculate failure occurred"},
+  {0x6E,0x00,A,"Command to logical unit failed"},
+  {0x6F,0x00,R,"Copy protection key exchange failure - authentication failure"},
+  {0x6F,0x01,R,"Copy protection key exchange failure - key not present"},
+  {0x6F,0x02,R,"Copy protection key exchange failure - key not established"},
+  {0x6F,0x03,R,"Read of scrambled sector without authentication"},
+  {0x6F,0x04,R,"Media region code is mismatched to logical unit region"},
+  {0x6F,0x05,R,"Drive region must be permanent/region reset count error"},
+  /*
+   * FIXME(eric) - need a way to represent wildcards here.
+   */
+  {0x70,0x00,T,"Decompression exception short algorithm id of nn"},
+  {0x71,0x00,T,"Decompression exception long algorithm id"},
+  {0x72,0x00,R,"Session fixation error"},
+  {0x72,0x01,R,"Session fixation error writing lead-in"},
+  {0x72,0x02,R,"Session fixation error writing lead-out"},
+  {0x72,0x03,R,"Session fixation error - incomplete track in session"},
+  {0x72,0x04,R,"Empty or partially written reserved track"},
+  {0x72,0x05,R,"No more track reservations allowed"},
+  {0x73,0x00,R,"Cd control error"},
+  {0x73,0x01,R,"Power calibration area almost full"},
+  {0x73,0x02,R,"Power calibration area is full"},
+  {0x73,0x03,R,"Power calibration area error"},
+  {0x73,0x04,R,"Program memory area update failure"},
+  {0x73,0x05,R,"Program memory area is full"},
+  {0x73,0x06,R,"RMA/PMA is full"},
   {0, 0, 0, NULL}
 };
 
+static const char * sc_oft_used[0x1f] = {
+        "umulig",                       /* index 0x0 should be impossible */
+        "Audio play operation ",
+        "Logical unit ",
+        "not ready, ",
+        " operation",
+        " in progress ",
+        "Hardware impending failure ",
+        "Controller impending failure ",
+        "Data channel impending failure ",      /* index 0x8 */
+        "Servo impending failure ",
+        "Spindle impending failure ",
+        "Firmware impending failure ",
+        "Recovered data ",
+        " error rate too high",
+        " times too high",
+};
+
 static const char *snstext[] = {
-    "None",                     /* There is no sense information */
+    "No Sense",                 /* There is no sense information */
     "Recovered Error",          /* The last command completed successfully
                                    but used error correction */
     "Not Ready",                /* The addressed target is not ready */
@@ -386,42 +810,94 @@
     "Key=9",                    /* Vendor specific */
     "Copy Aborted",             /* COPY or COMPARE was aborted */
     "Aborted Command",          /* The target aborted the command */
-    "Equal",                    /* A SEARCH DATA command found data equal */
+    "Equal",                    /* SEARCH DATA found data equal (obsolete) */
     "Volume Overflow",          /* Medium full with still data to be written */
     "Miscompare",               /* Source data and data on the medium
                                    do not agree */
     "Key=15"                    /* Reserved */
 };
 
+static
+void sg_print_asc_ascq(unsigned char asc, unsigned char ascq)
+{
+    int k, j;
+    char obuff[256];
+    const char * ccp;
+    const char * oup;
+    char c;
+    int found = 0;
+
+    for (k=0; additional[k].text; k++) {
+	if (additional[k].code1 == asc &&
+	    additional[k].code2 == ascq) {
+	    found = 1;
+	    ccp = additional[k].text;
+	    for (j = 0; *ccp && (j < sizeof(obuff)); ++ccp) {
+		c = *ccp;
+		if ((c < 0x20) && (c > 0)) {
+		    oup = sc_oft_used[(int)c];
+		    if (oup) {
+			strcpy(obuff + j, oup);
+			j += strlen(oup);
+		    }
+		    else {
+			strcpy(obuff + j, "???");
+			j += 3;
+		    }
+		}
+		else
+		    obuff[j++] = c;
+	    }
+	    if (j < sizeof(obuff))
+		obuff[j] = '\0';
+	    else
+		obuff[sizeof(obuff) - 1] = '\0';
+	    fprintf(OUTP, "Additional sense: %s\n", obuff);
+	}
+    }
+    if (found)
+	return;
+
+    for(k=0; additional2[k].text; k++) {
+	if ((additional2[k].code1 == asc) &&
+	    (ascq >= additional2[k].code2_min)  &&
+	    (ascq <= additional2[k].code2_max)) {
+	    found = 1;
+	    fprintf(OUTP, "Additional sense: ");
+	    fprintf(OUTP, additional2[k].text, ascq);
+	    fprintf(OUTP, "\n");
+	}
+    }
+    if (! found)
+	fprintf(OUTP, "ASC=%2x ASCQ=%2x\n", asc, ascq);
+}
+
 /* Print sense information */
 void sg_print_sense(const char * leadin, const unsigned char * sense_buffer,
                     int sb_len)
 {
-    int i, s;
-    int sense_class, valid, code;
+    int k, s;
+    int sense_key, sense_class, valid, code;
+    int descriptor_format = 0;
     const char * error = NULL;
 
+    if (sb_len < 1) {
+	    fprintf(OUTP, "sense buffer empty\n");
+	    return;
+    }
     sense_class = (sense_buffer[0] >> 4) & 0x07;
     code = sense_buffer[0] & 0xf;
     valid = sense_buffer[0] & 0x80;
+    if (leadin)
+	fprintf(OUTP, "%s: ", leadin);
 
     if (sense_class == 7) {     /* extended sense data */
         s = sense_buffer[7] + 8;
-        if(s > sb_len)
-           s = sb_len;
-
-        if (!valid)
-            fprintf(OUTP, "[valid=0] ");
-        fprintf(OUTP, "Info fld=0x%x, ", (int)((sense_buffer[3] << 24) |
-                (sense_buffer[4] << 16) | (sense_buffer[5] << 8) |
-                sense_buffer[6]));
-
-        if (sense_buffer[2] & 0x80)
-           fprintf(OUTP, "FMK ");     /* current command has read a filemark */
-        if (sense_buffer[2] & 0x40)
-           fprintf(OUTP, "EOM ");     /* end-of-medium condition exists */
-        if (sense_buffer[2] & 0x20)
-           fprintf(OUTP, "ILI ");     /* incorrect block length requested */
+        if(s > sb_len) {
+	    fprintf(OUTP, "Sense buffer too small (at %d bytes), %d bytes "
+		    "truncated\n", sb_len, s - sb_len);
+	    s = sb_len;
+	}
 
         switch (code) {
         case 0x0:
@@ -432,34 +908,54 @@
                 /* e.g., an earlier write to disk cache succeeded, but
                    now the disk discovers that it cannot write the data */
             break;
+	case 0x2:
+	    descriptor_format = 1;
+	    error = "Descriptor current";
+	    /* new descriptor sense format */
+	    break;
+	case 0x3:
+	    descriptor_format = 1;
+	    error = "Descriptor deferred";
+	    /* new descriptor sense format (deferred report) */
+	    break;
         default:
             error = "Invalid";
         }
+	sense_key = sense_buffer[ descriptor_format ? 1 : 2 ] & 0xf;
+        fprintf(OUTP, "%s, Sense key: %s\n", error, snstext[sense_key]);
 
-        fprintf(OUTP, "%s ", error);
+	if (descriptor_format)
+	    sg_print_asc_ascq(sense_buffer[2], sense_buffer[3]);
+	else {
+            if (!valid)
+                fprintf(OUTP, "[valid=0] ");
+            fprintf(OUTP, "Info fld=0x%x, ", (int)((sense_buffer[3] << 24) |
+                    (sense_buffer[4] << 16) | (sense_buffer[5] << 8) |
+                    sense_buffer[6]));
 
-        if (leadin)
-            fprintf(OUTP, "%s: ", leadin);
-        fprintf(OUTP, "sense key: %s\n", snstext[sense_buffer[2] & 0x0f]);
+            if (sense_buffer[2] & 0x80)
+               fprintf(OUTP, "FMK "); /* current command has read a filemark */
+            if (sense_buffer[2] & 0x40)
+               fprintf(OUTP, "EOM "); /* end-of-medium condition exists */
+            if (sense_buffer[2] & 0x20)
+               fprintf(OUTP, "ILI "); /* incorrect block length requested */
 
-        /* Check to see if additional sense information is available */
-        if(sense_buffer[7] + 7 < 13 ||
-           (sense_buffer[12] == 0  && sense_buffer[13] ==  0)) goto done;
+	    if (s > 13) {
+		if (sense_buffer[12] || sense_buffer[13])
+		    sg_print_asc_ascq(sense_buffer[12], sense_buffer[13]);
+	    }
+	    if (sense_key == 5 && s >= 18 && (sense_buffer[15]&0x80)) {
+		fprintf(OUTP, "Sense Key Specific: Error in %s byte %d",
+			(sense_buffer[15]&0x40)?"Command":"Data",
+			(sense_buffer[16]<<8)|sense_buffer[17]);
+		if(sense_buffer[15]&0x08) {
+		    fprintf(OUTP, " bit %d\n", sense_buffer[15]&0x07);
+		} else {
+		    fprintf(OUTP, "\n");
+		}
+	    }
+	}
 
-        for(i=0; additional[i].text; i++)
-            if(additional[i].code1 == sense_buffer[12] &&
-               additional[i].code2 == sense_buffer[13])
-                fprintf(OUTP, "Additional sense indicates: %s\n",
-                        additional[i].text);
-
-        for(i=0; additional2[i].text; i++)
-            if(additional2[i].code1 == sense_buffer[12] &&
-               additional2[i].code2_min >= sense_buffer[13]  &&
-               additional2[i].code2_max <= sense_buffer[13]) {
-                fprintf(OUTP, "Additional sense indicates: ");
-                fprintf(OUTP, additional2[i].text, sense_buffer[13]);
-                fprintf(OUTP, "\n");
-            };
     } else {    /* non-extended sense data */
 
          /*
@@ -470,6 +966,10 @@
           *    sense_buffer[1..3] : 21-bit logical block address
           */
 
+	if (sb_len < 4) {
+	    fprintf(OUTP, "sense buffer too short (4 byte minimum)\n");
+	    return;
+	}
         if (leadin)
             fprintf(OUTP, "%s: ", leadin);
         if (sense_buffer[0] < 15)
@@ -483,15 +983,13 @@
         s = 4;
     }
 
- done:
     fprintf(OUTP, "Raw sense data (in hex):\n  ");
-    for (i = 0; i < s; ++i) {
-        if ((i > 0) && (0 == (i % 24)))
+    for (k = 0; k < s; ++k) {
+        if ((k > 0) && (0 == (k % 24)))
             fprintf(OUTP, "\n  ");
-        fprintf(OUTP, "%02x ", sense_buffer[i]);
+        fprintf(OUTP, "%02x ", sense_buffer[k]);
     }
     fprintf(OUTP, "\n");
-    return;
 }
 
 static const char * hostbyte_table[]={
@@ -543,32 +1041,26 @@
             su < suggest_max ? driversuggest_table[su]:"invalid");
 }
 
-#ifdef SG_IO
-int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp)
-{
-    return sg_chk_n_print(leadin, hp->masked_status, hp->host_status,
-                          hp->driver_status, hp->sbp, hp->sb_len_wr);
-}
-#endif
-
-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)
+static int sg_sense_print(const char * leadin, int scsi_status,
+                          int host_status, int driver_status,
+                          const unsigned char * sense_buffer, int sb_len)
 {
     int done_leadin = 0;
     int done_sense = 0;
 
-    if ((0 == masked_status) && (0 == host_status) &&
+    scsi_status &= 0x7e; /*sanity */
+    if ((0 == scsi_status) && (0 == host_status) &&
         (0 == driver_status))
         return 1;       /* No problems */
-    if (0 != masked_status) {
+    if (0 != scsi_status) {
         if (leadin)
             fprintf(OUTP, "%s: ", leadin);
         done_leadin = 1;
-        sg_print_status(masked_status);
+	fprintf(OUTP, "scsi status: ");
+        sg_print_scsi_status(scsi_status);
         fprintf(OUTP, "\n");
-        if (sense_buffer && ((masked_status == CHECK_CONDITION) ||
-                             (masked_status == COMMAND_TERMINATED))) {
+        if (sense_buffer && ((scsi_status == SCSI_CHECK_CONDITION) ||
+                             (scsi_status == SCSI_COMMAND_TERMINATED))) {
             sg_print_sense(0, sense_buffer, sb_len);
             done_sense = 1;
         }
@@ -593,17 +1085,35 @@
         sg_print_driver_status(driver_status);
         fprintf(OUTP, "\n");
         if (sense_buffer && (! done_sense) &&
-            (SG_ERR_DRIVER_SENSE & driver_status))
+            (SG_ERR_DRIVER_SENSE == (0xf & driver_status)))
             sg_print_sense(0, sense_buffer, sb_len);
     }
     return 0;
 }
 
 #ifdef SG_IO
+int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp)
+{
+    return sg_sense_print(leadin, hp->status, hp->host_status,
+                          hp->driver_status, hp->sbp, hp->sb_len_wr);
+}
+#endif
+
+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)
+{
+    int scsi_status = (masked_status << 1) & 0x7e;
+
+    return sg_sense_print(leadin, scsi_status, host_status, driver_status,
+    			  sense_buffer, sb_len);
+}
+
+#ifdef SG_IO
 int sg_err_category3(struct sg_io_hdr * hp)
 {
-    return sg_err_category(hp->masked_status, hp->host_status,
-                           hp->driver_status, hp->sbp, hp->sb_len_wr);
+    return sg_err_category_new(hp->status, hp->host_status,
+                               hp->driver_status, hp->sbp, hp->sb_len_wr);
 }
 #endif
 
@@ -611,20 +1121,41 @@
                     int driver_status, const unsigned char * sense_buffer,
                     int sb_len)
 {
-    if ((0 == masked_status) && (0 == host_status) &&
+    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)
+{
+    scsi_status &= 0x7e;
+    if ((0 == scsi_status) && (0 == host_status) &&
         (0 == driver_status))
         return SG_ERR_CAT_CLEAN;
-    if ((CHECK_CONDITION == masked_status) ||
-        (COMMAND_TERMINATED == masked_status) ||
-        (SG_ERR_DRIVER_SENSE & driver_status)) {
+    if ((SCSI_CHECK_CONDITION == scsi_status) ||
+        (SCSI_COMMAND_TERMINATED == scsi_status) ||
+        (SG_ERR_DRIVER_SENSE == (0xf & driver_status))) {
         if (sense_buffer && (sb_len > 2)) {
-            if(RECOVERED_ERROR == sense_buffer[2])
+	    int sense_key;
+	    unsigned char asc;
+
+	    if (sense_buffer[0] & 0x2) {
+	    	sense_key = sense_buffer[1] & 0xf;
+		asc = sense_buffer[2];
+	    }
+	    else {
+	    	sense_key = sense_buffer[2] & 0xf;
+		asc = (sb_len > 12) ? sense_buffer[12] : 0;
+	    }
+
+            if(RECOVERED_ERROR == sense_key)
                 return SG_ERR_CAT_RECOVERED;
-            else if ((UNIT_ATTENTION == (0x0f & sense_buffer[2])) &&
-                     (sb_len > 12)) {
-                if (0x28 == sense_buffer[12])
+            else if (UNIT_ATTENTION == sense_key) {
+                if (0x28 == asc)
                     return SG_ERR_CAT_MEDIA_CHANGED;
-                if (0x29 == sense_buffer[12])
+                if (0x29 == asc)
                     return SG_ERR_CAT_RESET;
             }
         }
@@ -647,3 +1178,23 @@
 {
     return COMMAND_SIZE(opcode);
 }
+
+void sg_get_command_name(unsigned char opcode, int buff_len, char * buff)
+{
+    const char **table = commands[ group(opcode) ];
+
+    if ((NULL == buff) || (buff_len < 1))
+    	return;
+
+    switch ((unsigned long) table) {
+    case RESERVED_GROUP:
+        strncpy(buff, reserved, buff_len);
+        break;
+    case VENDOR_GROUP:
+        strncpy(buff, vendor, buff_len);
+        break;
+    default:
+        strncpy(buff, table[opcode & 0x1f], buff_len);
+        break;
+    }
+}
diff --git a/sg_err.h b/sg_err.h
index fd59e3b..317767b 100644
--- a/sg_err.h
+++ b/sg_err.h
@@ -3,9 +3,7 @@
 
 /* Feel free to copy and modify this GPL-ed code into your applications. */
 
-/* Version 0.84 (20010115) 
-	- all output now sent to stderr rather thatn stdout
-	- remove header files included in this file
+/* Version 0.89 (20030313) 
 */
 
 
@@ -15,6 +13,22 @@
    here is copied from drivers/scsi/scsi.h which is not visible in
    the user space. */
 
+#ifndef SCSI_CHECK_CONDITION
+/* Following are the "true" SCSI status codes. Linux has traditionally
+   used a 1 bit right and masked version of these. So now CHECK_CONDITION
+   and friends (in <scsi/scsi.h>) are deprecated. */
+#define SCSI_CHECK_CONDITION 0x2
+#define SCSI_CONDITION_MET 0x4
+#define SCSI_BUSY 0x8
+#define SCSI_IMMEDIATE 0x10
+#define SCSI_IMMEDIATE_CONDITION_MET 0x14
+#define SCSI_RESERVATION_CONFLICT 0x18
+#define SCSI_COMMAND_TERMINATED 0x22
+#define SCSI_TASK_SET_FULL 0x28
+#define SCSI_ACA_ACTIVE 0x30
+#define SCSI_TASK_ABORTED 0x40
+#endif
+
 /* The following are 'host_status' codes */
 #ifndef DID_OK
 #define DID_OK 0x00
@@ -102,6 +116,7 @@
 extern void sg_print_sense(const char * leadin,
                            const unsigned char * sense_buffer, int sb_len);
 extern void sg_print_status(int masked_status);
+extern void sg_print_scsi_status(int scsi_status);
 extern void sg_print_host_status(int host_status);
 extern void sg_print_driver_status(int driver_status);
 
@@ -130,11 +145,18 @@
                int driver_status, const unsigned char * sense_buffer,
                int sb_len);
 
+extern 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. 
    Only version 3 sg_err.c defines it. */
 extern int sg_err_category3(struct sg_io_hdr * hp);
 
 /* Returns length of SCSI command given the opcode (first byte) */
-int sg_get_command_size(unsigned char opcode);
+extern int sg_get_command_size(unsigned char opcode);
+
+extern void sg_get_command_name(unsigned char opcode, int buff_len, 
+				char * buff);
 
 #endif
diff --git a/sg_inq.8 b/sg_inq.8
index 9995daf..849ff60 100644
--- a/sg_inq.8
+++ b/sg_inq.8
@@ -1,4 +1,4 @@
-.TH SG_INQ "8" "March 2003" "sg3_utils-1.03" SG3_UTILS
+.TH SG_INQ "8" "April 2003" "sg3_utils-1.03" SG3_UTILS
 .SH NAME
 sg_inq \- outputs data retrieved from the SCSI INQUIRY command
 .SH SYNOPSIS
@@ -8,7 +8,7 @@
 .SH DESCRIPTION
 .\" Add any additional description here
 .PP
-This command sends an INQUIRY SCSI command to the given device and then
+This utility sends an INQUIRY SCSI command to the given device and then
 outputs the response. All SCSI devices are meant to respond to
 a "standard" INQUIRY command with at least a 36 byte response (in SCSI 2
 and higher). An INQUIRY is termed as "standard" when both the EVPD and
diff --git a/sg_inq.c b/sg_inq.c
index ffcaf8f..179a3f4 100644
--- a/sg_inq.c
+++ b/sg_inq.c
@@ -12,19 +12,22 @@
 #include "sg_err.h"
 
 /* A utility program for the Linux OS SCSI generic ("sg") device driver.
-*  Copyright (C) 2000-2002 D. Gilbert
+*  Copyright (C) 2000-2003 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)
 *  any later version.
 
    This program outputs information provided by a SCSI INQUIRY command.
-   It is mainly based on the SCSI-3 SPC-1 document with some additions
-   from SPC-2 (draft revision 18).
+   It is mainly based on the SCSI-3 SPC-2 document.
+
+   Acknowledgment:
+      -	Martin Schwenke <martin@meltin.net> added the raw switch and other
+	improvements [20020814]
    
 */
 
-static char * version_str = "0.17 20020228";
+static char * version_str = "0.23 20030506";
 
 
 /* #define SG_DEBUG */
@@ -90,22 +93,33 @@
 
 static void usage()
 {
-    printf("Usage: 'sg_inq [-e] [-h] [-o=<opcode_page>] [-V]"
-	   " <sg_device>'\n"
-	   " where -e   set EVPD mode\n"
-	   "       -c   set CmdDt mode\n"
-	   "       -cl  list supported commands using CmdDt mode\n"
-	   "       -h   output in hex (ASCII to the right)\n"
-	   "       -o=<opcode_page> opcode or page code in hex\n"
-	   "       -p   output SCSI adapter PCI information\n"
-	   "       -V   output version string\n"
-	   "       -36  only perform a 36 byte INQUIRY\n"
-	   "       -?   output this usage message\n"
-	   " If no optional switches given (or '-h') then does"
-	   " a standard INQUIRY\n");
+    fprintf(stderr,
+	    "Usage: 'sg_inq [-c] [-cl] [-e] [-h|-r] [-o=<opcode_page>] [-p]"
+	    " [-V] [-36]\n               [-?] <sg_device>'\n"
+	    " where -c   set CmdDt mode (use -o for opcode)\n"
+	    "       -cl  list supported commands using CmdDt mode\n"
+	    "       -e   set EVPD mode (use -o for page code)\n"
+	    "       -h   output in hex (ASCII to the right)\n"
+	    "       -o=<opcode_page> opcode or page code in hex\n"
+	    "       -p   output SCSI adapter PCI information\n"
+	    "       -r   output raw binary data\n"
+	    "       -V   output version string\n"
+	    "       -36  only perform a 36 byte INQUIRY\n"
+	    "       -?   output this usage message\n"
+	    " If no optional switches given (or '-h') then does"
+	    " a standard INQUIRY\n");
 }
 
 
+static void dStrRaw(const char* str, int len)
+{
+    int i;
+    
+    for (i = 0 ; i < len; i++) {
+	printf("%c", str[i]);
+    }
+}
+
 static void dStrHex(const char* str, int len)
 {
     const char* p = str;
@@ -169,16 +183,18 @@
     int do_cmddt = 0;
     int do_cmdlst = 0;
     int do_hex = 0;
+    int do_raw = 0;
     int do_pci = 0;
     int do_36 = 0;
-    int oflags = O_RDONLY;
+    int oflags = O_RDONLY | O_NONBLOCK;
     int ansi_version = 0;
+    int ret = 0;
 
     for (k = 1; k < argc; ++k) {
         if (0 == strncmp("-o=", argv[k], 3)) {
             num = sscanf(argv[k] + 3, "%x", &num_opcode);
             if ((1 != num) || (num_opcode > 255)) {
-                printf("Bad number after '-o' switch\n");
+                fprintf(stderr, "Bad number after '-o' switch\n");
                 file_name = 0;
                 break;
             }
@@ -187,6 +203,8 @@
 	    do_evpd = 1;
         else if (0 == strcmp("-h", argv[k]))
 	    do_hex = 1;
+        else if (0 == strcmp("-r", argv[k]))
+	    do_raw = 1;
         else if (0 == strcmp("-cl", argv[k])) {
 	    do_cmdlst = 1;
 	    do_cmddt = 1;
@@ -202,29 +220,35 @@
 	    break;
 	}
         else if (0 == strcmp("-V", argv[k])) {
-	    printf("Version string: %s\n", version_str);
+	    fprintf(stderr, "Version string: %s\n", version_str);
 	    exit(0);
 	}
         else if (*argv[k] == '-') {
-            printf("Unrecognized switch: %s\n", argv[k]);
+            fprintf(stderr, "Unrecognized switch: %s\n", argv[k]);
             file_name = 0;
             break;
         }
         else if (0 == file_name)
             file_name = argv[k];
         else {
-            printf("too many arguments\n");
+            fprintf(stderr, "too many arguments\n");
             file_name = 0;
             break;
         }
     }
+    
+    if (do_raw && do_hex) {
+	fprintf(stderr, "Can't do hex and raw at the same time\n");
+	file_name = 0;
+    }
+    
     if (0 == file_name) {
         usage();
         return 1;
     }
 
     if (do_pci)
-    	oflags = O_RDWR;
+    	oflags = O_RDWR | O_NONBLOCK;
     if ((sg_fd = open(file_name, oflags)) < 0) {
         snprintf(ebuff, EBUFF_SZ, "sg_inq: error opening file: %s", file_name);
         perror(ebuff);
@@ -232,26 +256,34 @@
     }
     /* Just to be safe, check we have a new sg device by trying an ioctl */
     if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
-        printf("sg_inq: %s doesn't seem to be a version 3 sg device\n",
-               file_name);
+        fprintf(stderr,
+		"sg_inq: %s doesn't seem to be a version 3 sg device\n",
+		file_name);
         close(sg_fd);
         return 1;
     }
     memset(rsp_buff, 0, MX_ALLOC_LEN + 1);
 
     if (! (do_cmddt || do_evpd)) {
-	printf("standard INQUIRY:\n");
+        if (!do_raw)
+	    printf("standard INQUIRY:\n");
+	if (num_opcode > 0)
+	    printf(" <<given opcode or page_code is being ignored>>\n");
+	
         if (0 == do_inq(sg_fd, 0, 0, 0, rsp_buff, 36, 1)) {
 	    len = rsp_buff[4] + 5;
 	    ansi_version = rsp_buff[2] & 0x7;
 	    if ((len > 36) && (len < 256) && (! do_36)) {
 		if (do_inq(sg_fd, 0, 0, 0, rsp_buff, len, 1)) {
-	    	    printf("second INQUIRY (%d byte) failed\n", len);
+	    	    fprintf(stderr, "second INQUIRY (%d byte) failed\n", len);
 	    	    return 1;
 		}
-		if (len != (rsp_buff[4] + 5))
-	    	    printf("strange, twin INQUIRYs yield different "
-		    	   "'additional length'\n");
+		if (len != (rsp_buff[4] + 5)) {
+	    	    fprintf(stderr,
+			    "strange, twin INQUIRYs yield different "
+			    "'additional length'\n");
+		    ret = 2;
+		}
 	    }
 	    if (do_36) {
 	    	act_len = len;
@@ -261,6 +293,8 @@
 	    	act_len = len;
 	    if (do_hex)
 		dStrHex((const char *)rsp_buff, len);
+	    else if (do_raw)
+		dStrRaw((const char *)rsp_buff, len);
 	    else {
 	        printf("  PQual=%d, Device type=%d, RMB=%d, ANSI version=%d, ",
 	               (rsp_buff[0] & 0xe0) >> 5, rsp_buff[0] & 0x1f,
@@ -322,7 +356,8 @@
 		    }
 		}
             }
-	    if (0 == do_inq(sg_fd, 0, 1, 0x80, rsp_buff, MX_ALLOC_LEN, 0)) {
+	    if (!do_raw &&
+		(0 == do_inq(sg_fd, 0, 1, 0x80, rsp_buff, MX_ALLOC_LEN, 0))) {
 	        len = rsp_buff[3];
 		if (len > 0) {
 		    memcpy(buff, rsp_buff + 4, len);
@@ -337,66 +372,123 @@
 	}
     }
     else if (do_cmddt) {
+	int reserved_cmddt;
+	char op_name[128];
+
     	if (do_cmdlst) {
 	    printf("Supported command list:\n");
 	    for (k = 0; k < 256; ++k) {
 		if (0 == do_inq(sg_fd, 1, 0, k, rsp_buff, MX_ALLOC_LEN, 1)) {
 		    support_num = rsp_buff[1] & 7;
+		    reserved_cmddt = rsp_buff[4];
 		    if ((3 == support_num) || (5 == support_num)) {
 		    	num = rsp_buff[5];
 		    	for (j = 0; j < num; ++j)
 			    printf(" %.2x", (int)rsp_buff[6 + j]);
 			if (5 == support_num)
-			    printf("  [vendor specific manner (5)]\n");
-			else
-			    printf("\n");
+			    printf("  [vendor specific manner (5)]");
+			sg_get_command_name((unsigned char)k, 
+					    sizeof(op_name) - 1, op_name);
+			op_name[sizeof(op_name) - 1] = '\0';
+			printf("  %s\n", op_name);
 		    }
 		    else if ((4 == support_num) || (6 == support_num))
 		    	printf("  opcode=0x%.2x vendor specific (%d)\n",
 			       k, support_num);
+		    else if ((0 == support_num) && (reserved_cmddt > 0)) {
+		    	printf("  opcode=0x%.2x ignored cmddt bit, "
+			       "given standard INQUIRY response, stop\n", k);
+			break;
+		    }
 		}
 		else {
-		    printf("CmdDt INQUIRY on opcode=0x%.2x: failed\n", k);
+		    fprintf(stderr,
+			    "CmdDt INQUIRY on opcode=0x%.2x: failed\n", k);
 		    break;
 		}
 	    }
 	}
 	else {
-	    printf("CmdDt INQUIRY, opcode=0x%.2x:\n", num_opcode);
+	    if (! do_raw) {
+	        printf("CmdDt INQUIRY, opcode=0x%.2x:  [", num_opcode);
+		sg_get_command_name((unsigned char)num_opcode, 
+				    sizeof(op_name) - 1, op_name);
+		op_name[sizeof(op_name) - 1] = '\0';
+		printf("%s]\n", op_name);
+	    }
 	    if (0 == do_inq(sg_fd, 1, 0, num_opcode, rsp_buff, 
 	    		    MX_ALLOC_LEN, 1)) {
 		len = rsp_buff[5] + 6;
+		reserved_cmddt = rsp_buff[4];
 		if (do_hex)
 		    dStrHex((const char *)rsp_buff, len);
+		else if (do_raw)
+		    dStrRaw((const char *)rsp_buff, len);
 		else {
 		    const char * desc_p;
-		    support_num = rsp_buff[1] & 7;
+		    int prnt_cmd = 0;
 
+		    support_num = rsp_buff[1] & 7;
+		    num = rsp_buff[5];
 		    switch (support_num) {
-		    case 0: desc_p = "no data available"; break;
+		    case 0: 
+			if (0 == reserved_cmddt)
+			    desc_p = "no data available"; 
+			else
+			    desc_p = "ignored cmddt bit, standard INQUIRY "
+				     "response";
+			break;
 		    case 1: desc_p = "not supported"; break;
 		    case 2: desc_p = "reserved (2)"; break;
-		    case 3: desc_p = "supported as per standard"; break;
+		    case 3: desc_p = "supported as per standard"; 
+			    prnt_cmd = 1;
+			    break;
 		    case 4: desc_p = "vendor specific (4)"; break;
-		    case 5: desc_p = "supported in vendor specific way"; 
+		    case 5: desc_p = "supported in vendor specific way";
+			    prnt_cmd = 1; 
 		    	    break;
 		    case 6: desc_p = "vendor specific (6)"; break;
 		    case 7: desc_p = "reserved (7)"; break;
 		    default: desc_p = "impossible value > 7"; break;
 		    }
-
-		    printf("  Support field: %s\n", desc_p);
+		    if (prnt_cmd) {
+		        printf("  Support field: %s [", desc_p);
+		        for (j = 0; j < num; ++j)
+			    printf(" %.2x", (int)rsp_buff[6 + j]);
+			printf(" ]\n");
+		    } else
+		        printf("  Support field: %s\n", desc_p);
 		}
 	    }
+	    else {
+		fprintf(stderr,
+			"CmdDt INQUIRY on opcode=0x%.2x: failed\n",
+			num_opcode);
+		return 1;
+	    }
+
 	}
     }
     else if (do_evpd) {
-	printf("EVPD INQUIRY, page code=0x%.2x:\n", num_opcode);
+	if (!do_raw)
+	    printf("EVPD INQUIRY, page code=0x%.2x:\n", num_opcode);
         if (0 == do_inq(sg_fd, 0, 1, num_opcode, rsp_buff, MX_ALLOC_LEN, 1)) {
 	    len = rsp_buff[3] + 4;
-	    if (! do_hex)
-	    	printf(" Only hex output supported\n");
-	    dStrHex((const char *)rsp_buff, len);
+	    if (num_opcode != rsp_buff[1])
+		printf("non evpd respone; probably a STANDARD INQUIRY "
+		       "response\n");
+	    else if (do_raw)
+		dStrRaw((const char *)rsp_buff, len);
+	    else {
+		if (! do_hex)
+		    printf(" Only hex output supported\n");
+		dStrHex((const char *)rsp_buff, len);
+	    }
+	}
+	else {
+	    fprintf(stderr,
+		    "EVPD INQUIRY, page code=0x%.2x: failed\n", num_opcode);
+	    return 1;
 	}
     }
 
@@ -419,5 +511,5 @@
     }
 
     close(sg_fd);
-    return 0;
+    return ret;
 }
diff --git a/sg_logs.8 b/sg_logs.8
index 08414d2..774dd7f 100644
--- a/sg_logs.8
+++ b/sg_logs.8
@@ -1,17 +1,17 @@
-.TH SG_LOGS "8" "March 2003" "sg3_utils-1.03" SG3_UTILS
+.TH SG_LOGS "8" "April 2003" "sg3_utils-1.04" SG3_UTILS
 .SH NAME
 sg_logs \- reads SCSI LOG SENSE pages
 .SH SYNOPSIS
 .B sg_logs
 [\fI-a\fR] [\fI-c=<page_control>\fR] [\fI-h\fR] [\fI-l\fR]
 [\fI-p=<page_code>\fR] [\fI-paramp=<parameter_pointer>\fR] [\fI-ppc\fR]
-[\fI-sp\fR] [\fI-V\fR] [\fI-?\fR] \fI<sg_device>\fR
+[\fI-sp\fR] [\fI-t\fR] [\fI-V\fR] [\fI-?\fR] \fI<sg_device>\fR
 .SH DESCRIPTION
 .\" Add any additional description here
 .PP
-This command sends a LOG SENSE SCSI command to the given device and then
+This utility sends a LOG SENSE SCSI command to the given device and then
 outputs the response. There is no facility to change log page data with 
-this command.
+this utility.
 .TP
 -a
 outputs all the log pages supported by the device.
@@ -34,8 +34,8 @@
 inclusive.
 .TP
 -paramp=<parameter_pointer>
-parameter pointer value (in hex) to plave in command. Should be a number
-between 0 and ffff inclusive. Requires PPC bit to be set to be active.
+parameter pointer value (in hex) to place in command. Should be a number
+between 0 and ffff inclusive. 
 .TP
 -ppc
 sets the Parameter Pointer Control (PPC) bit. Default is 0 (i.e. cleared).
@@ -43,6 +43,11 @@
 -sp
 sets the Saving Parameters (SP) bit. Default is 0 (i.e. cleared).
 .TP
+-t
+outputs the temperature. First looks in the temperature log page and if
+that is not available tries the Informational Exceptions page which may also
+have the current temperature (especially in older disks).
+.TP
 -V
 print out version string
 .TP
@@ -51,7 +56,7 @@
 .PP
 Various log pages hold information error rates, device temperature,
 start stop cycles since device produced and the results of the last
-20 self tests. Self tests can be initiated by the sg_senddiag command.
+20 self tests. Self tests can be initiated by the sg_senddiag utility.
 .PP
 In the 2.4 series of Linux kernels the given device must be
 a SCSI generic (sg) device. In the 2.5 series block devices (e.g. disks
diff --git a/sg_logs.c b/sg_logs.c
index 2c8bd4e..a5f0f2a 100644
--- a/sg_logs.c
+++ b/sg_logs.c
@@ -12,7 +12,7 @@
 #include "sg_err.h"
 
 /* A utility program for the Linux OS SCSI generic ("sg") device driver.
-*  Copyright (C) 2000-2002 D. Gilbert
+*  Copyright (C) 2000-2003 D. Gilbert
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
@@ -22,7 +22,7 @@
    
 */
 
-static char * version_str = "0.10 20020228";
+static char * version_str = "0.21 20030513";
 
 #define ME "sg_logs: "
 
@@ -75,6 +75,10 @@
         perror("SG_IO (log sense) error");
         return -1;
     }
+#if 0
+    printf("SG_IO ioctl: status=%d, info=%d, sb_len_wr=%d\n", 
+	   io_hdr.status, io_hdr.info, io_hdr.sb_len_wr);
+#endif
     res = sg_err_category3(&io_hdr);
     switch (res) {
     case SG_ERR_CAT_CLEAN:
@@ -84,7 +88,7 @@
 	if (noisy) {
 	    char ebuff[EBUFF_SZ];
 	    snprintf(ebuff, EBUFF_SZ, ME "ppc=%d, sp=%d, "
-	    	     "pc=%d, page_code=%x, paramp=%x ", ppc, sp, pc, 
+	    	     "pc=%d, page_code=%x, paramp=%x\n    ", ppc, sp, pc, 
 		     pg_code, paramp);
             sg_chk_n_print3(ebuff, &io_hdr);
 	}
@@ -94,16 +98,21 @@
 
 static void usage()
 {
-    printf("Usage: 'sg_logs [-l] [-h] [-ppc] [-sp] [-p=<page_number>] "
-    	   " [-c=<page_control] [-paramp=<parameter_pointer> [-V]"
-	   " <sg_device>'\n"
-	   " where -l   list supported log pages\n"
+    printf("Usage: 'sg_logs [-a] [-c=<page_control] [-h] [-l] "
+	   "[-p=<page_number>]\n                [-p=<page_number>] "
+	   " [-paramp=<parameter_pointer> [-ppc] [-sp]\n"
+	   "                [-t] [-V] <sg_device>'\n"
+	   " where -a   output all log pages\n"
+	   "       -c=<page_control> page control(PC) (default: 1)\n"
+	   "             (0 [current threshhold], 1 [current cumulative]\n"
+	   "              2 [default threshhold], 3 [default cumulative])\n"
 	   "       -h   output in hex\n"
-	   "       -ppc set the PPC bit (def: 0)\n"
-	   "       -sp  set the PPC bit (def: 0)\n"
+	   "       -l   list supported log page names\n"
 	   "       -p=<page_code> page code (in hex)\n"
-	   "       -c=<page_control> page control (def: 0 (current))\n"
-	   "       -paramp=<parameter_pointer> (def: 0)\n"
+	   "       -paramp=<parameter_pointer> (in hex) (def: 0)\n"
+	   "       -ppc set the Parameter Pointer Control (PPC) bit (def: 0)\n"
+	   "       -sp  set the Saving Parameters (SP) bit (def: 0)\n"
+	   "       -t   outputs temperature log page (0xd)\n"
 	   "       -V   output version string\n"
 	   "       -?   output this usage message\n");
 }
@@ -161,6 +170,395 @@
     }
 }
 
+static void show_page_name(int page_no)
+{
+    switch (page_no) {
+    case 0x0 : printf("    0x00    Supported log pages\n"); break;
+    case 0x1 : printf("    0x01    Buffer over-run/under-run\n"); break;
+    case 0x2 : printf("    0x02    Error counters (write)\n"); break;
+    case 0x3 : printf("    0x03    Error counters (read)\n"); break;
+    case 0x4 : printf("    0x04    Error counters (read reverse)\n"); break;
+    case 0x5 : printf("    0x05    Error counters (verify)\n"); break;
+    case 0x6 : printf("    0x06    Non-medium errors\n"); break;
+    case 0x7 : printf("    0x07    Last n error events\n"); break;
+    case 0x8 : printf("    0x08    Format status (sbc2)\n"); break;
+    case 0xb : printf("    0x0b    Last n deferred errors of "
+		"asynchronous events\n"); break;
+    case 0xc : printf("    0x0c    Sequential Access (ssc-2)\n"); break;
+    case 0xd : printf("    0x0d    Temperature\n"); break;
+    case 0xe : printf("    0x0e    Start-stop cycle counter\n"); break;
+    case 0xf : printf("    0x0f    Application client\n"); break;
+    case 0x10 : printf("    0x10    Self-test results\n"); break;
+    case 0x18 : printf("    0x18    Protocol specific port\n"); break;
+    case 0x2e : printf("    0x2e    Tape alerts (ssc-2)\n"); break;
+    case 0x2f : printf("    0x2f    Informational exceptions (SMART)\n"); break;
+    default: printf("    0x%.2x\n", page_no); break;
+    }
+}
+
+static void show_buffer_under_overrun_page(unsigned char * resp, int len)
+{
+    int k, j, num, pl, count_basis, cause;
+    unsigned char * ucp;
+    unsigned char * xp;
+    unsigned long long ull;
+
+    printf("Buffer over-run/under-run page\n");
+    num = len - 4;
+    ucp = &resp[0] + 4;
+    while (num > 3) {
+	pl = ucp[3] + 4;
+    	count_basis = (ucp[1] >> 5) & 0x7;
+	printf("  Count basis: ");
+	switch (count_basis) {
+	case 0 : printf("undefined"); break;
+	case 1 : printf("per command"); break;
+	case 2 : printf("per failed reconnect"); break;
+	case 3 : printf("per unit of time"); break;
+	default: printf("reserved [0x%x]", count_basis); break;
+	}
+    	cause = (ucp[1] >> 1) & 0xf;
+	printf(", Cause: ");
+	switch (cause) {
+	case 0 : printf("bus busy"); break;
+	case 1 : printf("transfer rate too slow"); break;
+	default: printf("reserved [0x%x]", cause); break;
+	}
+	printf(", Type: ");
+	if (ucp[1] & 1)
+	    printf("over-run");
+	else
+	    printf("under-run");
+	printf(", count");
+	k = pl - 4;
+	xp = ucp + 4;
+	if (k > sizeof(ull)) {
+	    xp += (k - sizeof(ull));
+	    k = sizeof(ull);
+	}
+	ull = 0;
+	for (j = 0; j < k; ++j) {
+	    if (j > 0)
+	    	ull <<= 8;
+	    ull |= xp[j];
+	}
+	printf(" = %llu\n", ull);
+	num -= pl;
+	ucp += pl;
+    }
+}
+
+static void show_error_counter_page(unsigned char * resp, int len)
+{
+    int k, j, num, pl, pc;
+    unsigned char * ucp;
+    unsigned char * xp;
+    unsigned long long ull;
+
+    switch(resp[0]) {
+    case 2:
+    	printf("Write error counter page\n");
+	break;
+    case 3:
+    	printf("Read error counter page\n");
+	break;
+    case 4:
+    	printf("Read Reverse error counter page\n");
+	break;
+    case 5:
+    	printf("Verify error counter page\n");
+	break;
+    default:
+    	printf("expecting error counter page, got page=0x%x\n", resp[0]);
+	return;
+    }
+    num = len - 4;
+    ucp = &resp[0] + 4;
+    while (num > 3) {
+    	pc = (ucp[0] << 8) | ucp[1];
+	pl = ucp[3] + 4;
+	switch (pc) {
+	case 0: printf("  Errors corrected without substantion delay"); break;
+	case 1: printf("  Errors corrected with possible delays"); break;
+	case 2: printf("  Total operations"); break;
+	case 3: printf("  Total errors corrected"); break;
+	case 4: printf("  Total times correction algorithm processed"); break;
+	case 5: printf("  Total bytes processed"); break;
+	case 6: printf("  Total uncorrected errors"); break;
+	default: printf("  Reserved or vendor specific [0x%x]", pc); break;
+	}
+	k = pl - 4;
+	xp = ucp + 4;
+	if (k > sizeof(ull)) {
+	    xp += (k - sizeof(ull));
+	    k = sizeof(ull);
+	}
+	ull = 0;
+	for (j = 0; j < k; ++j) {
+	    if (j > 0)
+	    	ull <<= 8;
+	    ull |= xp[j];
+	}
+	printf(" = %llu\n", ull);
+	num -= pl;
+	ucp += pl;
+    }
+}
+
+static void show_non_medium_error_page(unsigned char * resp, int len)
+{
+    int k, j, num, pl, pc;
+    unsigned char * ucp;
+    unsigned char * xp;
+    unsigned long long ull;
+
+    printf("Non-medium error page\n");
+    num = len - 4;
+    ucp = &resp[0] + 4;
+    while (num > 3) {
+    	pc = (ucp[0] << 8) | ucp[1];
+	pl = ucp[3] + 4;
+	switch (pc) {
+	case 0:
+	    printf("  Non-medium error count"); break;
+	default: 
+	    if (pc <= 0x7fff)
+		printf("  Reserved [0x%x]", pc);
+	    else
+		printf("  Vendor specific [0x%x]", pc);
+	    break;
+	}
+	k = pl - 4;
+	xp = ucp + 4;
+	if (k > sizeof(ull)) {
+	    xp += (k - sizeof(ull));
+	    k = sizeof(ull);
+	}
+	ull = 0;
+	for (j = 0; j < k; ++j) {
+	    if (j > 0)
+	    	ull <<= 8;
+	    ull |= xp[j];
+	}
+	printf(" = %llu\n", ull);
+	num -= pl;
+	ucp += pl;
+    }
+}
+
+const char * self_test_code[] = {
+    "default", "background short", "background extended", "reserved",
+    "aborted background", "foreground short", "foreground extended",
+    "reserved"};
+
+const char * self_test_result[] = {
+    "completed without error", 
+    "aborted by SEND DIAGNOSTIC", 
+    "aborted other than by SEND DIAGNOSTIC", 
+    "unknown error, unable to complete", 
+    "self test completed with failure in test segment (which one unkown)", 
+    "first segment in self test failed", 
+    "second segment in self test failed", 
+    "another segment in self test failed", 
+    "reserved", "reserved", "reserved", "reserved", "reserved", "reserved",
+    "reserved",
+    "self test in progress"};
+
+static void show_self_test_page(unsigned char * resp, int len)
+{
+    int k, num, n, res;
+    unsigned char * ucp;
+    unsigned long long ull;
+
+    num = len - 4;
+    if (num < 0x190) {
+	printf("badly formed self-test results page\n");
+	return;
+    }
+    printf("Self-test results page\n");
+    for (k = 0, ucp = resp + 4; k < 20; ++k, ucp += 20 ) {
+	n = (ucp[6] << 8) | ucp[7];
+	if ((0 == n) && (0 == ucp[4]))
+	    break;
+	printf("  Parameter code=%d, accumulated power-on hours=%d\n",
+	       (ucp[0] << 8) | ucp[1], n);
+	printf("    self test code: %s [%d]\n",
+	       self_test_code[(ucp[4] >> 5) & 0x7], (ucp[4] >> 5) & 0x7);
+	res = ucp[4] & 0xf;
+	printf("    self test result: %s [%d]\n",
+	       self_test_result[res], res);
+	if (ucp[5])
+	    printf("    self-test number=%d\n", (int)ucp[5]);
+	ull = ucp[8]; ull <<= 8; ull |= ucp[9]; ull <<= 8; ull |= ucp[10];
+	ull <<= 8; ull |= ucp[11]; ull <<= 8; ull |= ucp[12];
+	ull <<= 8; ull |= ucp[13]; ull <<= 8; ull |= ucp[14];
+	ull <<= 8; ull |= ucp[14]; ull <<= 8; ull |= ucp[15];
+	if ((0xffffffffffffffffULL != ull) && (res > 0) && ( res < 0xf))
+	    printf("    address of first error=0x%llx\n", ull);
+	if (ucp[16] & 0xf)
+	    printf("    sense key=0x%x, asc=0x%x, asq=0x%x\n",
+		   ucp[16] & 0xf, ucp[17], ucp[18]);
+    }
+}
+
+static void show_Temperature_page(unsigned char * resp, int len, int hdr)
+{
+    int k, num, extra, pc;
+    unsigned char * ucp;
+
+    num = len - 4;
+    ucp = &resp[0] + 4;
+    if (num < 4) {
+	printf("badly formed Temperature log page\n");
+	return;
+    }
+    if (hdr)
+        printf("Temperature log page\n");
+    for (k = num; k > 0; k -= extra, ucp += extra) {
+	if (k < 3) {
+	    printf("short Temperature log page\n");
+	    return;
+	}
+	extra = ucp[3] + 4;
+	pc = ((ucp[0] << 8) & 0xff) + ucp[1];
+	if (0 == pc) {
+	    if (extra > 5) {
+		if (ucp[5] < 0xff)
+		    printf("  Current temperature= %d C\n", ucp[5]);
+		else
+		    printf("  Current temperature=<not available>\n");
+	    }
+	} else if (1 == pc) {
+	    if (extra > 5) {
+		if (ucp[5] < 0xff)
+		    printf("  Reference temperature= %d C\n", ucp[5]);
+		else
+		    printf("  Reference temperature=<not available>\n");
+	    }
+
+	}else {
+	    printf("  parameter code=0x%x, contents in hex:\n", pc);
+	    dStrHex((const char *)ucp, extra, 1);
+	}
+    }
+}
+
+static void show_IE_page(unsigned char * resp, int len, int full)
+{
+    int k, num, extra, pc;
+    unsigned char * ucp;
+
+    num = len - 4;
+    ucp = &resp[0] + 4;
+    if (num < 4) {
+	printf("badly formed Informational Exceptions log page\n");
+	return;
+    }
+    if (full)
+        printf("Informational Exceptions log page\n");
+    for (k = num; k > 0; k -= extra, ucp += extra) {
+	if (k < 3) {
+	    printf("short Informational Exceptions log page\n");
+	    return;
+	}
+	extra = ucp[3] + 4;
+	pc = ((ucp[0] << 8) & 0xff) + ucp[1];
+	if (0 == pc) {
+	    if (extra > 5) {
+		if (full)
+	            printf("  IE asc=0x%x, ascq=0x%x", ucp[4], ucp[5]); 
+	        if (extra > 6) {
+		    if (full)
+		        printf(",");
+		    if (ucp[6] < 0xff)
+	                printf("  Current temperature=%d C", ucp[6]);
+		    else
+	                printf("  Current temperature=<not available>");
+		}
+	        printf("\n");
+	    }
+	} else if (full) {
+	    printf("  parameter code=0x%x, contents in hex:\n", pc);
+	    dStrHex((const char *)ucp, extra, 1);
+	}
+    }
+}
+
+static void show_ascii_page(unsigned char * resp, int len)
+{
+    int k, n, num;
+
+    if (len < 0) {
+    	printf("response has bad length\n");
+    	return;
+    }
+    num = len - 4;
+    switch (resp[0]) {
+    case 0:
+    	printf("Supported pages:\n");
+	for (k = 0; k < num; ++k)
+	    show_page_name((int)resp[4 + k]);
+	break;
+    case 0x1:
+    	show_buffer_under_overrun_page(resp, len);
+	break;
+    case 0x2:
+    case 0x3:
+    case 0x4:
+    case 0x5:
+    	show_error_counter_page(resp, len);
+	break;
+    case 0x6:
+    	show_non_medium_error_page(resp, len);
+	break;
+    case 0xd:
+	show_Temperature_page(resp, len, 1);
+	break;
+    case 0xe:
+    	if (len < 40) {
+	    printf("badly formed start-stop cycle counter page\n");
+	    break;
+	}
+	printf("Start-stop cycle counter page\n");
+	printf("  Date of manufacture, year: %.4s, week: %.2s\n", 
+	       &resp[8], &resp[12]); 
+	printf("  Accounting date, year: %.4s, week: %.2s\n", 
+	       &resp[18], &resp[22]); 
+	n = (resp[28] << 24) | (resp[29] << 16) | (resp[30] << 8) | resp[31];
+	printf("  Specified cycle count over device lifetime=%d\n", n);
+	n = (resp[36] << 24) | (resp[37] << 16) | (resp[38] << 8) | resp[39];
+	printf("  Accumulated start-stop cycles=%d\n", n);
+	break;
+    case 0x10:
+    	show_self_test_page(resp, len);
+	break;
+    case 0x2f:
+    	show_IE_page(resp, len, 1);
+	break;
+    default:
+    	printf("No ascii information for page=0x%x, here is hex:\n", resp[0]);
+	dStrHex((const char *)resp, len, 1);
+	break;
+    }
+}
+	
+static int fetchTemperature(int sg_fd, int do_hex, unsigned char * resp, 
+			    int max_len)
+{
+    int res = 0;
+
+    if (0 == do_logs(sg_fd, 0, 0, 1, 0xd, 0, resp, max_len, 0))
+    	show_Temperature_page(resp, (resp[2] << 8) + resp[3] + 4, 0);
+    else if (0 == do_logs(sg_fd, 0, 0, 1, 0x2f, 0, resp, max_len, 0))
+    	show_IE_page(resp, (resp[2] << 8) + resp[3] + 4, 0);
+    else {
+	printf("Unable to find temperature in either log page (temperature "
+	       "or IE)\n");
+	res = 1;
+    }
+    close(sg_fd);
+    return res;
+}
 
 
 int main(int argc, char * argv[])
@@ -171,15 +569,15 @@
     unsigned char rsp_buff[MX_ALLOC_LEN];
     unsigned int u;
     int pg_code = 0;
-    int pc = 0;
+    int pc = 1;	/* N.B. some disks only give data for current cumulative */
     int paramp = 0;
     int do_list = 0;
     int do_ppc = 0;
     int do_sp = 0;
     int do_hex = 0;
-    int oflags = O_RDWR;
-    struct sg_scsi_id a_sid;
-    int scsi_ptype;
+    int do_all = 0;
+    int do_temp = 0;
+    int oflags = O_RDWR | O_NONBLOCK;
 
     for (k = 1; k < argc; ++k) {
         if (0 == strncmp("-p=", argv[k], 3)) {
@@ -215,6 +613,10 @@
 	    do_ppc = 1;
         else if (0 == strcmp("-sp", argv[k]))
 	    do_sp = 1;
+        else if (0 == strcmp("-a", argv[k]))
+	    do_all = 1;
+        else if (0 == strcmp("-t", argv[k]))
+	    do_temp = 1;
         else if (0 == strcmp("-h", argv[k]))
 	    do_hex = 1;
         else if (0 == strcmp("-?", argv[k])) {
@@ -255,44 +657,56 @@
         close(sg_fd);
         return 1;
     }
-    if (ioctl(sg_fd, SG_GET_SCSI_ID, &a_sid) < 0) {
-        printf(ME "ioctl(SG_GET_SCSI_ID) failed, errno=%d\n", errno);
-        close(sg_fd);
-        return 1;
-    }
-    scsi_ptype = a_sid.scsi_type;
-    if (do_list)
+    if (do_list || do_all)
     	pg_code = PG_CODE_ALL;
+    pg_len = 0;
+    if (1 == do_temp)
+	return fetchTemperature(sg_fd, do_hex, rsp_buff, MX_ALLOC_LEN);
 
     if (0 == do_logs(sg_fd, do_ppc, do_sp, pc, pg_code, paramp,
     		     rsp_buff, MX_ALLOC_LEN, 1))
     {
     	pg_len = (rsp_buff[2] << 8) + rsp_buff[3];
-    	printf("Returned log page code=0x%x,  page len=0x%x\n", 
-	       rsp_buff[0], pg_len);
 	if ((pg_len + 4) > MX_ALLOC_LEN) {
 	    printf("Only fetched %d bytes of response, truncate output\n",
-	    	   MX_ALLOC_LEN);
+		   MX_ALLOC_LEN);
 	    pg_len = MX_ALLOC_LEN - 4;
 	}
-	dStrHex((const char *)rsp_buff, pg_len + 4, 1);
-#if 0
-	{
-	    int para_len;
-	    unsigned char * ucp;
+	if (do_hex) {
+	    printf("Returned log page code=0x%x,  page len=0x%x\n", 
+		   rsp_buff[0], pg_len);
+	    dStrHex((const char *)rsp_buff, pg_len + 4, 1);
+	}
+	else
+	    show_ascii_page(rsp_buff, pg_len + 4);
+    }
+    if (do_all && (pg_len > 1)) {
+    	int my_len = pg_len - 1;
+	unsigned char parr[256];
 
-	    ucp = rsp_buff + 4;
-	    while (pg_len > 0) {
-		para_len = *(ucp + 3) + 4;
-		printf(">> parameter code=0x%x\n", (ucp[0] << 8) + ucp[1]); 
-		dStrHex((const char *)ucp, para_len, 1);
-		ucp += para_len;
-		pg_len -= para_len;
+	memcpy(parr, rsp_buff + 5, my_len);
+	for (k = 0; k < my_len; ++k) {
+	    printf("\n");
+	    pg_code = parr[k];
+	    if (0 == do_logs(sg_fd, do_ppc, do_sp, pc, pg_code, paramp,
+			     rsp_buff, MX_ALLOC_LEN, 1))
+	    {
+		pg_len = (rsp_buff[2] << 8) + rsp_buff[3];
+		if ((pg_len + 4) > MX_ALLOC_LEN) {
+		    printf("Only fetched %d bytes of response, truncate "
+		    	   "output\n", MX_ALLOC_LEN);
+		    pg_len = MX_ALLOC_LEN - 4;
+		}
+		if (do_hex) {
+		    printf("Returned log page code=0x%x,  page len=0x%x\n", 
+			   rsp_buff[0], pg_len);
+		    dStrHex((const char *)rsp_buff, pg_len + 4, 1);
+		}
+		else
+		    show_ascii_page(rsp_buff, pg_len + 4);
 	    }
 	}
-#endif
     }
-
     close(sg_fd);
     return 0;
 }
diff --git a/sg_map.c b/sg_map.c
index 8043d4f..47d69a3 100644
--- a/sg_map.c
+++ b/sg_map.c
@@ -11,7 +11,7 @@
 #include "sg_err.h"  /* needed for INQUIRY, if problematic then remove */
 
 /* Utility program for the Linux OS SCSI generic ("sg") device driver.
-*  Copyright (C) 2000,2001 D. Gilbert
+*  Copyright (C) 2000-2002 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 @@
 
    Note: This program requires sg version 2 or better.
 
-   Version 0.15 20010819
+   Version 0.16 20021211
 	- additions for osst [Kurt Garloff <garloff@suse.de>]
 */
 
@@ -363,6 +363,9 @@
 
     for (k = 0, res = 0; (k < max_dev)  && (num_errors < MAX_ERRORS);
          ++k, res = (sg_fd >= 0) ? close(sg_fd) : 0) {
+
+/* ignore close() errors */
+#if 0
         if (res < 0) {
             snprintf(ebuff, EBUFF_SZ, "Error closing %s ", fname);
             perror("sg_map: close error");
@@ -373,6 +376,7 @@
 	    sg_fd = 0;
 #endif
         }
+#endif
         make_dev_name(fname, leadin, k, do_numeric);
 #ifdef DEBUG
         printf ("Trying %s: ", fname);
diff --git a/sg_modes.8 b/sg_modes.8
index dd53c88..cc00205 100644
--- a/sg_modes.8
+++ b/sg_modes.8
@@ -1,16 +1,17 @@
-.TH SG_MODES "8" "March 2003" "sg3_utils-1.03" SG3_UTILS
+.TH SG_MODES "8" "April 2003" "sg3_utils-1.04" SG3_UTILS
 .SH NAME
 sg_modes \- reads SCSI MODE SENSE pages
 .SH SYNOPSIS
 .B sg_modes
 [\fI-a\fR] [\fI-c=<page_control>\fR] [\fI-d\fR] [\fI-h\fR] [\fI-l\fR]
-[\fI-p=<page_code>\fR] [\fI-V\fR] [\fI-6\fR] [\fI-?\fR] [\fI<sg_device>\fR]
+[\fI-p=<page_code>\fR] [\fI-subp=<sub_page_code>\fR] [\fI-V\fR] 
+[\fI-6\fR] [\fI-?\fR] [\fI<sg_device>\fR]
 .SH DESCRIPTION
 .\" Add any additional description here
 .PP
-This command sends a MODE SENSE SCSI command (the 10 byte variant)
-to the given device and then outputs the response. There is no facility
-to change the page or descriptor data with this command.
+This utility sends a MODE SENSE SCSI command (the 10 byte variant
+by default) to the given device and then outputs the response. There
+is no facility to change the page or descriptor data with this utility.
 .TP
 -a
 lists all the mode pages supported by the device.
@@ -37,24 +38,28 @@
 .TP
 -p=<page_code>
 page code to fetch. Should be a hexadecimal number between 0 and 0x3f
-inclusive.
+inclusive. The default value is 0.
+.TP
+-subp=<sub_page_code>
+sub page code to fetch. Should be a hexadecimal number between 0 and 
+0xff inclusive. The default value is 0.
 .TP
 -V
 print out version string
 .TP
 -6
-by default this command sends out a 10 byte MODE SENSE command. However
+by default this utility sends out a 10 byte MODE SENSE command. However
 some SCSI devices only support 6 byte MODE SENSE commands (e.g. SCSI-2
 tape drives). This parameter forces the use of 6 byte MODE SENSE commands.
 .TP
 -?
 output usage message. Ignore all other parameters.
 .PP
-If the normal sg_modes command fails with "illegal command
+If the normal sg_modes utility fails with "illegal command
 operation code" then try the "-6" parameter. To alter page settings
 see the program listed in the "See Also" section below.
 .PP
-This command performs a SCSI INQUIRY command to determine the peripheral
+This utility performs a SCSI INQUIRY command to determine the peripheral
 type of the device (e.g. 0 -> Direct Access Device (disk)) prior to
 sending a MODE SENSE command. This helps in decoding the block
 descriptor and mode pages.
@@ -74,3 +79,7 @@
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 .SH "SEE ALSO"
 .B sginfo(this package), sgmode(scsirastools), scsiinfo(see net), scu(see net)
+.B seatools(see seagate)
+.PP
+All these utilities offer some facility to change mode page (or block
+descriptor) parameters.
diff --git a/sg_modes.c b/sg_modes.c
index 5ba811d..bb8cefa 100644
--- a/sg_modes.c
+++ b/sg_modes.c
@@ -7,22 +7,23 @@
 #include <errno.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
-#include <sys/stat.h>
 #include "sg_include.h"
 #include "sg_err.h"
 
 /* A utility program for the Linux OS SCSI generic ("sg") device driver.
-*  Copyright (C) 2000-2002 D. Gilbert
+*  Copyright (C) 2000-2003 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)
 *  any later version.
 
    This program outputs information provided by a SCSI MODE SENSE command.
+   Does 10 byte MODE SENSE commands by default, Trent Piepho added a "-6"
+   switch for force 6 byte mode sense commands.
    
 */
 
-static char * version_str = "0.11 20020227";
+static char * version_str = "0.18 20030507";
 
 #define ME "sg_modes: "
 
@@ -31,8 +32,12 @@
 #define SENSE_BUFF_LEN 32       /* Arbitrary, could be larger */
 #define DEF_TIMEOUT 60000       /* 60,000 millisecs == 60 seconds */
 
+#define MODE_SENSE6_CMD      0x1a
+#define MODE_SENSE6_CMDLEN   6
 #define MODE_SENSE10_CMD     0x5a
 #define MODE_SENSE10_CMDLEN  10
+#define INQUIRY_CMD     0x12
+#define INQUIRY_CMDLEN  6
 #define MX_ALLOC_LEN (1024 * 4)
 
 #define PG_CODE_ALL 0x3f
@@ -40,8 +45,47 @@
 #define EBUFF_SZ 256
 
 
-static int do_modes(int sg_fd, int dbd, int pc, int pg_code, 
-		  void * resp, int mx_resp_len, int noisy)
+/* Returns 0 when successful, else -1 */
+static int do_simple_inq(int sg_fd, void * resp, int mx_resp_len, int noisy)
+{
+    int res;
+    unsigned char inqCmdBlk[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    sg_io_hdr_t io_hdr;
+
+    inqCmdBlk[4] = (unsigned char)mx_resp_len;
+    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+    io_hdr.interface_id = 'S';
+    io_hdr.cmd_len = sizeof(inqCmdBlk);
+    io_hdr.mx_sb_len = sizeof(sense_b);
+    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+    io_hdr.dxfer_len = mx_resp_len;
+    io_hdr.dxferp = resp;
+    io_hdr.cmdp = inqCmdBlk;
+    io_hdr.sbp = sense_b;
+    io_hdr.timeout = DEF_TIMEOUT;
+
+    if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+        perror("SG_IO (inquiry) error");
+        return -1;
+    }
+    res = sg_err_category3(&io_hdr);
+    switch (res) {
+    case SG_ERR_CAT_CLEAN:
+    case SG_ERR_CAT_RECOVERED:
+	return 0;
+    default:
+	if (noisy) {
+	    char ebuff[EBUFF_SZ];
+	    snprintf(ebuff, EBUFF_SZ, "Inquiry error ");
+            sg_chk_n_print3(ebuff, &io_hdr);
+	}
+	return -1;
+    }
+}
+
+static int do_modes(int sg_fd, int dbd, int pc, int pg_code, int sub_pg_code,
+		  void * resp, int mx_resp_len, int noisy, int mode6)
 {
     int res;
     unsigned char modesCmdBlk[MODE_SENSE10_CMDLEN] = 
@@ -51,16 +95,23 @@
 
     modesCmdBlk[1] = (unsigned char)(dbd ? 0x8 : 0);
     modesCmdBlk[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
-    if (mx_resp_len > 0xffff) {
+    modesCmdBlk[3] = (unsigned char)(sub_pg_code & 0xff);
+    if (mx_resp_len > (mode6?0xff:0xffff)) {
     	printf( ME "mx_resp_len too big\n");
 	return -1;
     }
-    modesCmdBlk[7] = (unsigned char)((mx_resp_len >> 8) & 0xff);
-    modesCmdBlk[8] = (unsigned char)(mx_resp_len & 0xff);
+    if(mode6) {
+	modesCmdBlk[0] = MODE_SENSE6_CMD;
+	modesCmdBlk[4] = (unsigned char)(mx_resp_len & 0xff);
+    } else {
+	modesCmdBlk[7] = (unsigned char)((mx_resp_len >> 8) & 0xff);
+	modesCmdBlk[8] = (unsigned char)(mx_resp_len & 0xff);
+    }
 
     memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+    memset(sense_b, 0, sizeof(sense_b));
     io_hdr.interface_id = 'S';
-    io_hdr.cmd_len = sizeof(modesCmdBlk);
+    io_hdr.cmd_len = mode6 ? MODE_SENSE6_CMDLEN : MODE_SENSE10_CMDLEN;
     io_hdr.mx_sb_len = sizeof(sense_b);
     io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
     io_hdr.dxfer_len = mx_resp_len;
@@ -81,24 +132,233 @@
     default:
 	if (noisy) {
 	    char ebuff[EBUFF_SZ];
-	    snprintf(ebuff, EBUFF_SZ, "Mode sense error, dbd=%d, "
-	    	     "pc=%d, page_code=%x ", dbd, pc, pg_code);
+	    snprintf(ebuff, EBUFF_SZ, "Mode sense error, dbd=%d "
+	    	     "pc=%d page_code=%x sub_page_code=%x\n     ", dbd, pc, 
+		     pg_code, sub_pg_code);
             sg_chk_n_print3(ebuff, &io_hdr);
 	}
+	if ((0x70 == (0x7f & sense_b[0])) && (0x20 == sense_b[12]) &&
+	    (0x0 == sense_b[13])) {
+	    if (mode6)
+	        fprintf(stderr, ">>>>>> drop '-6' switch and try again with "
+			        "a 10 byte MODE SENSE\n");
+	    else
+	        fprintf(stderr, ">>>>>> add '-6' switch and try again with "
+				"a 6 byte MODE SENSE\n");
+	}
 	return -1;
     }
 }
 
+const char * scsi_ptype_strs[] = {
+    "disk",
+    "tape",
+    "printer",
+    "processor",
+    "write once optical disk",
+    "cd/dvd",
+    "scanner",
+    "optical memory device",
+    "medium changer",
+    "communications",
+    "graphics",
+    "graphics",
+    "storage array controller",
+    "enclosure services device",
+    "simplified direct access device",
+    "optical card reader/writer device",
+};
+
+const char * get_ptype_str(int scsi_ptype)
+{
+    int num = sizeof(scsi_ptype_strs) / sizeof(scsi_ptype_strs[0]);
+
+    return (scsi_ptype < num) ? scsi_ptype_strs[scsi_ptype] : "";
+}
+
+struct page_code_desc {
+    int page_code;
+    const char * desc;
+};
+
+static struct page_code_desc pc_desc_all[] = {
+    {0x0, "Unit Attention condition [vendor: page format not required]"},
+    {0x2, "Disconnect-Reconnect"},
+    {0xa, "Control"},
+    {0x15, "Extended"},
+    {0x16, "Extended device-type specific"},
+    {0x18, "Protocol specific LUN"},
+    {0x19, "Protocol specific port"},
+    {0x1a, "Power condition"},
+    {0x1c, "Informational exceptions control"},
+    {0x3f, "[yields all supported pages]"},
+};
+
+static struct page_code_desc pc_desc_disk[] = {
+    {0x1, "Read-Write error recovery"},
+    {0x3, "Format"},
+    {0x4, "Rigid disk geometry"},
+    {0x5, "Flexible geometry"},
+    {0x7, "Verify error recovery"},
+    {0x8, "Caching"},
+    {0x9, "Peripheral device (spc-2 ?)"},
+    {0xb, "Medium types supported"},
+    {0xc, "Notch and partition"},
+    {0xd, "Power condition (obsolete)"},
+    {0x10, "XOR control"},
+};
+
+static struct page_code_desc pc_desc_tape[] = {
+    {0xf, "Data Compression"},
+    {0x10, "Device config"},
+    {0x11, "Medium Partition [1]"},
+    {0x12, "Medium Partition [2]"},
+    {0x13, "Medium Partition [3]"},
+    {0x14, "Medium Partition [4]"},
+    {0x1c, "Informational exceptions control (tape version)"},
+};
+
+static struct page_code_desc pc_desc_cddvd[] = {
+    {0x1, "Read-Write error recovery"},
+    {0x3, "MRW"},
+    {0x5, "Write parameters"},
+    {0xd, "CD device parameters (obsolete)"},
+    {0xe, "CD audio"},
+    {0x1a, "Power condition"},
+    {0x1c, "Fault/failure reporting control"},
+    {0x1d, "Timeout and protect"},
+    {0x2a, "MM capabilities and mechanical status (obsolete)"},
+};
+
+static struct page_code_desc pc_desc_smc[] = {
+    {0x1d, "Element address assignment"},
+    {0x1e, "Transport geometry parameters"},
+    {0x1f, "Device capabilities"},
+};
+
+static struct page_code_desc pc_desc_scc[] = {
+    {0x1b, "LUN mapping"},
+};
+
+static struct page_code_desc pc_desc_ses[] = {
+    {0x14, "Enclosure services management"},
+};
+
+struct page_code_desc * find_mode_page_table(int scsi_ptype, int * size)
+{
+    switch (scsi_ptype)
+    {
+	case 0:		/* disk (direct access) type devices */
+	case 4:
+	case 7:
+	case 0xe:
+	    *size = sizeof(pc_desc_disk) / sizeof(pc_desc_disk[0]);
+	    return &pc_desc_disk[0];
+	case 1:		/* tape devices */
+	case 2:
+	    *size = sizeof(pc_desc_tape) / sizeof(pc_desc_tape[0]);
+	    return &pc_desc_tape[0];
+	case 5:		/* cd/dvd devices */
+	    *size = sizeof(pc_desc_cddvd) / sizeof(pc_desc_cddvd[0]);
+	    return &pc_desc_cddvd[0];
+	case 8:		/* medium changer devices */
+	    *size = sizeof(pc_desc_smc) / sizeof(pc_desc_smc[0]);
+	    return &pc_desc_smc[0];
+	case 0xc:	/* storage array devices */
+	    *size = sizeof(pc_desc_scc) / sizeof(pc_desc_scc[0]);
+	    return &pc_desc_scc[0];
+	case 0xd:	/* enclosure services devices */
+	    *size = sizeof(pc_desc_ses) / sizeof(pc_desc_ses[0]);
+	    return &pc_desc_ses[0];
+    }
+    *size = 0;
+    return NULL;
+}
+
+const char * find_page_code_desc(int page_num, int scsi_ptype)
+{
+    int k;
+    int num;
+    const struct page_code_desc * pcdp;
+
+    pcdp = find_mode_page_table(scsi_ptype, &num);
+    if (pcdp) {
+        for (k = 0; k < num; ++k, ++pcdp) {
+	    if (page_num == pcdp->page_code)
+	        return pcdp->desc;
+	    else if (page_num < pcdp->page_code)
+	        break;
+	}
+    }
+    pcdp = &pc_desc_all[0];
+    num = sizeof(pc_desc_all) / sizeof(pc_desc_all[0]);
+    for (k = 0; k < num; ++k, ++pcdp) {
+	if (page_num == pcdp->page_code)
+	    return pcdp->desc;
+	else if (page_num < pcdp->page_code)
+	    break;
+    }
+    return NULL;
+}
+
+static void list_page_codes(int scsi_ptype)
+{
+    int k;
+    int num = sizeof(pc_desc_all) / sizeof(pc_desc_all[0]);
+    const struct page_code_desc * pcdp = &pc_desc_all[0];
+    int num_ptype;
+    const struct page_code_desc * pcd_ptypep;
+
+    pcd_ptypep = find_mode_page_table(scsi_ptype, &num_ptype);
+    printf("Page_Code  Description\n");
+    for (k = 0; k < 0x3f; ++k) {
+	if (pcd_ptypep && (num_ptype > 0)) {
+	    if (k == pcd_ptypep->page_code) {
+	        printf(" 0x%02x      %s\n", pcd_ptypep->page_code, 
+		       pcd_ptypep->desc);   
+	        ++pcd_ptypep;
+		--num_ptype;
+		continue;
+	    } else if (k > pcd_ptypep->page_code) {
+	        pcd_ptypep++;
+		--num_ptype;
+	    }
+	}
+	if (pcdp && (num > 0)) {
+	    if (k == pcdp->page_code) {
+	        printf(" 0x%02x      %s\n", pcdp->page_code, pcdp->desc);   
+	        ++pcdp;
+		--num;
+		continue;
+	    } else if (k > pcdp->page_code) {
+	        pcdp++;
+		--num;
+	    }
+	}
+    }
+}
+
+static const char * pg_control_str_arr[] = {
+    "current",
+    "changeable",
+    "default",
+    "saved"};
+
 static void usage()
 {
-    printf("Usage: 'sg_modes [-a] [-h] [-p=<page_number>]"
-	   " [-c=<page_control] [-d] [-V]\n\t\t<sg_device>'\n"
+    printf("Usage: 'sg_modes [-a] [-c=<page_control] [-d] [-h]"
+	   " [-l] [-p=<page_number>]\n\t\t [-subp=<sub_page_code>] [-V] "
+	   "[-6] [<sg_device>]'\n"
 	   " where -a   get all mode pages\n"
-	   "       -h   output in hex\n"
-	   "       -p=<page_code> page code (in hex, def: 0)\n"
-	   "       -c=<page_control> page control (def: 0 (current))\n"
+	   "       -c=<page_control> page control (def: 0 [current],"
+	   " 1 [changeable],\n            2 [default], 3 [saved])\n"
 	   "       -d   disable block descriptors\n"
+	   "       -h   output in hex\n"
+	   "       -l   list common page codes\n"
+	   "       -p=<page_code> page code (in hex, def: 0)\n"
+	   "       -subp=<sub_page_code> (in hex, def: 0)\n"
 	   "       -V   output version string\n"
+	   "       -6   Use MODE SENSE(6) instead of MODE SENSE(10)\n"
 	   "       -?   output this usage message\n");
 }
 
@@ -159,17 +419,22 @@
 
 int main(int argc, char * argv[])
 {
-    int sg_fd, k, num, len, md_len, bd_len, longlba;
+    int sg_fd, k, num, len, md_len, bd_len, longlba, page_num;
     char * file_name = 0;
     char ebuff[EBUFF_SZ];
+    const char * descp;
     unsigned char rsp_buff[MX_ALLOC_LEN];
+    int rsp_buff_size = MX_ALLOC_LEN;
     unsigned int u;
     int pg_code = 0;
+    int sub_pg_code = 0;
     int pc = 0;
     int do_all = 0;
     int do_dbd = 0;
     int do_hex = 0;
-    int oflags = O_RDONLY;
+    int do_mode6 = 0;  /* Use MODE SENSE(6) instead of MODE SENSE(10) */
+    int do_list = 0;
+    int oflags = O_RDONLY | O_NONBLOCK;
     struct sg_scsi_id a_sid;
     int scsi_ptype, density_code_off;
     unsigned char * ucp;
@@ -178,16 +443,25 @@
     for (k = 1; k < argc; ++k) {
         if (0 == strncmp("-p=", argv[k], 3)) {
             num = sscanf(argv[k] + 3, "%x", &u);
-            if ((1 != num) || (pg_code > 63)) {
+            if ((1 != num) || (u > 63)) {
                 printf("Bad page code after '-p' switch\n");
                 file_name = 0;
                 break;
             }
 	    pg_code = u;
         }
+	else if (0 == strncmp("-subp=", argv[k], 6)) {
+            num = sscanf(argv[k] + 6, "%x", &u);
+            if ((1 != num) || (u > 255)) {
+                printf("Bad sub page code after '-subp' switch\n");
+                file_name = 0;
+                break;
+            }
+	    sub_pg_code = u;
+        }
         else if (0 == strncmp("-c=", argv[k], 3)) {
             num = sscanf(argv[k] + 3, "%x", &u);
-            if ((1 != num) || (pc > 3)) {
+            if ((1 != num) || (u > 3)) {
                 printf("Bad page control after '-c' switch\n");
                 file_name = 0;
                 break;
@@ -200,9 +474,13 @@
 	    do_all = 1;
         else if (0 == strcmp("-h", argv[k]))
 	    do_hex = 1;
+	else if (0 == strcmp("-6", argv[k]))
+	    do_mode6 = 1;
+	else if (0 == strcmp("-l", argv[k]))
+	    do_list = 1;
         else if (0 == strcmp("-?", argv[k])) {
-	    file_name = 0;
-	    break;
+	    usage();
+	    return 0;
 	}
         else if (0 == strcmp("-V", argv[k])) {
 	    printf("Version string: %s\n", version_str);
@@ -222,10 +500,18 @@
         }
     }
     if (0 == file_name) {
+        if (do_list) {
+            printf("Assume 'disk' device type\n");
+	    list_page_codes(0);
+	    return 0;
+    	}
         usage();
         return 1;
     }
 
+    /* The 6 bytes command only allows up to 255 bytes of response data */
+    if(do_mode6) rsp_buff_size = 255;
+
     if ((sg_fd = open(file_name, oflags)) < 0) {
         snprintf(ebuff, EBUFF_SZ, ME "error opening file: %s", file_name);
         perror(ebuff);
@@ -239,32 +525,63 @@
         return 1;
     }
     if (ioctl(sg_fd, SG_GET_SCSI_ID, &a_sid) < 0) {
-        printf(ME "ioctl(SG_GET_SCSI_ID) failed, errno=%d\n", errno);
-        close(sg_fd);
-        return 1;
+	unsigned char inqBuff[36];
+
+	if (do_simple_inq(sg_fd, inqBuff, sizeof(inqBuff), 1)) {
+            printf(ME "%s doesn't respond to a SCSI INQUIRY\n", file_name);
+            close(sg_fd);
+            return 1;
+	}
+	scsi_ptype = inqBuff[0] & 0x1f; /* fetch peripheral device type */
     }
-    scsi_ptype = a_sid.scsi_type;
+    else
+	scsi_ptype = a_sid.scsi_type;
+    printf("  SCSI peripheral type: %s [0x%x] (from INQUIRY)\n", 
+	   get_ptype_str(scsi_ptype), scsi_ptype);
+    if (do_list) {
+	list_page_codes(scsi_ptype);
+	return 0;
+    }
     if (do_all)
     	pg_code = PG_CODE_ALL;
 
-    if (0 == do_modes(sg_fd, do_dbd, pc, pg_code, rsp_buff, MX_ALLOC_LEN, 1))
+    if (0 == do_modes(sg_fd, do_dbd, pc, pg_code, sub_pg_code, 
+		      rsp_buff, rsp_buff_size, 1, do_mode6))
     {
-    	printf("Mode parameter header:\n");
-	md_len = (rsp_buff[0] << 8) + rsp_buff[1];
-	bd_len = (rsp_buff[6] << 8) + rsp_buff[7];
-	longlba = rsp_buff[4] & 1;
+	int medium_type, specific, headerlen;
+
+    	printf("Mode parameter header from %s byte MODE SENSE:\n",
+	       (do_mode6 ? "6" : "10"));
+	if(do_mode6) {
+	    headerlen = 4;
+	    if (do_hex)
+		dStrHex((const char *)rsp_buff, headerlen, 1);
+	    md_len = rsp_buff[0]+1;
+	    bd_len = rsp_buff[3];
+	    medium_type = rsp_buff[1];
+	    specific = rsp_buff[2];
+	    longlba = 0; /* what is this field? */
+	} else {
+	    headerlen = 8;
+	    md_len = (rsp_buff[0] << 8) + rsp_buff[1] + 2;
+	    bd_len = (rsp_buff[6] << 8) + rsp_buff[7];
+	    medium_type = rsp_buff[2];
+	    specific = rsp_buff[3];
+	    longlba = rsp_buff[4] & 1;
+	}
+	if (do_hex)
+	    dStrHex((const char *)rsp_buff, headerlen, 1);
     	printf("  Mode data length=%d, medium type=0x%.2x, specific"
-	       " param=0x%.2x, longlba=%d\n", md_len, rsp_buff[2], 
-	       rsp_buff[3], longlba);
-        if ((md_len + 2) > MX_ALLOC_LEN) {
+	       " param=0x%.2x, longlba=%d\n", md_len, medium_type, 
+	       specific, longlba);
+        if (md_len > rsp_buff_size) {
             printf("Only fetched %d bytes of response, truncate output\n",
-                   MX_ALLOC_LEN);
-            md_len = MX_ALLOC_LEN - 2;
-	    if ((md_len + 6) > bd_len);
-		bd_len = MX_ALLOC_LEN - 8;
+                   rsp_buff_size);
+            md_len = rsp_buff_size;
+	    if (bd_len + headerlen > rsp_buff_size)
+		bd_len = rsp_buff_size - headerlen;
         }
-    	printf("  Block descriptor length=%d,  SCSI peripheral type=0x%x\n", 
-	       bd_len, scsi_ptype);
+    	printf("  Block descriptor length=%d\n", bd_len);
 	if (bd_len > 0) {
 	    len = 8;
 	    density_code_off = 0;
@@ -281,7 +598,7 @@
 	    else
 		printf("> General mode parameter block descriptors:\n");
 
-	    ucp = rsp_buff + 8;
+	    ucp = rsp_buff + headerlen;
 	    while (num > 0) {
 		printf("   Density code=0x%x\n", *(ucp + density_code_off));
 		dStrHex((const char *)ucp, len, 1);
@@ -290,18 +607,37 @@
 	    }
 	    printf("\n");
 	}
-	ucp = rsp_buff + bd_len + 8;
-	md_len -= bd_len + 6;
+	ucp = rsp_buff + bd_len + headerlen;	/* start of mode page(s) */
+	md_len -= bd_len + headerlen;		/* length of mode page(s) */
 	while (md_len > 0) { /* got mode page(s) */
 	    uc = *ucp;
+	    page_num = ucp[0] & 0x3f;
+	    if (do_hex)
+	        descp = NULL;
+	    else {
+	        descp = find_page_code_desc(page_num, scsi_ptype);
+		if (NULL == descp)
+		    snprintf(ebuff, EBUFF_SZ, "vendor[0x%x]", page_num);
+	    }
 	    if (uc & 0x40) {
 		len = (ucp[2] << 8) + ucp[3] + 4;
-		printf(">> page_code=0x%x, subpage code=0x%x\n", 
-		       ucp[0] & 0x3f, ucp[1]);
+		if (do_hex)
+		    printf(">> page_code=0x%x, subpage_code=0x%x, "
+			   "page_control=%d\n", page_num, ucp[1], pc);
+		else
+		    printf(">> page_code: %s, subpage_code=0x%x, "
+			   "page_control: %s\n",
+			   (descp ? descp: ebuff), ucp[1],
+			   pg_control_str_arr[pc]);
 	    }
 	    else {
 		len = ucp[1] + 2;
-		printf(">> page_code=0x%x\n", ucp[0] & 0x3f);
+		if (do_hex)
+		    printf(">> page_code=0x%x, page_control=%d\n", page_num,
+			   pc);
+		else
+		    printf(">> page_code: %s, page_control: %s\n", 
+		           (descp ? descp: ebuff), pg_control_str_arr[pc]);
 	    }
 	    dStrHex((const char *)ucp, len, 1);
 	    ucp += len;
diff --git a/sg_rbuf.8 b/sg_rbuf.8
index 98bcbb7..64c5d7e 100644
--- a/sg_rbuf.8
+++ b/sg_rbuf.8
@@ -1,4 +1,4 @@
-.TH SG_RBUF "5" "December 2001" "sg3_utils-0.96" SG_UTILS
+.TH SG_RBUF "8" "December 2001" "sg3_utils-0.96" SG_UTILS
 .SH NAME
 sg_rbuf \- reads data using SCSI READ BUFFER command
 .SH SYNOPSIS
diff --git a/sg_rbuf.c b/sg_rbuf.c
index 894c101..e5c02fc 100644
--- a/sg_rbuf.c
+++ b/sg_rbuf.c
@@ -4,6 +4,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <errno.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -14,7 +15,7 @@
 
 /* Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg")
    device driver.
-*  Copyright (C) 1999-2001 D. Gilbert
+*  Copyright (C) 1999-2002 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)
@@ -34,7 +35,7 @@
    The ability to time transfers internally (based on gettimeofday()) has
    been added with the '-t' option.
 
-   Version 4.72 (20011205)
+   Version 4.73 (20020520)
 */
 
 
@@ -51,6 +52,8 @@
 #define SG_FLAG_MMAP_IO 4
 #endif
 
+#define ME "sg_rbuf: "
+
 
 int main(int argc, char * argv[])
 {
@@ -119,7 +122,7 @@
         printf("         -d       requests dio ('-q' overrides it)\n");
         printf("         -m       requests mmap-ed IO (overrides -q, -d)\n");
         printf("         -t       time the data transfer\n");
-        printf("         -b=num   num is buff size to use (in KBytes)\n");
+        printf("         -b=num   num is buffer size to use (in KBytes)\n");
         printf("         -s=num   num is total size to read (in MBytes)\n");
         printf("                    default total size is 200 MBytes\n");
         printf("                    max total size is 4000 MBytes\n");
@@ -128,13 +131,13 @@
 
     sg_fd = open(file_name, O_RDONLY);
     if (sg_fd < 0) {
-        perror("sg_rbuf: open error");
+        perror(ME "open error");
         return 1;
     }
     /* Don't worry, being very careful not to write to a none-sg file ... */
     res = ioctl(sg_fd, SG_GET_VERSION_NUM, &k);
     if ((res < 0) || (k < 30000)) {
-        printf("sg_rbuf: not a sg device, or driver prior to 3.x\n");
+        printf(ME "not a sg device, or driver prior to 3.x\n");
         return 1;
     }
     if (do_mmap) {
@@ -142,7 +145,7 @@
     	do_quick = 0;
     }
     if (NULL == (rawp = malloc(512))) {
-	printf("sg_rbuf: out of memory (query)\n");
+	printf(ME "out of memory (query)\n");
 	return 1;
     }
     rbBuff = rawp;
@@ -164,7 +167,7 @@
     /* do normal IO to find RB size (not dio or mmap-ed at this stage) */
 
     if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
-        perror("sg_rbuf: SG_IO READ BUFFER descriptor error");
+        perror(ME "SG_IO READ BUFFER descriptor error");
         if (rawp) free(rawp);
         return 1;
     }
@@ -205,20 +208,24 @@
 	    k = ((k / psz) + 1) * psz;  /* round up to page size */
         res = ioctl(sg_fd, SG_SET_RESERVED_SIZE, &k);
         if (res < 0)
-            perror("sg_rbuf: SG_SET_RESERVED_SIZE error");
+            perror(ME "SG_SET_RESERVED_SIZE error");
     }
 
     if (do_mmap) {
 	rbBuff = mmap(NULL, buf_size, PROT_READ, MAP_SHARED, sg_fd, 0);
 	if (MAP_FAILED == rbBuff) {
-	    perror("sg_rbuf: error using mmap()");
+	    if (ENOMEM == errno)
+	    	printf(ME "mmap() out of memory, try a smaller "
+		       "buffer size than %d KB\n", buf_size / 1024);
+	    else
+		perror(ME "error using mmap()");
 	    return 1;
 	}
     }
     else { /* non mmap-ed IO */
 	rawp = malloc(buf_size + (do_dio ? psz : 0));
 	if (NULL == rawp) {
-	    printf("sg_rbuf: out of memory (data)\n");
+	    printf(ME "out of memory (data)\n");
 	    return 1;
 	}
 	if (do_dio)    /* align to page boundary */
@@ -266,7 +273,11 @@
             io_hdr.flags |= SG_FLAG_NO_DXFER;
 
         if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
-            perror("sg_rbuf: SG_IO READ BUFFER data error");
+	    if (ENOMEM == errno)
+	    	printf(ME "SG_IO data; out of memory, try a smaller "
+		       "buffer size than %d KB\n", buf_size / 1024);
+            else
+	    	perror(ME "SG_IO READ BUFFER data error");
             if (rawp) free(rawp);
             return 1;
         }
@@ -328,7 +339,7 @@
     if (rawp) free(rawp);
     res = close(sg_fd);
     if (res < 0) {
-        perror("sg_rbuf: close error");
+        perror(ME "close error");
         return 1;
     }
 #ifdef SG_DEBUG
diff --git a/sg_read.c b/sg_read.c
index 4545cbe..ce9f67f 100644
--- a/sg_read.c
+++ b/sg_read.c
@@ -48,7 +48,7 @@
 
 */
 
-static const char * version_str = "0.94 20020204";
+static const char * version_str = "0.95 20021207";
 
 #define DEF_BLOCK_SIZE 512
 #define DEF_BLOCKS_PER_TRANSFER 128
@@ -246,7 +246,6 @@
     unsigned char rdCmd[MAX_SCSI_CDBSZ];
     unsigned char senseBuff[SENSE_BUFF_LEN];
     sg_io_hdr_t io_hdr;
-    int res;
 
     if (sg_build_scsi_cdb(rdCmd, cdbsz, blocks, from_block)) {
         fprintf(stderr, ME "bad cdb build, from_block=%d, blocks=%d\n",
@@ -270,23 +269,13 @@
     else if (do_mmap)
         io_hdr.flags |= SG_FLAG_MMAP_IO;
 
-    while (((res = write(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
-           (EINTR == errno))
-        ;
-    if (res < 0) {
+    if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
         if (ENOMEM == errno)
             return 1;
-        perror("reading (wr) on sg device, error");
+        perror("reading (SG_IO) on sg device, error");
         return -1;
     }
 
-    while (((res = read(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
-           (EINTR == errno))
-        ;
-    if (res < 0) {
-        perror("reading (rd) on sg device, error");
-        return -1;
-    }
     switch (sg_err_category3(&io_hdr)) {
     case SG_ERR_CAT_CLEAN:
         break;
diff --git a/sg_readcap.8 b/sg_readcap.8
new file mode 100644
index 0000000..25caf17
--- /dev/null
+++ b/sg_readcap.8
@@ -0,0 +1,39 @@
+.TH SG_READCAP "8" "April 2003" "sg3_utils-1.03" SG3_UTILS
+.SH NAME
+sg_readcap \- calls a READ CAPACITY command on a SCSI device
+.SH SYNOPSIS
+.B sg_readcap 
+[\fI-lba=<block>\fR] [\fI-pmi\fR] [\fI-V\fR]
+<\fIdevice\fR>
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+The normal usage is to find the number of blocks (and block size)
+of the given device and output them.
+.TP
+-lba=<block>
+used in conjunction with "-pmi" option. This variant of READ CAPACITY will
+yield the last block address after "<block>" prior to a delay. For a
+disk, given a "<block>" address it yields the highest numbered block on
+the same cylinder (i.e. before the heads need to move). "<block>" is 
+in hex (from 0 to ffffffff) and defaults to zero.
+.TP
+-pmi
+partial medium indicator: for finding the next block address prior to
+some delay (e.g. head movement). In the absence of this switch, the
+total number of blocks and the block size of the device are output.
+.TP
+-V
+outputs version string then exits.
+.PP
+In the 2.4 series of Linux kernels the given device must be
+a SCSI generic (sg) device. In the 2.5 series block devices (e.g. disks
+and SCSI DVDs) can also be specified. For example "sg_readcap /dev/sda"
+will work in the 2.5 series kernels.
+.SH AUTHORS
+Written by Douglas Gilbert
+.SH COPYRIGHT
+Copyright \(co 1999-2003 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/sg_readcap.c b/sg_readcap.c
index 4ed05b7..810a63c 100644
--- a/sg_readcap.c
+++ b/sg_readcap.c
@@ -1,243 +1,62 @@
-#include <unistd.h>
-#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <unistd.h>
 #include <string.h>
+#include <fcntl.h>
 #include <errno.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
-#include <sys/stat.h>
 #include "sg_include.h"
 #include "sg_err.h"
 
 /* This code is does a SCSI READ CAPACITY command on the given device
    and outputs the result.
 
-*  Copyright (C) 1999 - 2002 D. Gilbert
+*  Copyright (C) 1999 - 2003 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)
 *  any later version.
 
-   This program is program should work on the 2.0, 2.2 and 2.4 series
-   of Linux kernels no matter which of those environments it was
-   compiled and built under.
-
-   Version 3.57 (20020114)
-
-10 byte READ CAPACITY command:
-[0x25][  |lu][    ][    ][    ][    ][    ][    ][cntrl] {ignore PMI mode}
+   This program will only work with Linux 2.4 kernels and later (i.e.
+   those that support the SG_IO ioctl). Another version of this program
+   that should work on the 2.0, 2.2 and 2.4 series of Linux kernels no 
+   matter which of those environments it was compiled and built under
+   can be found in the sg_utils package (e.g. sg_utils-1.02).
 
 */
 
-#ifndef SG_GET_RESERVED_SIZE
-#define SG_GET_RESERVED_SIZE 0x2272
-#endif
+static char * version_str = "3.60 20030430";
 
-#ifndef SG_SET_RESERVED_SIZE
-#define SG_SET_RESERVED_SIZE 0x2275
-#endif
+#define ME "sg_readcap: "
 
-#ifndef SG_GET_VERSION_NUM
-#define SG_GET_VERSION_NUM 0x2282
-#endif
-
-#ifndef SG_NEXT_CMD_LEN
-#define SG_NEXT_CMD_LEN 0x2283
-#endif
-
-#ifndef SG_MAX_SENSE
-#define SG_MAX_SENSE 16
-#endif
-
-#ifndef SG_IO
-#define SG_IO 0x2285
-#endif
-
-#ifndef SG_DXFER_FROM_DEV
-#define SG_DXFER_FROM_DEV -3
-#endif
-
-
-#define SG_RT_UNKN (-1)
-#define SG_RT_ORIG 0    /* original driver as found in 2.0.* kernels */
-#define SG_RT_NEW32 1   /* driver version 2.1.31 + 2.1.32 */
-#define SG_RT_NEW34 2   /* version >= 2.1.34 and < 3.0.0 */
-#define SG_RT_NEW_V3 3  /* version >= 3.0.0 */
-
-typedef struct sg_h_n  /* for "forward" compatibility case */
-{
-    int pack_len;    /* [o] reply_len (ie useless), ignored as input */
-    int reply_len;   /* [i] max length of expected reply (inc. sg_header) */
-    int pack_id;     /* [io] id number of packet (use ints >= 0) */
-    int result;      /* [o] 0==ok, else (+ve) Unix errno (best ignored) */
-    unsigned int twelve_byte:1;
-        /* [i] Force 12 byte command length for group 6 & 7 commands  */
-    unsigned int target_status:5;   /* [o] scsi status from target */
-    unsigned int host_status:8;     /* [o] host status (see "DID" codes) */
-    unsigned int driver_status:8;   /* [o] driver status+suggestion */
-    unsigned int other_flags:10;    /* unused */
-    unsigned char sense_buffer[SG_MAX_SENSE]; /* [o] Output in 3 cases:
-           when target_status is CHECK_CONDITION or
-           when target_status is COMMAND_TERMINATED or
-           when (driver_status & DRIVER_SENSE) is true. */
-} sg_h_n_t;      /* This structure is 36 bytes long on i386 */
-
-
-typedef struct m_sg_iovec /* same structure as used by readv() Linux system */
-{                         /* call. It defines one scatter-gather element. */
-    void * iov_base;            /* Starting address  */
-    size_t iov_len;             /* Length in bytes  */
-} M_sg_iovec_t;
-
-
-typedef struct m_sg_io_hdr
-{
-    int interface_id;           /* [i] 'S' for SCSI generic */
-    int dxfer_direction;        /* [i] data transfer direction  */
-    unsigned char cmd_len;      /* [i] SCSI command length ( <= 16 bytes) */
-    unsigned char mx_sb_len;    /* [i] max length to write to sbp */
-    unsigned short iovec_count; /* [i] 0 implies no scatter gather */
-    unsigned int dxfer_len;     /* [i] byte count of data transfer */
-    void * dxferp;              /* [i], [*io] points to data transfer memory
-                                              or scatter gather list */
-    unsigned char * cmdp;       /* [i], [*i] points to command to perform */
-    unsigned char * sbp;        /* [i], [*o] points to sense_buffer memory */
-    unsigned int timeout;       /* [i] MAX_UINT->no timeout (unit: millisec) */
-    unsigned int flags;         /* [i] 0 -> default, see SG_FLAG... */
-    int pack_id;                /* [i->o] unused internally */
-    void * usr_ptr;             /* [i->o] unused internally */
-    unsigned char status;       /* [o] scsi status */
-    unsigned char masked_status;/* [o] shifted, masked scsi status */
-    unsigned char msg_status;   /* [o] messaging level data (optional) */
-    unsigned char sb_len_wr;    /* [o] byte count actually written to sbp */
-    unsigned short host_status; /* [o] errors from host adapter */
-    unsigned short driver_status;/* [o] errors from software driver */
-    int resid;                  /* [o] dxfer_len - actual_transferred */
-    unsigned int duration;      /* [o] 0 -> time taken (unit: millisec) */
-    unsigned int info;          /* [o] auxiliary information */
-} M_sg_io_hdr_t;  /* 64 bytes long (on i386) */
-
-/* Use negative values to flag difference from original sg_header structure */
-
-static int open_scsi_dev_as_sg(const char * devname);
-
-#define OFF sizeof(struct sg_header)
+#define READCAP_TIMEOUT 60000	/* 60,000 milliseconds == 1 minute */
+#define SENSE_BUFF_SZ 64
 #define RCAP_REPLY_LEN 8
-#define RCAP_CMD_LEN 10
-#define MY_PACK_ID 1234
+
+#define EBUFF_SZ 256
 
 
-static void usage()
-{
-    printf("Usage: 'sg_readcap <scsi_device>'\n");
-}
-
-
-/* Return of 0 -> success, -1 -> failure */
-int readcap_sg_header(int sg_fd, int sg_which, int * last_sect, int * sect_sz)
-{
-    int cmd_len, ok;
-    unsigned char rcapCmdBlk [RCAP_CMD_LEN] =
-                                {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-    unsigned char rcapBuff[OFF + OFF + RCAP_REPLY_LEN]; /* Overkill */
-    int rcapInLen = OFF + sizeof(rcapCmdBlk);
-    int rcapOutLen = OFF + RCAP_REPLY_LEN;
-    unsigned char * buffp = rcapBuff + OFF;
-    struct sg_header * sghp = (struct sg_header *)rcapBuff;
-    sg_h_n_t * n_sghp = (sg_h_n_t *)rcapBuff;
-
-    sghp->reply_len = rcapOutLen;
-    sghp->pack_id = MY_PACK_ID;
-    sghp->twelve_byte = 0;
-    sghp->other_flags = 0;     /* some apps assume this is done ?!? */
-
-    switch (sg_which) {
-    case SG_RT_ORIG:
-        sghp->sense_buffer[0] = 0;
-        break;
-    case SG_RT_NEW32:
-        break;
-    case SG_RT_NEW34: /* this is optional, explicitly setting cmd length */
-        cmd_len = RCAP_CMD_LEN;
-        if (ioctl(sg_fd, SG_NEXT_CMD_LEN, &cmd_len) < 0) {
-            perror("sg_readcap: SG_NEXT_CMD_LEN error");
-            close(sg_fd);
-            return 1;
-        }
-        break;
-    default:
-        printf("Illegal state for sg_which=%d\n", sg_which);
-        return 1;
-    }
-    memcpy(rcapBuff + OFF, rcapCmdBlk, RCAP_CMD_LEN);
-
-    if (write(sg_fd, rcapBuff, rcapInLen) < 0) {
-        perror("sg_readcap: write error");
-        close(sg_fd);
-        return 1;
-    }
-
-    if (read(sg_fd, rcapBuff, rcapOutLen) < 0) {
-        perror("sg_readcap: read error");
-        close(sg_fd);
-        return 1;
-    }
-
-    /* now for the error processing */
-    ok = 0;
-    switch (sg_which) {
-    case SG_RT_ORIG:
-        if ((0 == sghp->result) && (0 == sghp->sense_buffer[0]))
-            ok = 1;
-        else if (sghp->sense_buffer[0])
-            sg_print_sense("READ CAPACITY command error", sghp->sense_buffer,
-                           SG_MAX_SENSE);
-        else /* sghp->result is != 0 */
-            printf("READ CAPACITY failed, sghp->result=%d\n", sghp->result);
-        break;
-    case SG_RT_NEW32:
-    case SG_RT_NEW34:
-        switch (sg_err_category(n_sghp->target_status, n_sghp->host_status,
-                n_sghp->driver_status, n_sghp->sense_buffer, SG_MAX_SENSE)) {
-        case SG_ERR_CAT_CLEAN:
-            ok = 1;
-            break;
-        case SG_ERR_CAT_RECOVERED:
-            printf("Recovered error on READ CAPACITY, continuing\n");
-            ok = 1;
-            break;
-        default: /* won't bother decoding other categories */
-            sg_chk_n_print("READ CAPACITY command error",
-                           n_sghp->target_status,
-                           n_sghp->host_status, n_sghp->driver_status,
-                           n_sghp->sense_buffer, SG_MAX_SENSE);
-            break;
-        }
-        break;
-    default:
-        break;
-    }
-
-    if (ok) { /* get result if it is available */
-        *last_sect = ((buffp[0] << 24) | (buffp[1] << 16) |
-                      (buffp[2] << 8) | buffp[3]);
-        *sect_sz = (buffp[4] << 24) | (buffp[5] << 16) |
-                   (buffp[6] << 8) | buffp[7];
-    }
-    return 0;
-}
-
-/* Return of 0 -> success, -1 -> failure */
-int readcap_sg_io_hdr(int sg_fd, int * last_sect, int * sect_sz)
+/* Performs a 10 byte READ CAPACITY command and fetches response. There is
+ * evidently a 16 byte READ CAPACITY command coming.
+ * Return of 0 -> success, -1 -> failure */
+int do_readcap_10(int sg_fd, int pmi, unsigned int lba, 
+		  unsigned int * last_sect, unsigned int * sect_sz)
 {
     int res;
-    unsigned char rcCmdBlk [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char rcCmdBlk[10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
     unsigned char rcBuff[RCAP_REPLY_LEN];
-    unsigned char sense_b[64];
-    M_sg_io_hdr_t io_hdr;
+    unsigned char sense_b[SENSE_BUFF_SZ];
+    sg_io_hdr_t io_hdr;
 
-    memset(&io_hdr, 0, sizeof(M_sg_io_hdr_t));
+    if (pmi) { /* lbs only valid when pmi set */
+	rcCmdBlk[8] |= 1;
+	rcCmdBlk[2] = (lba >> 24) & 0xff;
+	rcCmdBlk[3] = (lba >> 16) & 0xff;
+	rcCmdBlk[4] = (lba >> 8) & 0xff;
+	rcCmdBlk[5] = lba & 0xff;
+    }
+    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
     io_hdr.interface_id = 'S';
     io_hdr.cmd_len = sizeof(rcCmdBlk);
     io_hdr.mx_sb_len = sizeof(sense_b);
@@ -253,15 +72,11 @@
             perror("read_capacity (SG_IO) error");
             return -1;
         }
-        res = sg_err_category(io_hdr.masked_status, io_hdr.host_status,
-                io_hdr.driver_status, io_hdr.sbp, io_hdr.sb_len_wr);
+        res = sg_err_category3(&io_hdr);
         if (SG_ERR_CAT_MEDIA_CHANGED == res)
             continue;
         else if (SG_ERR_CAT_CLEAN != res) {
-            sg_chk_n_print("READ CAPACITY command error",
-                           io_hdr.masked_status,
-                           io_hdr.host_status, io_hdr.driver_status,
-                           io_hdr.sbp, io_hdr.sb_len_wr);
+            sg_chk_n_print3("READ CAPACITY command error", &io_hdr);
             return -1;
         }
         else
@@ -271,22 +86,49 @@
                  (rcBuff[2] << 8) | rcBuff[3]);
     *sect_sz = (rcBuff[4] << 24) | (rcBuff[5] << 16) |
                (rcBuff[6] << 8) | rcBuff[7];
-    // printf("last sector=%d, sector size=%d\n", *last_sect, *sect_sz);
     return 0;
 }
 
+void usage ()
+{
+    fprintf(stderr, "Usage:  sg_readcap [-lba=<block>] [-pmi] [-V] "
+            "<device>\n"
+	    " where    -lba=<block>: yields the last block prior to (head "
+	    "movement) delay\n"
+	    "                        after <block> [in hex (def: 0) "
+	    "valid with -pmi]\n"
+	    "          -pmi: partial medium indicator (without this switch "
+	    "shows total\n"
+	    "                disk capacity\n"
+	    "          -V: output version string and exit\n");
+}
 
 int main(int argc, char * argv[])
 {
-    int sg_fd, k, res;
-    int reserved_size;
-    int sg_which = SG_RT_UNKN;
-    int sg_version = 0;
-    int last_blk_addr, block_size;
+    int sg_fd, k, res, num;
+    unsigned int lba = 0;
+    int pmi = 0;
+    unsigned int last_blk_addr, block_size, u;
+    char ebuff[EBUFF_SZ];
     const char * file_name = 0;
 
     for (k = 1; k < argc; ++k) {
-        if (*argv[k] == '-') {
+	if (0 == strcmp("-pmi", argv[k]))
+	    pmi = 1;
+	else if (0 == strncmp("-lba=", argv[k], 5)) {
+	    num = sscanf(argv[k] + 5, "%x", &u);
+	    if (1 != num) {
+		printf("Bad value after '-lba' switch\n");
+		file_name = 0;
+		break;
+	    }
+	    lba = u;
+	}
+	else if (0 == strcmp("-V", argv[k])) {
+	    printf("Version string: %s\n", version_str);
+	    exit(0);
+	}
+	else if (*argv[k] == '-') {
             printf("Unrecognized switch: %s\n", argv[k]);
             break;
         }
@@ -297,159 +139,35 @@
         usage();
         return 1;
     }
-
-    sg_fd = open_scsi_dev_as_sg(file_name);
-    if (sg_fd < 0) {
-        if (-9999 == sg_fd)
-            printf("Failed trying to open SCSI device as an sg device\n");
-        else
-            perror("sg_readcap: open error");
-        return 1;
+    if ((0 == pmi) && (lba > 0)) {
+	fprintf(stderr, ME "lba can only be non-zero when pmi is set\n");
+	usage();
+	return 1;
     }
-
-    /* Run time selection code follows */
-    if (ioctl(sg_fd, SG_GET_RESERVED_SIZE, &reserved_size) < 0) {
-        reserved_size = SG_BIG_BUFF;
-        sg_which = SG_RT_ORIG;
+    if ((sg_fd = open(file_name, O_RDONLY)) < 0) {
+	snprintf(ebuff, EBUFF_SZ, ME "error opening file: %s", file_name);
+	perror(ebuff);
+	return 1;
     }
-    else if (ioctl(sg_fd, SG_GET_VERSION_NUM, &sg_version) < 0)
-        sg_which = SG_RT_NEW32;
-    else if (sg_version >= 30000)
-        sg_which = SG_RT_NEW_V3;
-    else
-        sg_which = SG_RT_NEW34;
+    /* Just to be safe, check we have a new sg device by trying an ioctl */
+    if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
+	printf( ME "%s doesn't seem to be a version 3 sg device\n",
+	       file_name);
+	close(sg_fd);
+	return 1;
+    }
+    res = do_readcap_10(sg_fd, pmi, lba, &last_blk_addr, &block_size);
 
-    if (SG_RT_NEW_V3 == sg_which)
-        res = readcap_sg_io_hdr(sg_fd, &last_blk_addr, &block_size);
-    else
-        res = readcap_sg_header(sg_fd, sg_which, &last_blk_addr, &block_size);
-
-   if (! res) {
+   if (0 == res) {
         printf("Read Capacity results:\n");
-        printf("   Last block address = %u (0x%x), Number of blocks = %u\n",
-               last_blk_addr, (int)last_blk_addr, last_blk_addr + 1);
+	if (pmi)
+	    printf("   PMI mode: given lba=0x%x, last block before "
+		   "delay=0x%x\n", lba, last_blk_addr);
+	else
+	    printf("   Last block address=%u (0x%x), Number of blocks=%u\n",
+                   last_blk_addr, last_blk_addr, last_blk_addr + 1);
         printf("   Block size = %u bytes\n", block_size);
     }
     close(sg_fd);
     return 0;
 }
-
-
-#define MAX_SG_DEVS 26
-#define MAX_FILENAME_LEN 128
-
-#define SCAN_ALPHA 0
-#define SCAN_NUMERIC 1
-#define DEF_SCAN SCAN_ALPHA
-
-static void make_dev_name(char * fname, int k, int do_numeric)
-{
-    char buff[MAX_FILENAME_LEN];
-
-    strcpy(fname, "/dev/sg");
-    if (do_numeric) {
-        sprintf(buff, "%d", k);
-        strcat(fname, buff);
-    }
-    else {
-        if (k <= 26) {
-            buff[0] = 'a' + (char)k;
-            buff[1] = '\0';
-            strcat(fname, buff);
-        }
-        else
-            strcat(fname, "xxxx");
-    }
-}
-
-typedef struct my_scsi_idlun
-{
-    int mux4;
-    int host_unique_id;
-
-} My_scsi_idlun;
-
-static int open_scsi_dev_as_sg(const char * devname)
-{
-    int fd, bus, bbus, k;
-    My_scsi_idlun m_idlun, mm_idlun;
-    int do_numeric = DEF_SCAN;
-    char name[MAX_FILENAME_LEN];
-
-    strncpy(name, devname, MAX_FILENAME_LEN);
-    if ((fd = open(name, O_RDONLY | O_NONBLOCK)) < 0) {
-        if (EACCES == errno) {
-            if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
-                return fd;
-        }
-    }
-    if (ioctl(fd, SG_GET_TIMEOUT, 0) < 0) { /* not sg device ? */
-        if (ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bus) < 0) {
-            printf("Need a filename that resolves to a SCSI device\n");
-            close(fd);
-            return -9999;
-        }
-        if (ioctl(fd, SCSI_IOCTL_GET_IDLUN, &m_idlun) < 0) {
-            printf("Need a filename that resolves to a SCSI device (2)\n");
-            close(fd);
-            return -9999;
-        }
-        close(fd);
-
-        for (k = 0; k < MAX_SG_DEVS; k++) {
-            make_dev_name(name, k, do_numeric);
-            if ((fd = open(name, O_RDONLY | O_NONBLOCK)) < 0) {
-                if (EACCES == errno)
-                    fd = open(name, O_RDWR | O_NONBLOCK);
-                if (fd < 0) {
-                    if ((ENOENT == errno) && (0 == k) &&
-                        (do_numeric == DEF_SCAN)) {
-                        do_numeric = ! DEF_SCAN;
-                        make_dev_name(name, k, do_numeric);
-                        if ((fd = open(name, O_RDONLY | O_NONBLOCK)) < 0) {
-                            if (EACCES == errno)
-                                fd = open(name, O_RDWR | O_NONBLOCK);
-                        }
-                    }
-                    if (fd < 0) {
-                        if (EBUSY == errno)
-                            continue;  /* step over if O_EXCL already on it */
-                        else
-                            break;
-                    }
-                }
-            }
-            if (ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bbus) < 0) {
-                perror("sg ioctl failed");
-                close(fd);
-                fd = -9999;
-            }
-            if (ioctl(fd, SCSI_IOCTL_GET_IDLUN, &mm_idlun) < 0) {
-                perror("sg ioctl failed (2)");
-                close(fd);
-                fd = -9999;
-            }
-            if ((bus == bbus) &&
-                ((m_idlun.mux4 & 0xff) == (mm_idlun.mux4 & 0xff)) &&
-                (((m_idlun.mux4 >> 8) & 0xff) ==
-                                        ((mm_idlun.mux4 >> 8) & 0xff)) &&
-                (((m_idlun.mux4 >> 16) & 0xff) ==
-                                        ((mm_idlun.mux4 >> 16) & 0xff))) {
-                printf("  >>> Mapping %s to sg device: %s\n", devname, name);
-                break;
-            }
-            else {
-                close(fd);
-                fd = -9999;
-            }
-        }
-    }
-    if (fd >= 0) { /* everything ok, close and re-open read-write */
-        close(fd);
-        if ((fd = open(name, O_RDWR)) < 0) {
-            if (EACCES == errno)
-                fd = open(name, O_RDONLY);
-        }
-    }
-    return fd;
-}
diff --git a/sg_reset.8 b/sg_reset.8
new file mode 100644
index 0000000..6b10847
--- /dev/null
+++ b/sg_reset.8
@@ -0,0 +1,36 @@
+.TH SG_RESET "8" "April 2003" "sg3_utils-1.03" SG3_UTILS
+.SH NAME
+sg_reset \- exercises SCSI device/bus/host reset capability
+.SH SYNOPSIS
+.B sg_reset
+[\fI-d\fR] 
+[\fI-b\fR] 
+[\fI-h\fR] 
+<\fIgeneric device\fR>
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_reset exercises the SCSI device/bus/host reset capability. It is
+supported by the sg driver in lk 2.2.16 and beyond but associated
+SCSI middle level driver changes were not been accepted into the
+official kernel for some time. Many distributions contain the patch to
+the mid-level that activates this feature. The required patch first
+officially appeared in the kernel in lk 2.4.19 .
+.TP
+-d
+attempt a SCSI device reset
+.TP
+-b
+attempt a SCSI bus reset
+.TP
+-h
+attempt a host adapter reset
+.PP
+If no switch is given, then sg_reset checks if a reset is underway.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH COPYRIGHT
+Copyright \(co 1999-2002 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/sg_scan.8 b/sg_scan.8
new file mode 100644
index 0000000..af6a348
--- /dev/null
+++ b/sg_scan.8
@@ -0,0 +1,41 @@
+.TH SG_SCAN "8" "April 2003" "sg3_utils-1.03" SG3_UTILS
+.SH NAME
+sg_scan \- does a SCSI bus scan and prints the results to standard output
+.SH SYNOPSIS
+.B sg_scan
+[\fI-a\fR] 
+[\fI-n\fR] 
+[\fI-w\fR] 
+[\fI-i\fR] 
+[\fI-x\fR] 
+<\fIgeneric device\fR>
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_reset exercises the SCSI device/bus/host reset capability. It is
+supported by the sg driver in lk 2.2.16 and beyond but associated
+SCSI middle level driver changes have not been accepted into the
+standard kernel at this time. Many distributions contain the patch to
+the mid-level that activates this feature.
+.TP
+-a
+do alpha scan (ie sga, sgb, sgc)
+.TP
+-n
+do numeric scan (ie sg0, sg1...) [default]
+.TP
+-w
+force open with read/write flag
+.TP
+-i
+do SCSI INQUIRY, output results
+.TP
+-x
+extra information output about queuing
+.SH AUTHORS
+Written by D. Gilbert and F. Jansen
+.SH COPYRIGHT
+Copyright \(co 1999-2002 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/sg_senddiag.8 b/sg_senddiag.8
index 2ad6c7f..85d9c9a 100644
--- a/sg_senddiag.8
+++ b/sg_senddiag.8
@@ -1,22 +1,20 @@
-.TH SG_SENDDIAG "8" "March 2003" "sg3_utils-1.03" SG3_UTILS
+.TH SG_SENDDIAG "8" "April 2003" "sg3_utils-1.04" SG3_UTILS
 .SH NAME
 sg_senddiag \- performs a SCSI SEND DIAGNOSTIC command
 .SH SYNOPSIS
 .B sg_senddiag
-[\fI-cpf\fR] [\fI-doff\fR] [\fI-h\fR] [\fI-l\fR] [\fI-s=<self_test_code>\fR]
-[\fI-t\fR] [\fI-uoff\fR] [\fI-V\fR] [\fI-?\fR] \fI<sg_device>\fR
+[\fI-doff\fR] [\fI-e\fR] [\fI-h\fR] [\fI-l\fR] [\fI-pf\fR] 
+[\fI-s=<self_test_code>\fR] [\fI-t\fR] [\fI-uoff\fR] [\fI-V\fR] [\fI-?\fR]
+\fI<sg_device>\fR
 .SH DESCRIPTION
 .\" Add any additional description here
 .PP
-This command sends a SEND DIAGNOSTIC SCSI command to the given device.
+This utility sends a SEND DIAGNOSTIC SCSI command to the given device.
 In the case of '-l' option it then sends a RECEIVE DIAGNOSTIC SCSI
 command to fetch the response (i.e. the page numbers of supported
-diagnostic pages). This command is mainly used to make the device do
+diagnostic pages). This utility is mainly used to make the device do
 self tests.
 .TP
--cpf
-clear Page Format (PF) bit. By default it is set (i.e. 1).
-.TP
 -doff
 set the Device Offline (DevOfl) bit (default is clear). Only significant 
 when '-t' option is set for the default self test. In this sense the 'Device'
@@ -35,6 +33,9 @@
 The request is sent via a SEND DIAGNOSTIC command and the response
 is fetched by a RECEIVE DIAGNOSTIC command.
 .TP
+-pf
+set Page Format (PF) bit. By default it is clear (i.e. 0).
+.TP
 -s=<self_test_code>
 the default value is 0 which is inactive. A value of 1 selects a background
 short self test; 2 selects a background extended self test; 5 selects a 
diff --git a/sg_senddiag.c b/sg_senddiag.c
index 913b2c1..40870af 100644
--- a/sg_senddiag.c
+++ b/sg_senddiag.c
@@ -21,7 +21,7 @@
    command.
 */
 
-static char * version_str = "0.13 20030331";
+static char * version_str = "0.15 20030422";
 
 #define ME "sg_senddiag: "
 
@@ -243,13 +243,14 @@
 
 static void usage()
 {
-    printf("Usage: 'sg_senddiag [-cpf] [-doff] [-h] [-l]"
-	   " [-s=<self_test_code>]\n\t\t [-t] [-uoff] [-V] [<sg_device>]'\n"
-	   " where -cpf clear PF bit (def: 1)\n"
-	   "       -doff device online (def: 0, only with '-t')\n"
+    printf("Usage: 'sg_senddiag [-doff] [-e] [-h] [-l] [-pf]"
+	   " [-s=<self_test_code>]\n"
+	   "                    [-t] [-uoff] [-V] [<sg_device>]'\n"
+	   " where -doff device online (def: 0, only with '-t')\n"
 	   "       -e   duration of last extended test (from mode page 0xa)\n"
 	   "       -h   output in hex\n"
 	   "       -l   list supported page codes\n"
+	   "       -pf  set PF bit (def: 0)\n"
 	   "       -s=<self_test_code> (def: 0)\n"
 	   "          1->background short, 2->background extended,"
 	   " 4->abort test\n"
@@ -260,7 +261,6 @@
 	   "       -?   output this usage message\n");
 }
 
-
 static void dStrHex(const char* str, int len, int no_ascii)
 {
     const char* p = str;
@@ -324,7 +324,7 @@
     int rsp_buff_size = MX_ALLOC_LEN;
     unsigned int u;
     int self_test_code = 0;
-    int do_pf = 1;
+    int do_pf = 0;
     int do_doff = 0;
     int do_hex = 0;
     int do_list = 0;
@@ -343,8 +343,8 @@
             }
 	    self_test_code = u;
         }
-        else if (0 == strcmp("-cpf", argv[k]))
-	    do_pf = 0;
+        else if (0 == strcmp("-pf", argv[k]))
+	    do_pf = 1;
         else if (0 == strcmp("-doff", argv[k]))
 	    do_doff = 1;
         else if (0 == strcmp("-h", argv[k]))
@@ -426,7 +426,7 @@
 	return 0;
     }
     if (do_list) {
-	memset(rsp_buff, 0, 4);
+	memset(rsp_buff, 0, sizeof(rsp_buff));
 	if (0 == do_senddiag(sg_fd, 0, do_pf, 0, 0, 0, rsp_buff, 4, 1)) {
 	    if (0 == do_rcvdiag(sg_fd, 0, 0, rsp_buff, rsp_buff_size, 1)) {
 		printf("Supported diagnostic pages response:\n");
@@ -435,7 +435,7 @@
 		    dStrHex((const char *)rsp_buff, rsp_len, 1);
 		else {
 		    for (k = 0; k < (rsp_len - 4); ++k)
-			printf("  %s\n", find_page_code_desc(rsp_buff[k + 4]));
+		        printf("  %s\n", find_page_code_desc(rsp_buff[k + 4]));
 		}
 	    }
 	}
diff --git a/sg_start.8 b/sg_start.8
new file mode 100644
index 0000000..149286d
--- /dev/null
+++ b/sg_start.8
@@ -0,0 +1,63 @@
+.TH SG_START "8" "May 2003" "sg3_utils-1.04" SG3_UTILS
+.SH NAME
+sg_start \- starts (spins-up) or stops (spins down) SCSI devices
+.SH SYNOPSIS
+.B sg_start
+[\fI-d\fR] [\fI-imm=0|1\fR] [\fI-loej\fR] [\fI-pc=<n>\fR]
+[\fI-s\fR] [\fI-V\fR] [\fI0|1\fR] \fI<sg_device>\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_start sends a START STOP UNIT SCSI command to the given device with
+the selected options. The most used options are "0" to spin down a disk
+and "1" to spin up a disk. Using "sg_start 1 /dev/sg0" on a disk that
+is already spinning is harmless. 
+.TP
+-d
+outputs some extra debug information associated with executing this command
+.TP
+-imm=0|1
+when the immediate bit is 1 (default) then this command (and hence this
+utility) return immediately after the device has received the command.
+When this switch is 0 then the command returns when the action it
+requests has been completed.
+.TP
+-loej
+load the media when the unit is started or eject it when the unit is
+stopped. This option is ignored if 'pc > 0'. Default is off (i.e. don't
+attempt to load or eject media). Obviously only makes sense with
+removable media.
+.TP
+-pc=<n>
+set the 'power conditions' value (in hex); 0 to f (inclusive) are valid.
+When '-pc=0' then the start value (i.e. 0 or 1) and '-loej' are active.
+See recent SBC-2 or SAS drafts at www.t10.org for meaning.
+.TP
+-s
+send the SYNCHRONIZE CACHE SCSI command to the device prior to sending the
+START STOP UNIT command.
+.TP
+-V
+print out version string then exit.
+.TP
+0
+stop (spin-down) given device. Active when '-pc=0' or is not given.
+.TP
+1
+start (spin-up) given device. Active when '-pc=0' or is not given.
+.PP
+In the 2.4 series of Linux kernels the given device must be
+a SCSI generic (sg) device. In the 2.5 series block devices (e.g. disks
+and SCSI DVDs) can also be specified. For example "sg_start 0 /dev/sda"
+will work in the 2.5 series kernels.
+.SH AUTHOR
+Written by K. Garloff and D. Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert@interlog.com>.
+.SH COPYRIGHT
+Copyright \(co 2002-2003 Kurt Garloff, Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_senddiag(sg3_utils)
diff --git a/sg_start.c b/sg_start.c
index 22007f4..35362a7 100644
--- a/sg_start.c
+++ b/sg_start.c
@@ -5,13 +5,14 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <sys/ioctl.h>
+#include <sys/types.h>
 #include "sg_include.h"
 #include "sg_err.h"
 
 /* This program is modeled on the example code in the SCSI Programming
    HOWTO V1.5 by Heiko Eissfeldt dated 7 May 1996.
 *
-*  Copyright (C) 1999 D. Gilbert
+*  Copyright (C) 1999-2003 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)
@@ -21,134 +22,78 @@
    some Linux apps based on the "sg" device driver, it has been
    strengthened.
 
-   Version 0.32 (990728)
- 
    Start/Stop parameter by Kurt Garloff <garloff@suse.de>, 6/2000
    Sync cache parameter by Kurt Garloff <garloff@suse.de>, 1/2001
+   Guard block device answering sg's ioctls. <dgilbert@interlog.com> 12/2002
+   Convert to SG_IO ioctl so can use sg or block devices in 2.5.* 3/2003
  
 */
 
-#define SCSI_OFF sizeof(struct sg_header)
+static char * version_str = "0.35 20030506";
 
-int debug = 0;
-int fd; 					/* SCSI device/file descriptor */
-char* fn;
-static unsigned char cmd[SCSI_OFF + 18];	/* SCSI command buffer */
-
-/* process a complete SCSI cmd. Use the generic SCSI interface. */
-static int handle_SCSI_cmd(int cmd_len,		/* SCSI command length */
-                           int in_size,		/* sg_hd + cmd [+ in_data] */
-                           unsigned char * i_buff, 
-                           int out_size,	/* sg_hd [+ out_data] */
-                           unsigned char * o_buff,	/* if == 0 use i_buff */
-			   int ign_err		/* ignore errors? */
-                           )
-{
-	int status = 0;
-	struct sg_header * sg_hd;
-	
-	/* safety checks */
-	if (cmd_len < 6) return -1;            /* need a cmd_len != 0 */
-	if (! i_buff) return -1;             /* need an input buffer != NULL */
-	
-	if (!o_buff) out_size = 0;      /* no output buffer, no output size */
-	
-	/* generic SCSI device header construction */
-	sg_hd = (struct sg_header *)i_buff;
-	sg_hd->reply_len   = SCSI_OFF + out_size;
-	sg_hd->twelve_byte = (cmd_len == 12);
-	sg_hd->result = 0;
-	sg_hd->pack_len = SCSI_OFF + cmd_len + in_size; /* not necessary */
-	sg_hd->pack_id = 0;     /* not used internally, but passed back */
-	sg_hd->other_flags = 0; /* not used */
-	
-	if (debug) {
-		for (status = 0; status < cmd_len; status++)
-			printf (" %02x", i_buff[SCSI_OFF + status]);
-		//printf ("\n");
-	}
-	
-	/* send command */
-	status = write( fd, i_buff, SCSI_OFF + cmd_len + in_size );
-	if ( status < 0 || status != SCSI_OFF + cmd_len + in_size || 
-	    sg_hd->result ) {
-		/* some error happened */
-		fprintf( stderr, "write(generic) result = 0x%x cmd = 0x%x\n",
-			sg_hd->result, i_buff[SCSI_OFF] );
-		perror("");
-		return status;
-	}
-	
-	if (!o_buff) o_buff = i_buff;       /* buffer pointer check */
-	
-	/* retrieve result */
-	status = read( fd, o_buff, SCSI_OFF + out_size);
-	if ( status < 0 || status != SCSI_OFF + out_size || (!ign_err && sg_hd->result) ) {
-		/* some error happened */
-		fprintf( stderr, "read(generic) status = 0x%x, result = 0x%x, "
-			"cmd = 0x%x\n", 
-			status, sg_hd->result, o_buff[SCSI_OFF] );
-        fprintf( stderr, "read(generic) sense "
-                "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", 
-                sg_hd->sense_buffer[0],         sg_hd->sense_buffer[1],
-                sg_hd->sense_buffer[2],         sg_hd->sense_buffer[3],
-                sg_hd->sense_buffer[4],         sg_hd->sense_buffer[5],
-                sg_hd->sense_buffer[6],         sg_hd->sense_buffer[7],
-                sg_hd->sense_buffer[8],         sg_hd->sense_buffer[9],
-                sg_hd->sense_buffer[10],        sg_hd->sense_buffer[11],
-                sg_hd->sense_buffer[12],        sg_hd->sense_buffer[13],
-                sg_hd->sense_buffer[14],        sg_hd->sense_buffer[15]);
-		if (status < 0)
-			perror("");
-	}
-	/* Look if we got what we expected to get */
-	if (status == SCSI_OFF + out_size) status = 0; /* got them all */
-	
-	return status;  /* 0 means no error */
-}
-
+static int debug = 0;
 
 #define START_STOP		0x1b
 #define SYNCHRONIZE_CACHE	0x35
 
-static unsigned char cmdbuffer[ SCSI_OFF ];
+#define DEF_TIMEOUT 120000       /* 120,000 millisecs == 2 minutes */
 
-
-/* request vendor brand and model */
-static unsigned char *StartStop ( int start )
+static void do_start_stop(int fd, int start, int immed, int loej,
+			  int power_conditions)
 {
-	unsigned char cmdblk [ 6 ] = { 
+	unsigned char cmdblk [6] = { 
 		START_STOP,	/* Command */
-		1,		/* Resvd/Immed */
+		0,		/* Resvd/Immed */
 		0,		/* Reserved */
 		0,		/* Reserved */
 		0,		/* PowCond/Resvd/LoEj/Start */
 		0 };		/* Reserved/Flag/Link */
-	
-	if (start) cmdblk[4] |= 1;
-	//cmdblk[1] &= ~1;
-	memcpy( cmd + SCSI_OFF, cmdblk, sizeof(cmdblk) );
-	
-	/*
-	 * +------------------+
-	 * | struct sg_header | <- cmd
-	 * +------------------+
-	 * | copy of cmdblk   | <- cmd + SCSI_OFF
-	 * +------------------+
-	 */
-	if (debug)
-		printf ("%s device %s ... ", (start? "Start": "Stop"), fn);
-	if (handle_SCSI_cmd (sizeof(cmdblk), 0, cmd,
-			     sizeof(cmdbuffer) - SCSI_OFF, cmdbuffer, 0) ) {
-		fprintf( stderr, "Start/Stop failed\n" );
-		exit(2);
+	unsigned char sense_b[32];
+	sg_io_hdr_t io_hdr;
+	int k, res;
+
+	memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+	cmdblk[1] = immed & 1;
+	cmdblk[4] = ((power_conditions & 0xf) << 4) | 
+		    ((loej & 1) << 1) | (start & 1);
+	io_hdr.interface_id = 'S';
+	io_hdr.cmd_len = sizeof(cmdblk);
+	io_hdr.mx_sb_len = sizeof(sense_b);
+	io_hdr.dxfer_direction = SG_DXFER_NONE;
+	io_hdr.dxfer_len = 0;
+	io_hdr.dxferp = NULL;
+	io_hdr.cmdp = cmdblk;
+	io_hdr.sbp = sense_b;
+	io_hdr.timeout = DEF_TIMEOUT;
+	if (debug) {
+		printf("  Start/Stop command:");
+		for (k = 0; k < 6; ++k)
+			printf (" %02x", cmdblk[k]);
+		printf("\n");
 	}
+	
+	if (ioctl(fd, SG_IO, &io_hdr) < 0) {
+		perror("start_stop (SG_IO) error");
+		return;
+	}
+	res = sg_err_category3(&io_hdr);
+	if (SG_ERR_CAT_MEDIA_CHANGED == res) {
+		fprintf(stderr, "media change report, try start_stop again\n");
+		if (ioctl(fd, SG_IO, &io_hdr) < 0) {
+			perror("start_stop (SG_IO) error");
+			return;
+		}
+	}
+	if (SG_ERR_CAT_CLEAN != res) {
+		sg_chk_n_print3("start_stop", &io_hdr);
+		return;
+ 	}
 	if (debug)
-		printf ("\n");
-	return (cmdbuffer + SCSI_OFF);
+		fprintf(stderr, "start_stop [%s] successful\n",
+			start ? "start" : "stop");
 }
 
-static unsigned char *SyncCache (int ign_err)
+static void do_sync_cache(int fd)
 {
 	unsigned char cmdblk [ 10 ] = {
 		SYNCHRONIZE_CACHE,	/* Command */
@@ -157,85 +102,148 @@
 		0,			/* Reserved */
 		0, 0,			/* No of blocks */
 		0 };			/* Reserved/Flag/Link */
-	
-	memcpy( cmd + SCSI_OFF, cmdblk, sizeof(cmdblk) );
-	
-	/*
-	 * +------------------+
-	 * | struct sg_header | <- cmd
-	 * +------------------+
-	 * | copy of cmdblk   | <- cmd + SCSI_OFF
-	 * +------------------+
-	 */
+	unsigned char sense_b[32];
+	sg_io_hdr_t io_hdr;
+	int res;
 
-	if (debug) 
-		printf ("Sync cache %s ... ", fn);
-	if (handle_SCSI_cmd (sizeof(cmdblk), 0, cmd, 
-			     sizeof(cmdbuffer) - SCSI_OFF, cmdbuffer, ign_err) ) {
-		fprintf( stderr, "Synchronize_Cache failed\n" );
-		exit(2);
+	memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+	io_hdr.interface_id = 'S';
+	io_hdr.cmd_len = sizeof(cmdblk);
+	io_hdr.mx_sb_len = sizeof(sense_b);
+	io_hdr.dxfer_direction = SG_DXFER_NONE;
+	io_hdr.dxfer_len = 0;
+	io_hdr.dxferp = NULL;
+	io_hdr.cmdp = cmdblk;
+	io_hdr.sbp = sense_b;
+	io_hdr.timeout = DEF_TIMEOUT;
+	
+	if (ioctl(fd, SG_IO, &io_hdr) < 0) {
+		perror("sync_cache (SG_IO) error");
+		return;
 	}
+	res = sg_err_category3(&io_hdr);
+	if (SG_ERR_CAT_MEDIA_CHANGED == res) {
+		fprintf(stderr, "media change report, try sync_cache again\n");
+		if (ioctl(fd, SG_IO, &io_hdr) < 0) {
+			perror("sync_cache (SG_IO) error");
+			return;
+		}
+	}
+	if (SG_ERR_CAT_CLEAN != res) {
+		sg_chk_n_print3("sync_cache", &io_hdr);
+		return;
+ 	}
 	if (debug)
-		printf ("\n");
-	return (cmdbuffer + SCSI_OFF);
+		fprintf(stderr, "synchronize cache successful\n");
 }
 
 void usage ()
 {
-	printf("Usage:  sg_start <sg_device> [-s] [-d] [0/1]\n"
-	       "    -s: send the synchronize cache command before start/stop\n"
+	fprintf(stderr, "Usage:  sg_start [-d] [-imm=0|1] [-loej] [-pc=<n>] "
+		        "[-s] [0|1] <device>\n"
 	       "    -d: output debug\n"
-	       "     1: start (spin-up)\n"
+	       "    -imm=0|1: 0->await completion, 1->return "
+	       "immediately(def)\n"
+	       "    -loej: load on start, eject of stop\n"
+	       "    -pc=<n>: power conditions (in hex, default 0)\n"
+	       "    -s: send the synchronize cache command before start/stop\n"
+	       "    -V: print version string then exit\n"
 	       "     0: stop (spin-down)\n"
-	       "        Example: sg_start /dev/sgb 1\n");
+	       "     1: start (spin-up)\n"
+	       "     <device> sg or block device (latter in lk 2.5.*)\n"
+	       "        Example: sg_start /dev/sg2 1\n");
 	exit (1);
 }
 
 int main(int argc, char * argv[])
 {
-	char **argptr = argv + 2;
+	char **argptr;
 	int startstop = -1, synccache = 0;
+	char * file_name = 0;
+	int k, fd, num;
+	unsigned int u;
+	int immed = 1;
+	int loej = 0;
+	int power_conds = 0;
 	
-	if (argc < 3) 
+	if (argc < 2) 
 		usage ();
 
-	fn = argv[1];
-	if (!strcmp (*argptr, "-d")) {
-		debug = 1;
-		argptr++;
-	}
-	
-	if (*argptr && !strcmp (*argptr, "-s")) {
-		synccache = 1;
-		argptr++;
-	}
-	
-	if (*argptr) {
-		if (!strcmp (*argptr, "0"))
+	for (k = 1; k < argc; ++k) {
+		argptr = argv + k;
+		if (!strcmp (*argptr, "-d"))
+			debug = 1;
+		else if (!strcmp (*argptr, "-loej"))
+			loej = 1;
+		else if (!strcmp (*argptr, "-s"))
+			synccache = 1;
+		else if (0 == strncmp("-imm=", argv[k], 5)) {
+			num = sscanf(argv[k] + 5, "%x", &u);
+			if ((1 != num) || (u > 1)) {
+				printf("Bad value after '-imm' switch\n");
+				file_name = 0;
+				break;
+			}
+			immed = u;
+		}
+		else if (0 == strncmp("-pc=", argv[k], 4)) {
+			num = sscanf(argv[k] + 4, "%x", &u);
+			if ((1 != num) || (u > 15)) {
+				printf("Bad value after '-pc' switch\n");
+				file_name = 0;
+				break;
+			}
+			power_conds = u;
+		}
+		else if (!strcmp (*argptr, "-V")) {
+			printf("Version string: %s\n", version_str);
+			exit(0);
+		}
+		else if (!strcmp (*argptr, "0"))
 			startstop = 0;
 		else if (!strcmp (*argptr, "1"))
 			startstop = 1;
+		else if (*argv[k] == '-') {
+			fprintf(stderr, "Unrecognized switch: %s\n", argv[k]);
+			file_name = 0;
+			break;
+		}
+		else if (0 == file_name)
+			file_name = argv[k];
+		else {
+			fprintf(stderr, "too many arguments\n");
+			file_name = 0;
+			break;
+		}
 	}
-	if (!synccache && startstop == -1)
+	if (0 == file_name) {
+		usage();
+		return 1;
+	}
+	if (!synccache && (startstop == -1) && (0 == power_conds))
 		usage ();
 		
-	fd = open(fn, O_RDWR);
+	fd = open(file_name, O_RDWR | O_NONBLOCK);
 	if (fd < 0) {
-		fprintf(stderr, "Error trying to open %s\n", fn);
+		fprintf(stderr, "Error trying to open %s\n", file_name);
 		perror("");
+		usage();
 		return 2;
 	}
-	if (ioctl (fd, SG_GET_TIMEOUT, 0) < 0) {
-		fprintf( stderr, "Given file not a SCSI generic device\n" );
+	if (ioctl(fd, SG_GET_TIMEOUT, 0) < 0) {
+		fprintf( stderr, "Given file not block or SCSI "
+			"generic device\n" );
 		close(fd);
 		return 3;
 	}
 	
 	if (synccache)
-		SyncCache ((startstop == -1? 0: 1));
+		do_sync_cache(fd);
 	
-	if (startstop != -1)
-		StartStop (startstop);
+	if (power_conds > 0)
+		do_start_stop(fd, 0, immed, 0, power_conds);
+	else if (startstop != -1)
+		do_start_stop(fd, startstop, immed, loej, 0);
 	
 	close (fd);
 	return 0;
diff --git a/sg_test_rwbuf.8 b/sg_test_rwbuf.8
new file mode 100644
index 0000000..c4267b2
--- /dev/null
+++ b/sg_test_rwbuf.8
@@ -0,0 +1,27 @@
+.TH SG_TEST_RWBUF "8" "April 2003" "sg3_utils-1.03" SG3_UTILS
+.SH NAME
+sg_test_rwbuf \- Tests the SCSI host adapter by issueing write and read operations on a device's buffer and calculating checksums.
+.SH SYNOPSIS
+.B sg_test_rwbuf
+<\fIgeneric device\fR>
+<\fIsz\fR> 
+[\fIaddwr\fR]
+[\fIaddrd\fR] 
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_test_rwbuf writes and reads back sz bytes to the internal buffer of
+device /dev/sgX. For testing purposes, you can ask it to write
+(addwr) or read (addrd) some more bytes.
+.PP
+\fBWARNING\fR: If you access the device at the same time, e.g. because it's a
+mounted hard disk, the device's buffer may be used by the device
+itself for other data at the same time, and overwriting it may or may
+not cause data corruption!  
+.SH AUTHORS
+Written by D. Gilbert and K. Garloff
+.SH COPYRIGHT
+Copyright \(co 2000 Douglas Gilbert, Kurt Garloff
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/sg_test_rwbuf.c b/sg_test_rwbuf.c
index 1489238..7ed63b3 100644
--- a/sg_test_rwbuf.c
+++ b/sg_test_rwbuf.c
@@ -326,16 +326,24 @@
 int main (int argc, char * argv[])
 {
 	int sg_fd; int res;
+	struct stat a_st;
+	int block_dev = 0;
    
 	parseargs (argc, argv);
 	sg_fd = open(file_name, O_RDWR);
 	if (sg_fd < 0) {
 		perror("sg_test_rwbuf: open error");
-        return 1;
+		return 1;
 	}
+        if (fstat(sg_fd, &a_st) < 0) {
+                fprintf(stderr, "could do fstat() on fd ??\n");
+                close(sg_fd);
+                return 1;
+        }
+        if (S_ISBLK(a_st.st_mode))
+                block_dev = 1;
 	/* Don't worry, being very careful not to write to a none-sg file ... */
-	res = ioctl(sg_fd, SG_GET_TIMEOUT, 0);
-	if (res < 0) {
+        if (block_dev || (ioctl(sg_fd, SG_GET_TIMEOUT, 0) < 0)) {
 		/* perror("ioctl on generic device, error"); */
 		printf("sg_test_rwbuf: not a sg device, or wrong driver\n");
 		return 1;
diff --git a/sg_turs.8 b/sg_turs.8
new file mode 100644
index 0000000..29df5df
--- /dev/null
+++ b/sg_turs.8
@@ -0,0 +1,31 @@
+.TH SG_TURS "8" "April 2003" "sg3_utils-1.03" SG3_UTILS
+.SH NAME
+sg_turs \- executes a user specified number of TEST UNIT READY commands on
+the given device
+.SH SYNOPSIS
+.B sg_turs
+[\fI-n=<number of commands to send>\fR] [\fI-t]
+<\fIgeneric device\fR>
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_turs sends a number of TEST UNIT READY commands to a given SCSI
+generic device. Useful for timing the per command overhead. Note that
+TEST UNIT READY has no associated data, just a 6 byte command and a
+returned SCSI status value.
+.TP
+-n=<num>
+performs TEST UNIT READY "<num>" times. If not given defaults to 1.
+Postfix multipliers "k" (1024), "K" (1000), "m" (1048576) or "M" (1000000)
+may be applied to "<num>".
+.TP
+-t
+after completing the requested number of TEST UNIT READY commands, outputs
+the total duration and the average number of commands executed per second.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2000-2003 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/sg_turs.c b/sg_turs.c
index 0d153f0..0ec6c7c 100644
--- a/sg_turs.c
+++ b/sg_turs.c
@@ -8,29 +8,27 @@
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include "sg_include.h"
+#include "sg_err.h"
 
 /* This program sends a user specified number of TEST UNIT READY commands
    to the given sg device. Since TUR is a simple command involing no
    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-2002 D. Gilbert
+*  Copyright (C) 2000-2003 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)
 *  any later version.
 
-   Invocation: sg_turs -n=<number_of_turs> <sg_device>
+   Invocation: sg_turs [-t] [-n=<number_of_turs>] <sg_device>
    The value give to "-n=" can have the following multipliers:
      'c','C'  *1       'b','B' *512      'k' *1024      'K' *1000
      'm' *(1024^2)     'M' *(1000^2)     'g' *(1024^3)  'G' *(1000^3)
 
-   Version 03.06 (20020114)
-
-6 byte TEST UNIT READY command:
-[0x00][   |lu][res   ][res   ][res   ][res   ]
-
+   Version 03.08 (20020406)
 */
 
 #define TUR_CMD_LEN 6
@@ -84,8 +82,10 @@
     char * file_name = 0;
     char ebuff[EBUFF_SZ];
     unsigned char sense_buffer[32];
-    int num_turs = 0;
+    int num_turs = 1;
     int num_errs = 0;
+    int do_time = 0;
+    struct timeval start_tm, end_tm;
 
     for (k = 1; k < argc; ++k) {
         if (0 == strncmp("-n=", argv[k], 3)) {
@@ -96,6 +96,8 @@
                 break;
             }
         }
+	else if (0 == strcmp("-t", argv[k]))
+	    do_time = 1;
         else if (*argv[k] == '-') {
             printf("Unrecognized switch: %s\n", argv[k]);
             file_name = 0;
@@ -110,7 +112,13 @@
         }
     }
     if ((0 == file_name) || (num_turs <= 0)) {
-        printf("Usage: 'sg_turs -n=<num_of_test_unit_readys> <sg_device>'\n");
+        printf("Usage: 'sg_turs [-t] [-n=<num_of_test_unit_readys>] "
+	       "<sg_device>'\n"
+	       " where '-n=<num>' number of test_unit_ready commands "
+	       "(def: 1)\n"
+	       "                  can take k, K, m, M postfix multipliers\n"
+	       "       '-t'   outputs total duration and commands per "
+	       "second\n");
         return 1;
     }
 
@@ -136,7 +144,11 @@
     io_hdr.cmdp = turCmdBlk;
     io_hdr.sbp = sense_buffer;
     io_hdr.timeout = 20000;     /* 20000 millisecs == 20 seconds */
-
+    if (do_time) {
+	start_tm.tv_sec = 0;
+	start_tm.tv_usec = 0;
+	gettimeofday(&start_tm, NULL);
+    }
     for (k = 0; k < num_turs; ++k) {
         io_hdr.pack_id = k;
         if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
@@ -144,8 +156,34 @@
             close(sg_fd);
             return 1;
         }
-        if (io_hdr.info & SG_INFO_OK_MASK)
+        if (io_hdr.info & SG_INFO_OK_MASK) {
             ++num_errs;
+	    if (1 == num_turs) {	/* then print out the error message */
+	        if (SG_ERR_CAT_CLEAN != sg_err_category3(&io_hdr))
+		    sg_chk_n_print3("tur", &io_hdr);
+	    }
+	}
+    }
+    if ((do_time) && (start_tm.tv_sec || start_tm.tv_usec)) {
+        struct timeval res_tm;
+        double a, b;
+
+        gettimeofday(&end_tm, NULL);
+        res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
+        res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec;
+        if (res_tm.tv_usec < 0) {
+            --res_tm.tv_sec;
+            res_tm.tv_usec += 1000000;
+        }
+        a = res_tm.tv_sec;
+        a += (0.000001 * res_tm.tv_usec);
+        b = (double)num_turs;
+        printf("time to perform commands was %d.%06d secs",
+               (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+        if (a > 0.00001)
+            printf("; %.2f operations/sec\n", b / a);
+        else
+            printf("\n");
     }
 
     printf("Completed %d Test Unit Ready commands with %d errors\n",
diff --git a/sginfo.8 b/sginfo.8
new file mode 100644
index 0000000..fd33ae4
--- /dev/null
+++ b/sginfo.8
@@ -0,0 +1,101 @@
+.TH SGINFO "8" "April 2003" "sg3_utils-1.03" SG3_UTILS
+.SH NAME
+sginfo \- outputs mode sense information for a SCSI generic device
+the given device
+.SH SYNOPSIS
+.B sginfo
+[\fI-options\fR]
+[\fIdevice\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sginfo is a re-porting of the scsiinfo program by Eric Youngdale to
+use the sg devices (rather than the sd, sr or st block devices). This
+program outputs "mode sense" information. Amongst other things it outputs
+the full defect list of a disk (which was truncated at 4096 bytes in
+the original). With the '-R' option mode page data can be changed
+(using the SCSI mode select command).
+.TP
+-c
+Display information from Caching Page.
+.TP
+-C
+Display information from Control Mode Page.
+.TP
+-d
+Display defect lists.
+.TP
+-Farg
+Format of the defect list:
+                -Flogical  - logical blocks
+                -Fphysical - physical blocks
+                -Findex    - defect bytes from index
+.TP
+-e
+Display information from Error Recovery page.
+.TP
+-f
+Display information from Format Device Page.
+.TP
+-g
+Display information from Rigid Disk Drive Geometry Page.
+.TP
+-i
+Display all information from Inquiry command.
+.TP
+-s
+Display all information from unit serial number page.
+.TP
+-D
+Display information from Disconnect-Reconnect Page.
+.TP
+-n
+Display information from Notch and Partition Page.
+.TP
+-p
+Display information from Peripheral Device Page.
+.TP
+-V
+Display information from Verify Error Recovery Page.
+.TP
+-u <no>
+Display information from page number <no> (18 bytes).
+.TP
+-v
+Show version number
+.TP
+-a
+All of the above.
+
+.TP
+-l
+List known scsi devices on the system
+.TP
+-L
+List pages supported notched by program and target
+(notched and active notch are also returned)
+.PP
+Only one of the following three options can be specified.
+None of these three implies the current values are returned.
+.TP
+-m
+Display modifiable fields instead of current values
+.TP
+-M
+Display manufacturer defaults instead of current values
+.TP
+-S
+Display saved defaults instead of current values
+.PP
+The following are advanced options, not generally suited for most users:
+.TP
+-X
+Display output suitable for the X-based interface.
+.TP
+-R
+Replace parameters - best used with -X (expert use only)
+.SH AUTHORS
+Written by Eric Youngdale, Michael Weller, Douglas Gilbert, Kurt Garloff
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/sginfo.c b/sginfo.c
index 2298b2b..040cf02 100644
--- a/sginfo.c
+++ b/sginfo.c
@@ -18,7 +18,7 @@
  * -n    Display information from notch parameters page.
  * -p    Display information from Peripheral Device Page.
  * -V    Display information from Verify Error Recovery Page.
- * -v    Show version number
+ * -uno  Display info from page number no.
  * -v    Show version number
  * -a    All of the above.
  * -l    List known scsi devices on the system
@@ -88,9 +88,11 @@
  * 		sizeof(buffer) (which is sizeof(char*) == 4 or 32 bit archs) 
  *			was used incorrectly all over the place. Fixed.
  *                                      [version 1.95]
- *
  * Douglas Gilbert (dgilbert@interlog.com) 
- *    20020113	snprintf() type cleanup
+ *    20020113	snprintf() type cleanup [version 1.96]
+ *    20021211	correct sginfo MODE_SELECT, protect against block devices
+ *              that answer sg's ioctls. [version 1.97]
+ *    20021228  scan for some "scd<n>" as well as "sr<n>" device names [1.98]
  */
 
 #include <stdio.h>
@@ -102,6 +104,8 @@
 #include <errno.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include "sg_include.h"
 
 #ifdef SG_GET_RESERVED_SIZE
@@ -630,7 +634,8 @@
     pagelen1 = contents[0] + 1;
 
     *((int *) buffer1) = pagelen1+14;      /* length of input data */
-    *(((int *) buffer1) + 1) = pagelen1+8;        /* length of output buffer */
+    *(((int *) buffer1) + 1) = 0;          /* length of output buffer */
+    /* *(((int *) buffer1) + 1) = pagelen1+8;     was before .... */
 
     cmd = (unsigned char *) (((int *) buffer1) + 2);
 
@@ -1356,7 +1361,7 @@
     fname[MDEV_NAME_SZ - 1] = '\0';
     len = strlen(fname);
     if (do_numeric)
-        snprintf(buff + len, MDEV_NAME_SZ - len, "%d", k);
+        snprintf(fname + len, MDEV_NAME_SZ - len, "%d", k);
     else {
         if (k <= 26) {
             buff[0] = 'a' + (char)k;
@@ -1376,6 +1381,8 @@
  "/dev/sdm", "/dev/sdn", "/dev/sdo", "/dev/sdp", "/dev/sdq", "/dev/sdr",
  "/dev/sds", "/dev/sdt", "/dev/sdu", "/dev/sdv", "/dev/sdw", "/dev/sdx",
  "/dev/sdy", "/dev/sdz", "/dev/sdaa", "/dev/sdab", "/dev/sdac", "/dev/sdad",
+ "/dev/scd0", "/dev/scd1", "/dev/scd2", "/dev/scd3", "/dev/scd4", "/dev/scd5",
+ "/dev/scd6", "/dev/scd7", "/dev/scd8", "/dev/scd9", "/dev/scd10", "/dev/scd11",
  "/dev/sr0", "/dev/sr1", "/dev/sr2", "/dev/sr3", "/dev/sr4", "/dev/sr5",
  "/dev/sr6", "/dev/sr7", "/dev/sr8", "/dev/sr9", "/dev/sr10", "/dev/sr11",
  "/dev/nst0", "/dev/nst1", "/dev/nst2", "/dev/nst3", "/dev/nst4", "/dev/nst5",
@@ -1385,6 +1392,7 @@
 static Sg_map sg_map_arr[(sizeof(devices) / sizeof(char *)) + 1];
 
 #define EBUFF_SZ 256
+#define MAX_HOLES 4
 
 /* Print out a list of the known devices on the system */
 static void show_devices()
@@ -1394,6 +1402,7 @@
     char name[MDEV_NAME_SZ];
     char ebuff[EBUFF_SZ];
     int do_numeric = 1;
+    int max_holes = MAX_HOLES;
 
     for (k = 0, j = 0; k < sizeof(devices) / sizeof(char *); k++) {
         fd = open(devices[k], O_RDONLY | O_NONBLOCK);
@@ -1428,13 +1437,13 @@
         printf("%s ", devices[k]);
         close(fd);
     };
-    printf("\n");
+    printf("\n"); // <<<<<<<<<<<<<<<<<<<<<
     for (k = 0; k < MAX_SG_DEVS; k++) {
         make_dev_name(name, k, do_numeric);
         fd = open(name, O_RDWR | O_NONBLOCK);
         if (fd < 0) {
             if ((ENOENT == errno) && (0 == k)) {
-                do_numeric = 1;
+                do_numeric = 0;
                 make_dev_name(name, k, do_numeric);
                 fd = open(name, O_RDWR | O_NONBLOCK);
             }
@@ -1447,10 +1456,14 @@
 		    	     "open on %s failed (%d)", name, errno);
                     perror(ebuff);
 #endif
-                    break;
+		    if (max_holes-- > 0)
+			continue;
+		    else
+                        break;
                 }
             }
         }
+	max_holes = MAX_HOLES;
         err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bus);
         if (err < 0) {
             snprintf(ebuff, EBUFF_SZ, "SCSI(3) ioctl on %s failed", name);
@@ -1656,15 +1669,24 @@
 {
     int fd, err, bus, bbus, k;
     My_scsi_idlun m_idlun, mm_idlun;
-    int do_numeric = 0;
+    int do_numeric = 1;
     char name[DEVNAME_SZ];
+    struct stat a_st;
+    int block_dev = 0;
 
     strncpy(name, devname, DEVNAME_SZ);
     name[DEVNAME_SZ - 1] = '\0';
     fd = open(name, O_RDONLY);
     if (fd < 0)
         return fd;
-    if (ioctl(fd, SG_GET_TIMEOUT, 0) < 0) {
+    if (fstat(fd, &a_st) < 0) {
+	fprintf(stderr, "could do fstat() on fd ??\n");
+	close(fd);
+	return -9999;
+    }
+    if (S_ISBLK(a_st.st_mode))
+    	block_dev = 1;
+    if (block_dev || (ioctl(fd, SG_GET_TIMEOUT, 0) < 0)) {
         err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bus);
         if (err < 0) {
             perror("A SCSI device name is required\n");
@@ -1684,7 +1706,7 @@
             fd = open(name, O_RDWR | O_NONBLOCK);
             if (fd < 0) {
                 if ((ENOENT == errno) && (0 == k)) {
-                    do_numeric = 1;
+                    do_numeric = 0;
                     make_dev_name(name, k, do_numeric);
                     fd = open(name, O_RDWR | O_NONBLOCK);
                 }
@@ -1916,7 +1938,7 @@
             notch = 1;
             /* fall through */
         case 'v':
-            fprintf(stdout, " Sginfo version 1.96\n");
+            fprintf(stdout, " Sginfo version 1.98\n");
             break;
         default:
             fprintf(stdout, "Unknown option '-%c' (ascii %02xh)\n", c, c);
diff --git a/sgm_dd.8 b/sgm_dd.8
index 2737fde..b7d2d26 100644
--- a/sgm_dd.8
+++ b/sgm_dd.8
@@ -1,4 +1,4 @@
-.TH SGM_DD "8" "March 2002" "sg3_utils-0.99" SG3_UTILS
+.TH SGM_DD "8" "May 2002" "sg3_utils-1.00" SG3_UTILS
 .SH NAME
 sgm_dd \- copies data to and from sg and raw devices
 .SH SYNOPSIS
@@ -29,6 +29,13 @@
 copy this number of blocks. Default is minimum number that sg devices
 return from READ CAPACITY (if that works) or 0
 .TP
+dio=0 | 1
+permits direct IO to be selected on the write-side (i.e. "of"). Only
+allowed when the read-side (i.e. "if") is a sg device. When 1 there
+may be a "zero copy" copy (i.e. mmap-ed IO on the read into the user
+space and direct IO from there on the write, potentially two DMAs and
+no data copying from the CPU). Default is 0
+.TP
 fua=0 | 1 | 2 | 3
 force unit access bit. When 3, fua is set on both "if" and "of", when 2, fua
 is set on "if", when 1, fua is set on "of", when 0, fua is cleared on both.
@@ -45,7 +52,10 @@
 if given must be the same as bs
 .TP
 of=FILE
-write to FILE instead of stdout. A file name of - is taken to be stdout
+write to FILE instead of stdout. A file name of - is taken to be stdout.
+If FILE is /dev/null then no actual writes are performed. If FILE is .
+(period) then it is treated the same way as /dev/null (this is a
+shorthand notation)
 .TP
 seek=BLOCKS
 skip BLOCKS bs-sized blocks at start of output
@@ -64,7 +74,6 @@
 --version
 outputs version number information and exits
 .PP
-Either the input file or the output file must be a sg or raw device.
 A raw device must be bound to a block device prior to using sgm_dd.
 See
 .B raw(8)
diff --git a/sgm_dd.c b/sgm_dd.c
index e06e05f..a7040bf 100644
--- a/sgm_dd.c
+++ b/sgm_dd.c
@@ -21,7 +21,7 @@
 #include "llseek.h"
 
 /* A utility program for the Linux OS SCSI generic ("sg") device driver.
-*  Copyright (C) 1999 - 2002 D. Gilbert and P. Allworth
+*  Copyright (C) 1999 - 2003 D. Gilbert and P. Allworth
 *  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)
@@ -51,7 +51,7 @@
    This version should compile with Linux sg drivers with version numbers
    >= 30000 .
 */
-static char * version_str = "1.04 20020316";
+static char * version_str = "1.07 20030101";
 
 
 #define DEF_BLOCK_SIZE 512
@@ -78,6 +78,10 @@
 #define FT_OTHER 0		/* filetype other than sg or raw device */
 #define FT_SG 1			/* filetype is sg char device */
 #define FT_RAW 2		/* filetype is raw char device */
+#define FT_DEV_NULL 3           /* either "/dev/null" or "." as filename */
+#define FT_ST 4                 /* filetype is st char device (tape) */
+
+#define DEV_NULL_MINOR_NUM 3
 
 static int sum_of_resids = 0;
 
@@ -87,6 +91,9 @@
 static int out_full = 0;
 static int out_partial = 0;
 
+static const char * proc_allow_dio = "/proc/scsi/sg/allow_dio";
+
+
 static void install_handler (int sig_num, void (*sig_handler) (int sig))
 {
     struct sigaction sigact;
@@ -131,14 +138,22 @@
 int dd_filetype(const char * filename)
 {
     struct stat st;
+    size_t len = strlen(filename);
 
+    if ((1 == len) && ('.' == filename[0]))
+        return FT_DEV_NULL;
     if (stat(filename, &st) < 0)
-	return FT_OTHER;
+        return FT_OTHER;
     if (S_ISCHR(st.st_mode)) {
-	if (RAW_MAJOR == major(st.st_rdev))
-	    return FT_RAW;
-	else if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
-	    return FT_SG;
+        if ((MEM_MAJOR == major(st.st_rdev)) &&
+            (DEV_NULL_MINOR_NUM == minor(st.st_rdev)))
+            return FT_DEV_NULL;
+        if (RAW_MAJOR == major(st.st_rdev))
+            return FT_RAW;
+        if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+            return FT_SG;
+        if (SCSI_TAPE_MAJOR == major(st.st_rdev))
+            return FT_ST;
     }
     return FT_OTHER;
 }
@@ -148,15 +163,16 @@
     fprintf(stderr, "Usage: "
            "sgm_dd  [if=<infile>] [skip=<n>] [of=<ofile>] [seek=<n>]\n"
            "               [bs=<num>] [bpt=<num>] [count=<n>] [time=<n>]\n"
-           "               [cdbsz=<6|10|12|16>] [fua=0|1|2|3] [sync=0|1]"
-	   " [--version]\n"
-           "            either 'if' or 'of' must be a sg or raw device\n"
+           "               [cdbsz=6|10|12|16] [fua=0|1|2|3] [sync=0|1]\n"
+           "               [dio=0|1] [--version]\n"
            " 'bs'  must be device block size (default 512)\n"
            " 'bpt' is blocks_per_transfer (default is 128)\n"
            " 'time' 0->no timing(def), 1->time plus calculate throughput\n"
 	   " 'fua' force unit access: 0->don't(def), 1->of, 2->if, 3->of+if\n"
-	   " 'sync' 0->no sync(def), 1->SYNCHRONIZE CACHE on of after xfer\n"
-	   " 'cdbsz' size of SCSI READ or WRITE command (default is 10)\n");
+	   " 'sync' 0->no sync(def), 1->SYNCHRONIZE CACHE after xfer\n"
+	   " 'cdbsz' size of SCSI READ or WRITE command (default is 10)\n"
+	   " 'dio'  0->indirect IO on write, 1->direct IO on write\n"
+	   "        (only when read side is sg device (using mmap))\n");
 }
 
 /* Return of 0 -> success, -1 -> failure, 2 -> try again */
@@ -390,7 +406,7 @@
 /* -1 -> unrecoverable error, 0 -> successful, 1 -> recoverable (ENOMEM),
    2 -> try again */
 int sg_write(int sg_fd, unsigned char * buff, int blocks, int to_block,
-             int bs, int cdbsz, int fua, int do_mmap)
+             int bs, int cdbsz, int fua, int do_mmap, int * diop)
 {
     unsigned char wrCmd[MAX_SCSI_CDBSZ];
     unsigned char senseBuff[SENSE_BUFF_LEN];
@@ -417,6 +433,8 @@
     io_hdr.pack_id = to_block;
     if (do_mmap)
         io_hdr.flags |= SG_FLAG_MMAP_IO;
+    if (diop && *diop)
+        io_hdr.flags |= SG_FLAG_DIRECT_IO;
 
     while (((res = write(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
            (EINTR == errno))
@@ -448,6 +466,9 @@
         sg_chk_n_print3("writing", &io_hdr);
         return -1;
     }
+    if (diop && *diop &&
+	((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+	*diop = 0;      /* flag that dio not done (completely) */
     return 0;
 }
 
@@ -520,6 +541,8 @@
     int do_time = 0;
     int scsi_cdbsz = DEF_SCSI_CDBSZ;
     int do_sync = 0;
+    int do_dio = 0;
+    int num_dio_not_done = 0;
     int fua_mode = 0;
     int in_sect_sz, out_sect_sz;
     char ebuff[EBUFF_SZ];
@@ -570,6 +593,8 @@
             fua_mode = get_num(buf);
         else if (0 == strcmp(key,"sync"))
             do_sync = get_num(buf);
+        else if (0 == strcmp(key,"dio"))
+            do_dio = get_num(buf);
         else if (0 == strncmp(key, "--vers", 6)) {
             fprintf(stderr, ME "for Linux sg version 3 driver: %s\n",
                     version_str);
@@ -608,7 +633,11 @@
     if (inf[0] && ('-' != inf[0])) {
 	in_type = dd_filetype(inf);
 
-	if (FT_SG == in_type) {
+        if (FT_ST == in_type) {
+            fprintf(stderr, ME "unable to use scsi tape device %s\n", inf);
+            return 1;
+        }
+        else if (FT_SG == in_type) {
 	    if ((infd = open(inf, O_RDWR)) < 0) {
                 snprintf(ebuff, EBUFF_SZ, 
 			 ME "could not open %s for sg reading", inf);
@@ -642,7 +671,7 @@
 		return 1;
 	    }
         }
-        if (FT_SG != in_type) {
+        else {
             if ((infd = open(inf, O_RDONLY)) < 0) {
                 snprintf(ebuff, EBUFF_SZ,
 			 ME "could not open %s for reading", inf);
@@ -666,7 +695,11 @@
     if (outf[0] && ('-' != outf[0])) {
 	out_type = dd_filetype(outf);
 
-	if (FT_SG == out_type) {
+        if (FT_ST == out_type) {
+            fprintf(stderr, ME "unable to use scsi tape device %s\n", outf);
+            return 1;
+        }
+        else if (FT_SG == out_type) {
 	    if ((outfd = open(outf, O_RDWR)) < 0) {
                 snprintf(ebuff, EBUFF_SZ, ME "could not open %s for "
 			 "sg writing", outf);
@@ -700,8 +733,10 @@
 		}
 	    }
         }
+        else if (FT_DEV_NULL == out_type)
+            outfd = -1; /* don't bother opening */
 	else {
-	    if (FT_OTHER == out_type) {
+	    if (FT_RAW != out_type) {
 		if ((outfd = open(outf, O_WRONLY | O_CREAT, 0666)) < 0) {
 		    snprintf(ebuff, EBUFF_SZ,
 			     ME "could not open %s for writing", outf);
@@ -735,7 +770,7 @@
 		"Can't have both 'if' as stdin _and_ 'of' as stdout\n");
         return 1;
     }
-#if 1
+#if 0
     if ((FT_OTHER == in_type) && (FT_OTHER == out_type)) {
         fprintf(stderr, "Both 'if' and 'of' can't be ordinary files\n");
         return 1;
@@ -801,6 +836,24 @@
         fprintf(stderr, "Couldn't calculate count, please give one\n");
         return 1;
     }
+    if (do_dio && (FT_SG != in_type)) {
+    	do_dio = 0;
+	fprintf(stderr, ">>> dio only performed on 'of' side when 'if' is"
+		" an sg device\n");
+    }
+    if (do_dio) {
+        int fd;
+        char c;
+
+        if ((fd = open(proc_allow_dio, O_RDONLY)) >= 0) {
+            if (1 == read(fd, &c, 1)) {
+                if ('0' == c)
+                    fprintf(stderr, ">>> %s set to '0' but should be set "
+                            "to '1' for direct IO\n", proc_allow_dio);
+            }
+            close(fd);
+        }
+    }
 
     if (wrkMmap)
 	wrkPos = wrkMmap;
@@ -878,22 +931,28 @@
         if (FT_SG == out_type) {
             int do_mmap = (FT_SG == in_type) ? 0 : 1;
 	    int fua = fua_mode & 1;
+	    int dio_res = do_dio;
 
             res = sg_write(outfd, wrkPos, blocks, seek, bs, scsi_cdbsz, fua,
-			   do_mmap);
+			   do_mmap, &dio_res);
             if (2 == res) {
                 fprintf(stderr, 
 			"Unit attention, media changed, continuing (w)\n");
                 res = sg_write(outfd, wrkPos, blocks, seek, bs, scsi_cdbsz,
-			       fua, do_mmap);
+			       fua, do_mmap, &dio_res);
             }
             else if (0 != res) {
                 fprintf(stderr, "sg_write failed, seek=%d\n", seek);
                 break;
             }
-            else
+            else {
                 out_full += blocks;
+		if (do_dio && (0 == dio_res))
+		    num_dio_not_done++;
+	    }
         }
+        else if (FT_DEV_NULL == out_type)
+            out_full += blocks; /* act as if written out without error */
         else {
 	    while (((res = write(outfd, wrkPos, blocks * bs)) < 0)
 		   && (EINTR == errno))
@@ -957,7 +1016,7 @@
     if (wrkBuff) free(wrkBuff);
     if (STDIN_FILENO != infd)
         close(infd);
-    if (STDOUT_FILENO != outfd)
+    if ((STDOUT_FILENO != outfd) && (FT_DEV_NULL != out_type))
         close(outfd);
     res = 0;
     if (0 != dd_count) {
@@ -968,5 +1027,8 @@
     if (sum_of_resids)
         fprintf(stderr, ">> Non-zero sum of residual counts=%d\n", 
 		sum_of_resids);
+    if (num_dio_not_done)
+        fprintf(stderr, ">> dio requested but _not done %d times\n", 
+		num_dio_not_done);
     return res;
 }
diff --git a/sgp_dd.8 b/sgp_dd.8
index 86cea13..e4e8948 100644
--- a/sgp_dd.8
+++ b/sgp_dd.8
@@ -1,4 +1,4 @@
-.TH SGP_DD "8" "March 2002" "sg3_utils-0.99" SG3_UTILS
+.TH SGP_DD "8" "May 2002" "sg3_utils-1.00" SG3_UTILS
 .SH NAME
 sgp_dd \- copies data to and from sg and raw devices
 .SH SYNOPSIS
@@ -54,10 +54,6 @@
 6 byte SCSI READ and WRITE commands (cdbsz=6) do not support the fua bit.
 Only active for sg device file names
 .TP
-gen=0 | 1
-when 0 (default) either input or output must be a sg or raw device.
-When it is 1 then this condition is relaxed.
-.TP
 ibs=BYTES
 if given must be the same as bs
 .TP
@@ -68,7 +64,10 @@
 if given must be the same as bs
 .TP
 of=FILE
-write to FILE instead of stdout. A file name of - is taken to be stdout
+write to FILE instead of stdout. A file name of - is taken to be stdout.
+If FILE is /dev/null then no actual writes are performed. If FILE is .
+(period) then it is treated the same way as /dev/null (this is a
+shorthand notation)
 .TP
 seek=BLOCKS
 skip BLOCKS bs-sized blocks at start of output
@@ -91,8 +90,6 @@
 --version
 outputs version number information and exits
 .PP
-Either the input file or the output file should be a sg or raw device
-(unless gen=1).
 A raw device must be bound to a block device prior to using sgp_dd.
 See
 .B raw(8)
@@ -166,7 +163,7 @@
 .SH "REPORTING BUGS"
 Report bugs to <dgilbert@interlog.com>.
 .SH COPYRIGHT
-Copyright \(co 2000, 2001 Douglas Gilbert
+Copyright \(co 2000-2002 Douglas Gilbert
 .br
 This software is distributed under the GPL version 2. There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/sgp_dd.c b/sgp_dd.c
index 1c41283..f4aee1f 100644
--- a/sgp_dd.c
+++ b/sgp_dd.c
@@ -47,7 +47,7 @@
 
 */
 
-static char * version_str = "5.10 20020317";
+static char * version_str = "5.11 20020518";
 
 #define DEF_BLOCK_SIZE 512
 #define DEF_BLOCKS_PER_TRANSFER 128
@@ -73,6 +73,10 @@
 #define FT_OTHER 0              /* filetype other than sg or raw device */
 #define FT_SG 1                 /* filetype is sg char device */
 #define FT_RAW 2                /* filetype is raw char device */
+#define FT_DEV_NULL 3           /* either "/dev/null" or "." as filename */
+#define FT_ST 4                 /* filetype is st char device (tape) */
+
+#define DEV_NULL_MINOR_NUM 3
 
 #define EBUFF_SZ 512
 
@@ -166,14 +170,22 @@
 int dd_filetype(const char * filename)
 {
     struct stat st;
+    size_t len = strlen(filename);
 
+    if ((1 == len) && ('.' == filename[0]))
+        return FT_DEV_NULL;
     if (stat(filename, &st) < 0)
         return FT_OTHER;
     if (S_ISCHR(st.st_mode)) {
+        if ((MEM_MAJOR == major(st.st_rdev)) &&
+            (DEV_NULL_MINOR_NUM == minor(st.st_rdev)))
+            return FT_DEV_NULL;
         if (RAW_MAJOR == major(st.st_rdev))
             return FT_RAW;
-        else if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+        if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
             return FT_SG;
+        if (SCSI_TAPE_MAJOR == major(st.st_rdev))
+            return FT_ST;
     }
     return FT_OTHER;
 }
@@ -183,16 +195,13 @@
     fprintf(stderr, "Usage: "
            "sgp_dd  [if=<infile>] [skip=<n>] [of=<ofile>] [seek=<n>]\n"
            "               [bs=<num>] [bpt=<num>] [count=<n>]\n"
-           "               [dio=<n>] [thr=<n>] [coe=<n>] [gen=0|1]\n"
-           "               [time=0|1] [deb=<n>] [cdbsz=<6|10|12|16>] "
-	   "[--version]\n"
-           "            usually either 'if' or 'of' is a sg or raw device\n"
+           "               [dio=0|1>] [thr=<n>] [coe=0|1] [time=0|1]\n"
+           "               [deb=<n>] [cdbsz=6|10|12|16] [--version]\n"
            " 'bpt' is blocks_per_transfer (default is 128)\n"
            " 'dio' is direct IO, 1->attempt, 0->indirect IO (def)\n"
            " 'thr' is number of threads, must be > 0, default 4, max 16\n");
     fprintf(stderr, " 'coe' continue on error, 0->exit (def), "
     	   "1->zero + continue\n"
-           " 'gen' 0-> one file is special(def), 1-> any files allowed\n"
 	   " 'time' 0->no timing(def), 1->time plus calculate throughput\n"
            " 'fua' force unit access: 0->don't(def), 1->of, 2->if, 3->of+if\n"
            " 'sync' 0->no sync(def), 1->SYNCHRONIZE CACHE on of after xfer\n"
@@ -390,13 +399,16 @@
 
         status = pthread_mutex_lock(&clp->out_mutex);
         if (0 != status) err_exit(status, "lock out_mutex");
-        while ((! clp->out_stop) && ((rep->blk + seek_skip) != clp->out_blk)) {
-            /* if write would be out of sequence then wait */
-            pthread_cleanup_push(cleanup_out, (void *)clp);
-            status = pthread_cond_wait(&clp->out_sync_cv, &clp->out_mutex);
-            if (0 != status) err_exit(status, "cond out_sync_cv");
-            pthread_cleanup_pop(0);
-        }
+	if (FT_DEV_NULL != clp->out_type) {
+	    while ((! clp->out_stop) && 
+		   ((rep->blk + seek_skip) != clp->out_blk)) {
+		/* if write would be out of sequence then wait */
+		pthread_cleanup_push(cleanup_out, (void *)clp);
+		status = pthread_cond_wait(&clp->out_sync_cv, &clp->out_mutex);
+		if (0 != status) err_exit(status, "cond out_sync_cv");
+		pthread_cleanup_pop(0);
+	    }
+	}
 
         if (clp->out_stop || (clp->out_count <= 0)) {
             if (! clp->out_stop)
@@ -416,6 +428,12 @@
         pthread_cleanup_push(cleanup_out, (void *)clp);
         if (FT_SG == clp->out_type)
             sg_out_operation(clp, rep); /* releases out_mutex mid operation */
+	else if (FT_DEV_NULL == clp->out_type) {
+	    /* skip actual write operation */
+	    clp->out_done_count -= blocks;
+	    status = pthread_mutex_unlock(&clp->out_mutex);
+	    if (0 != status) err_exit(status, "unlock out_mutex");
+	}
         else {
             normal_out_operation(clp, rep, blocks);
 	    status = pthread_mutex_unlock(&clp->out_mutex);
@@ -917,7 +935,6 @@
     int out_num_sect = 0;
     int num_threads = DEF_NUM_THREADS;
     pthread_t threads[MAX_NUM_THREADS];
-    int gen = 0;
     int do_time = 0;
     int do_sync = 0;
     int in_sect_sz, out_sect_sz, status, infull, outfull;
@@ -973,8 +990,6 @@
             num_threads = get_num(buf);
         else if (0 == strcmp(key,"coe"))
             rcoll.coe = get_num(buf);
-        else if (0 == strcmp(key,"gen"))
-            gen = get_num(buf);
         else if (0 == strcmp(key,"time"))
             do_time = get_num(buf);
         else if (0 == strcmp(key,"cdbsz"))
@@ -1023,7 +1038,11 @@
     if (inf[0] && ('-' != inf[0])) {
     	rcoll.in_type = dd_filetype(inf);
 
-        if (FT_SG == rcoll.in_type) {
+        if (FT_ST == rcoll.in_type) {
+            fprintf(stderr, ME "unable to use scsi tape device %s\n", inf);
+            return 1;
+        }
+        else if (FT_SG == rcoll.in_type) {
             if ((rcoll.infd = open(inf, O_RDWR)) < 0) {
                 snprintf(ebuff, EBUFF_SZ,
 			 ME "could not open %s for sg reading", inf);
@@ -1034,7 +1053,7 @@
                            &rcoll.in_scsi_type))
                 return 1;
         }
-        if (FT_SG != rcoll.in_type) {
+        else {
             if ((rcoll.infd = open(inf, O_RDONLY)) < 0) {
                 snprintf(ebuff, EBUFF_SZ,
 			 ME "could not open %s for reading", inf);
@@ -1057,7 +1076,11 @@
     if (outf[0] && ('-' != outf[0])) {
 	rcoll.out_type = dd_filetype(outf);
 
-        if (FT_SG == rcoll.out_type) {
+        if (FT_ST == rcoll.out_type) {
+            fprintf(stderr, ME "unable to use scsi tape device %s\n", outf);
+            return 1;
+        }
+        else if (FT_SG == rcoll.out_type) {
 	    if ((rcoll.outfd = open(outf, O_RDWR)) < 0) {
                 snprintf(ebuff,  EBUFF_SZ,
 			 ME "could not open %s for sg writing", outf);
@@ -1069,8 +1092,10 @@
 			   &rcoll.out_scsi_type))
 		return 1;
         }
+	else if (FT_DEV_NULL == rcoll.out_type)
+            rcoll.outfd = -1; /* don't bother opening */
 	else {
-	    if (FT_OTHER == rcoll.out_type) {
+	    if (FT_RAW != rcoll.out_type) {
 		if ((rcoll.outfd = open(outf, O_WRONLY | O_CREAT, 0666)) < 0) {
                     snprintf(ebuff, EBUFF_SZ,
                              ME "could not open %s for writing", outf);
@@ -1103,10 +1128,6 @@
         fprintf(stderr, "Disallow both if and of to be stdin and stdout");
         return 1;
     }
-    if ((FT_OTHER == rcoll.in_type) && (FT_OTHER == rcoll.out_type) && !gen) {
-        fprintf(stderr, "Either 'if' or 'of' must be a sg or raw device\n");
-        return 1;
-    }
     if (count < 0) {
         if (FT_SG == rcoll.in_type) {
             res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz);
@@ -1265,7 +1286,7 @@
     if (0 != status) err_exit(status, "pthread_cancel");
     if (STDIN_FILENO != rcoll.infd)
         close(rcoll.infd);
-    if (STDOUT_FILENO != rcoll.outfd)
+    if ((STDOUT_FILENO != rcoll.outfd) && (FT_DEV_NULL != rcoll.out_type))
         close(rcoll.outfd);
     res = 0;
     if (0 != rcoll.out_count) {