Browse Source

Adding scuzz

Signed-off-by: Álvaro Jurado <elbingmiss@gmail.com>
Álvaro Jurado 6 years ago
parent
commit
b562711c2e

+ 413 - 0
sys/man/8/scuzz

@@ -0,0 +1,413 @@
+.TH SCUZZ 8
+.SH NAME
+scuzz \- SCSI target control
+.SH SYNOPSIS
+.B scuzz
+[
+.B -6eq
+] [
+.B -m
+.I max-xfer
+] [
+[
+.B -r
+]
+.I sddev
+]
+.SH DESCRIPTION
+.I Scuzz
+is an interactive program for exercising
+raw SCSI devices.
+Its intended purpose is to investigate and manipulate
+odd devices without the effort of writing a special driver,
+such as shuffling the media around on an optical jukebox.
+It reads commands from standard input and applies them to a SCSI target
+(other devices accessed through the
+.IR sd (3)
+interface,
+such as ATA(PI) devices,
+may also work).
+If
+.I sddev
+is given on the command line, an
+.B open
+(see below)
+is immediately applied to the target.
+On successful completion of a command,
+.BI ok " n
+is printed, where
+.I n
+is the number of bytes transferred to/from the target;
+the
+.B -q
+command line option suppresses the
+.B ok
+message.
+.LP
+The
+.B -6
+forces the use of 6-byte SCSI commands rather than 10-byte ones.
+Some older devices require this, though
+.I scuzz
+attempts to adapt automatically.
+The
+.B -e
+makes
+.I scuzz
+more willing to retry I/O errors but less tolerant of other errors
+and implies
+.BR -6 .
+This option is often needed to read Exabyte 8mm tapes.
+The
+.B -m
+option sets the maximum I/O transfer size to
+.IR max-xfer .
+Exabyte drives often require this to be 1024 or the exact tape block size
+and some 4mm drives require this to be the exact tape block size or larger.
+.SS Commands
+.TF "inquiry"
+.PD
+.TP
+.BI help " command
+.B Help
+is rudimentary and prints a one line synopsis for the named
+.IR command ,
+or for all commands if no argument is given.
+.TP
+.B probe
+.B Probe
+attempts an
+.B inquiry
+command on all SCSI units,
+and prints the result preceded by the name of those
+targets which respond.
+.LP
+The
+.B help
+and
+.B probe
+commands may be given at any time.
+.TF "inquiry"
+.PD
+.TP
+.BI open\ [ -r ] sddev
+.B Open
+must be given before any of the remaining commands will be accepted.
+Internally,
+unless the
+.B -r
+option is given,
+.B open
+issues
+.B ready
+then
+.BR inquiry ,
+followed by a device class-specific command to determine the
+logical block size of the target.
+.I Sddev
+is an
+.IR sd (3)
+device directory like
+.IR /dev/sdC0 .
+.TP
+.B close
+.B Close
+need only be given if another target is to be opened in the current
+session.
+.LP
+The remaining commands are in rough groups,
+intended for specific classes of device.
+With the exception of the
+.BR read ,
+.BR write ,
+and
+.B space
+commands,
+all arguments are in the style of ANSI-C integer constants.
+.TF "inquiry"
+.PD
+.TP
+.B ready
+Test Unit Ready
+checks if the unit is powered up and ready to do
+.B read
+and
+.B write
+commands.
+.TP
+.B rezero
+Rezero
+Unit requests that a disk be brought to a known state,
+usually by seeking to track zero.
+.TP
+.B rewind
+.B Rewind
+positions a tape at the beginning of current partition
+(there is usually only one partition, the beginning of tape).
+.TP
+.B reqsense
+Request Sense retrieves Sense Data concerning an error or
+other condition and is usually issued following the completion of a command
+that had check-condition status.
+.I Scuzz
+automatically issues a
+.B reqsense
+in response to a check-condition status and prints the result.
+.TP
+.B format
+Format
+Unit performs a ``low level'' format of a disk.
+.TP
+.B rblimits
+Read Block Limits
+reports the possible block lengths for the logical unit. Tapes only.
+.TP
+.BI read " file nbytes
+.B Read
+transfers data from the target to the host.
+A missing
+.I nbytes
+causes the entire device to be read.
+.TP
+.BI write " file nbytes
+.B Write
+transfers data from the host to the target.
+A missing
+.I nbytes
+causes the entire input file to be transferred.
+.IP
+The first argument to the
+.BR read
+and
+.BR write
+commands specifies a source
+.RB ( write )
+or destination
+.RB ( read )
+for the I/O.
+The argument is either a plain file name or
+.B |
+followed by a command to be executed by
+.IR rc (1).
+The argument may be quoted in the style of
+.IR rc (1).
+.TP
+.BI seek " offset whence
+.B Seek
+requests the target to seek to a position on a disk,
+arguments being in the style of
+.IR seek (2);
+.I whence
+is 0 by default.
+.IP
+.I Scuzz
+maintains an internal notion of where the current target
+is positioned.
+The
+.BR seek ,
+.BR read ,
+.BR write ,
+.BR rewind ,
+.BR rezero ,
+and
+.B wtrack
+commands all manipulate the internal offset.
+.TP
+.BI filemark " howmany
+Write Filemarks
+writes one (default) or more filemarks on a tape.
+.TP
+.BI space\ [ -b ]\ [ -f ]\ [[ "--\fP]\fIhowmany\fP]"
+.B Space
+positions a tape forwards or backwards.
+The arguments
+specify logical block
+.RB ( -b )
+or
+filemark
+.RB ( -f )
+spacing;
+default is
+.BR -b .
+If
+.I howmany
+is negative
+it specifies spacing backwards,
+and should be preceded by
+.B --
+to turn off any further
+option processing.
+Default is 1.
+.TP
+.B inquiry
+.B Inquiry
+is issued to determine the device type of a particular target,
+and to determine some basic information about the implemented options and
+the product name.
+.TP
+.BI modeselect bytes...
+.TP
+.BI modeselect6 bytes...
+Mode
+Select
+is issued to set variable parameters in the target.
+.I Bytes
+given as arguments comprise all the data for the target;
+see an appropriate manual for the format.
+The default is the 10-byte form of the command;
+modeselect6 is the 6-byte version.
+.TP
+.BI modesense\ [ page [ nbytes ]]
+.TP
+.BI modesense6\ [ page [ nbytes ]]
+Mode
+Sense
+reports variable and fixed parameters from the target.
+If no
+.I page
+is given,
+all pages are returned.
+.I Nbytes
+specifies how many bytes should be returned.
+The default is the 10-byte form of the command;
+modesense6 is the 6-byte version.
+.TP
+.BI start\ [ code ]
+.TP
+.BI stop\ [ code ]
+.TP
+.BI eject\ [ code ]
+.TP
+.BI ingest\ [ code ]
+.BR Start ,
+.BR stop ,
+.BR eject ,
+and
+.B ingest
+are synonyms for Start/Stop Unit with different default values of
+.IR code .
+Start/Stop Unit is typically used to spin up and spin down a rotating
+disk drive.
+.I Code
+is 0 to stop,
+1 to start and
+3 to eject (if the device supports ejection of the medium).
+.TP
+.B capacity
+Read Capacity reports the number of blocks and the block
+size of a disk.
+.LP
+The following commands are specific to CD and CD-R/RW devices.
+A brief description of each is given; see the SCSI-3
+Multimedia Commands (MMC) Specification for details of arguments
+and interpretation of the results.
+.TF "inquiry"
+.PD
+.TP
+.BI blank\ [ track/LBA [ type ]]
+Erase a CD-RW disk.
+Type identifies the method and coverage of the blanking.
+.TP
+.BI rtoc\ [ track/session-number [ ses ]]
+The Read TOC/PMA command transfers data from one of the tables of contents
+(TOC or PMA) on the CD medium.
+.TP
+.B rdiscinfo
+(Note the spelling.)
+Provides information about disks, including incomplete CD-R/RW.
+.TP
+.BI rtrackinfo\ [ track ]
+Provides information about a track, regardless of its status.
+.TP
+.B cdpause
+.TP
+.B cdresume
+Pause/resume playback.
+.TP
+.B cdstop
+Stop playback.
+.TP
+.BI cdplay\ [ track-number ]\ or\ [ -r [ "LBA\fP[\fIlength\fP]]]"
+Play audio.
+With no arguments, starts at the beginning of the medium.
+If a track number is given, the table of contents is read
+to find the playback start point.
+If the
+.B -r
+option is given, block addressing is used to find the
+playback start point.
+.TP
+.BI cdload\ [ slot ]
+.TP
+.BI cdunload\ [ slot ]
+Load/unload a disk from a changer.
+.TP
+.B cdstatus
+Read the mechanism status.
+.LP
+The following commands are specific to Media Changer devices.
+A brief description of each is given; see the SCSI-3
+Medium Changer Commands (SMC) Specification for details of arguments.
+.TF "inquiry"
+.PD
+.TP
+.B einit
+Initialize element status.
+.TP
+.BI "estatus " "type " [ length ]
+Report the status of the internal elements.
+Type 0 reports all element types.
+.TP
+.BI "mmove " transport\ source\ destination [ invert ]
+Move medium.
+.SH FILES
+.TF /dev/sdXX/raw
+.TP
+.B /dev/\fIsdXX\fP/raw
+raw SCSI interface for command, I/O, and status.
+.SH SOURCE
+.B /sys/src/cmd/scuzz
+.SH "SEE ALSO"
+.IR sd (3)
+.br
+.IR "Small Computer System Interface - 2 (X3T9.2/86-109)" ,
+Global Engineering Documents
+.br
+.IR "SCSI Bench Reference" ,
+ENDL Publications
+.br
+.IR "SCSI-3 Multimedia Commands (MMC) Specification" ,
+www.t10.org
+.br
+.IR "SCSI-3 Medium Changer Commands (SMC) Specification" ,
+www.t10.org
+.SH BUGS
+Only a limited subset of SCSI commands has been implemented (as needed).
+.LP
+Only one target can be open at a time.
+.LP
+LUNs other than 0 are not supported.
+.LP
+No way to force 10-byte commands, though they are the default.
+.LP
+Should be recoded to use
+.IR scsi (2)
+in order to get more complete sense code descriptions.
+.LP
+.I Scuzz
+betrays its origins by spelling
+.B rdiscinfo
+with a
+.B c
+even though the devices it manipulates are spelled with a
+.BR k .
+.LP
+The
+.I max-xfer
+value is currently limited to 245760
+to limit kernel memory consumption.
+.LP
+It may be necessary to set
+.I max-xfer
+to exactly the block size used to write a tape
+in order to read it on some drives.

+ 1 - 0
sys/src/cmd/build.json

@@ -63,6 +63,7 @@
 			"sam/build.json",
 			"samterm/build.json",
 			"scat/build.json",
+			"scuzz/build.json",
 			"tarsplit/build.json",
 			"tbl/build.json",
 			"troff2html/build.json",

+ 12 - 0
sys/src/cmd/scuzz/build.json

@@ -0,0 +1,12 @@
+{
+	"scuzz": {
+		"Include": [
+			"../cmd.json"
+		],
+		"Install": "/$ARCH/bin/",
+		"Program": "scuzz",
+		"SourceFiles": [
+			"*.c"
+		]
+	}
+}

+ 168 - 0
sys/src/cmd/scuzz/cdaudio.c

@@ -0,0 +1,168 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+#include "scsireq.h"
+
+extern Biobuf bout;
+
+int32_t
+SRcdpause(ScsiReq *rp, int resume)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdCDpause;
+	cmd[8] = resume;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRcdstop(ScsiReq *rp)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdCDstop;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+static int32_t
+_SRcdplay(ScsiReq *rp, int32_t lba, int32_t length)
+{
+	uint8_t cmd[12];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdCDplay;
+	cmd[2] = lba>>24;
+	cmd[3] = lba>>16;
+	cmd[4] = lba>>8;
+	cmd[5] = lba;
+	cmd[6] = length>>24;
+	cmd[7] = length>>16;
+	cmd[8] = length>>8;
+	cmd[9] = length;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+
+	return SRrequest(rp);
+}
+
+static struct {
+	int	trackno;
+	int32_t	lba;
+	int32_t	length;
+} tracks[100];
+static int ntracks;
+
+int32_t
+SRcdplay(ScsiReq *rp, int raw, int32_t start, int32_t length)
+{
+	uint8_t d[100*8+4], *p;
+	int lba, n, tdl;
+
+	if(raw || start == 0)
+		return _SRcdplay(rp, start, length);
+
+	ntracks = 0;
+	if(SRTOC(rp, d, sizeof(d), 0, 0) == -1){
+		if(rp->status == STok)
+			Bprint(&bout, "\t(probably empty)\n");
+		return -1;
+	}
+	tdl = (d[0]<<8)|d[1];
+	for(p = &d[4], n = tdl-2; n; n -= 8, p += 8){
+		tracks[ntracks].trackno = p[2];
+		lba = (p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7];
+		tracks[ntracks].lba = lba;
+		if(ntracks > 0)
+			tracks[ntracks-1].length = lba-tracks[ntracks-1].lba;
+		ntracks++;
+	}
+	if(ntracks > 0)
+		tracks[ntracks-1].length = 0xFFFFFFFF;
+
+	for(n = 0; n < ntracks; n++){
+		if(start != tracks[n].trackno)
+			continue;
+		return _SRcdplay(rp, tracks[n].lba, tracks[n].length);
+	}
+
+	return -1;
+}
+
+int32_t
+SRcdload(ScsiReq *rp, int load, int slot)
+{
+	uint8_t cmd[12];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdCDload;
+	if(load)
+		cmd[4] = 0x03;
+	else
+		cmd[4] = 0x02;
+	cmd[8] = slot;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRcdstatus(ScsiReq *rp, uint8_t *list, int nbytes)
+{
+	uint8_t cmd[12];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdCDstatus;
+	cmd[8] = nbytes>>8;
+	cmd[9] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRgetconf(ScsiReq *rp, uint8_t *list, int nbytes)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = Scmdgetconf;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}

+ 228 - 0
sys/src/cmd/scuzz/cdr.c

@@ -0,0 +1,228 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <disk.h>
+#include "scsireq.h"
+
+int32_t
+SRblank(ScsiReq *rp, uint8_t type, uint8_t track)
+{
+	uint8_t cmd[12];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdBlank;
+	cmd[1] = type;
+	cmd[2] = track>>24;
+	cmd[3] = track>>16;
+	cmd[4] = track>>8;
+	cmd[5] = track;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRsynccache(ScsiReq *rp)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdSynccache;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRTOC(ScsiReq *rp, void *data, int nbytes, uint8_t format, uint8_t track)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdRTOC;
+	cmd[2] = format;
+	cmd[6] = track;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = data;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRrdiscinfo(ScsiReq *rp, void *data, int nbytes)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdRdiscinfo;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = data;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRrtrackinfo(ScsiReq *rp, void *data, int nbytes, int track)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdRtrackinfo;
+	cmd[1] = 0x01;
+	cmd[2] = track>>24;
+	cmd[3] = track>>16;
+	cmd[4] = track>>8;
+	cmd[5] = track;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = data;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRfwaddr(ScsiReq *rp, uint8_t track, uint8_t mode, uint8_t npa,
+	 uint8_t *data)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdFwaddr;
+	cmd[2] = track;
+	cmd[3] = mode;
+	cmd[7] = npa;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = data;
+	rp->data.count = MaxDirData;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRtreserve(ScsiReq *rp, int32_t nbytes)
+{
+	uint8_t cmd[10];
+	int32_t n;
+
+	if((nbytes % rp->lbsize)){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdTreserve;
+	n = nbytes/rp->lbsize;
+	cmd[5] = n>>24;
+	cmd[6] = n>>16;
+	cmd[7] = n>>8;
+	cmd[8] = n;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRtinfo(ScsiReq *rp, uint8_t track, uint8_t *data)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdTinfo;
+	cmd[5] = track;
+	cmd[8] = MaxDirData;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = data;
+	rp->data.count = MaxDirData;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRwtrack(ScsiReq *rp, void *buf, int32_t nbytes, uint8_t track, uint8_t mode)
+{
+	uint8_t cmd[10];
+	int32_t m, n;
+
+	if((nbytes % rp->lbsize) || nbytes > maxiosize){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdTwrite;
+	cmd[5] = track;
+	cmd[6] = mode;
+	n = nbytes/rp->lbsize;
+	cmd[7] = n>>8;
+	cmd[8] = n;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = buf;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+	m = SRrequest(rp);
+	if(m < 0)
+		return -1;
+	rp->offset += n;
+	return m;
+}
+
+int32_t
+SRmload(ScsiReq *rp, uint8_t code)
+{
+	uint8_t cmd[12];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdMload;
+	cmd[8] = code;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRfixation(ScsiReq *rp, uint8_t type)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdFixation;
+	cmd[8] = type;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}

+ 71 - 0
sys/src/cmd/scuzz/changer.c

@@ -0,0 +1,71 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <disk.h>
+#include "scsireq.h"
+
+int32_t
+SReinitialise(ScsiReq *rp)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdEInitialise;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRmmove(ScsiReq *rp, int transport, int source, int destination, int invert)
+{
+	uint8_t cmd[12];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdMMove;
+	cmd[2] = transport>>8;
+	cmd[3] = transport;
+	cmd[4] = source>>8;
+	cmd[5] = source;
+	cmd[6] = destination>>8;
+	cmd[7] = destination;
+	cmd[10] = invert & 0x01;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRestatus(ScsiReq *rp, uint8_t type, uint8_t *list, int nbytes)
+{
+	uint8_t cmd[12];
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = ScmdEStatus;
+	cmd[1] = type & 0x07;
+	cmd[4] = 0xFF;
+	cmd[5] = 0xFF;
+	cmd[7] = nbytes>>16;
+	cmd[8] = nbytes>>8;
+	cmd[9] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof(cmd);
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}

+ 54 - 0
sys/src/cmd/scuzz/mo.words

@@ -0,0 +1,54 @@
+/*
+ * these scuzz modesense commands return the following for an hp worm drive:
+ *
+ * modesense
+ *  Header
+ *     00 5E 02 90 00 00 00 08
+ *  Block 0
+ *     00 13 47 70 00 00 08 00    (density 00 blocks 1263472 length 2048)
+ *  Page 01 10
+ *     80 05 00 00 00 00 05 00 00 00
+ *  Page 02 14
+ *     20 20 00 00 00 00 00 00 00 20 00 00 00 00
+ *  Page 08 10
+ *     06 00 00 40 00 01 00 01 00 40
+ *  Page 0A 6
+ *     00 00 00 00 00 00
+ *  Page 0B 6
+ *     00 00 02 03 00 00
+ *  Page 20 10
+ *     04 00 00 00 00 00 00 00 00 18
+ *  Page 21 10
+ *     00 00 01 00 02 00 00 00 00 00
+ *  Page 00 0
+ *
+ * modesense6
+ *  Header
+ *     5B 02 90 08
+ *  Block 0
+ *     00 13 47 70 00 00 08 00    (density 00 blocks 1263472 length 2048)
+ *  Page 01 10
+ *     80 05 00 00 00 00 05 00 00 00
+ *  Page 02 14
+ *     20 20 00 00 00 00 00 00 00 20 00 00 00 00
+ *  Page 08 10
+ *     06 00 00 40 00 01 00 01 00 40
+ *  Page 0A 6
+ *     00 00 00 00 00 00
+ *  Page 0B 6
+ *     00 00 02 03 00 00
+ *  Page 20 10
+ *     04 00 00 00 00 00 00 00 00 18
+ *  Page 21 10
+ *     00 00 01 00 02 00 00 00 00 00
+ *  Page 00 0
+ *     Page 00 0
+ *    ok 92
+ *
+ * the drive is:
+ *
+ * # cat ctl
+ * inquiry HP      C1113J          1.099904323710    8XMO  
+ * geometry 1263472 2048
+ * part data 0 1263472
+ */

+ 767 - 0
sys/src/cmd/scuzz/scsireq.c

@@ -0,0 +1,767 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+/*
+ * BUGS:
+ *	no luns
+ *	and incomplete in many other ways
+ */
+#include <disk.h>
+#include "scsireq.h"
+
+enum {
+	Debug = 0,
+};
+
+/*
+ * exabyte tape drives, at least old ones like the 8200 and 8505,
+ * are dumb: you have to read the exact block size on the tape,
+ * they don't take 10-byte SCSI commands, and various other fine points.
+ */
+extern int exabyte, force6bytecmds;
+
+static int debug = Debug;
+
+int32_t
+SRready(ScsiReq *rp)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRrewind(ScsiReq *rp)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRewind;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	if(SRrequest(rp) >= 0){
+		rp->offset = 0;
+		return 0;
+	}
+	return -1;
+}
+
+int32_t
+SRreqsense(ScsiReq *rp)
+{
+	uint8_t cmd[6];
+	ScsiReq req;
+	int32_t status;
+
+	if(rp->status == Status_SD){
+		rp->status = STok;
+		return 0;
+	}
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRsense;
+	cmd[4] = sizeof(req.sense);
+	memset(&req, 0, sizeof(req));
+	if(rp->flags&Fusb)
+		req.flags |= Fusb;
+	req.fd = rp->fd;
+	req.umsc = rp->umsc;
+	req.cmd.p = cmd;
+	req.cmd.count = sizeof cmd;
+	req.data.p = rp->sense;
+	req.data.count = sizeof(rp->sense);
+	req.data.write = 0;
+	status = SRrequest(&req);
+	rp->status = req.status;
+	return status;
+}
+
+int32_t
+SRformat(ScsiReq *rp)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdFormat;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 6;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRrblimits(ScsiReq *rp, uint8_t *list)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRblimits;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = 6;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+static int
+dirdevrw(ScsiReq *rp, uint8_t *cmd, int32_t nbytes)
+{
+	int32_t n;
+
+	n = nbytes / rp->lbsize;
+	if(rp->offset <= Max24off && n <= 256 && (rp->flags & Frw10) == 0){
+		PUTBE24(cmd+1, rp->offset);
+		cmd[4] = n;
+		cmd[5] = 0;
+		return 6;
+	}
+	cmd[0] |= ScmdExtread;
+	cmd[1] = 0;
+	PUTBELONG(cmd+2, rp->offset);
+	cmd[6] = 0;
+	cmd[7] = n>>8;
+	cmd[8] = n;
+	cmd[9] = 0;
+	return 10;
+}
+
+static int
+seqdevrw(ScsiReq *rp, uint8_t *cmd, int32_t nbytes)
+{
+	int32_t n;
+
+	/* don't set Cmd1sili; we want the ILI bit instead of a fatal error */
+	cmd[1] = rp->flags&Fbfixed? Cmd1fixed: 0;
+	n = nbytes / rp->lbsize;
+	PUTBE24(cmd+2, n);
+	cmd[5] = 0;
+	return 6;
+}
+
+int32_t
+SRread(ScsiReq *rp, void *buf, int32_t nbytes)
+{
+	uint8_t cmd[10];
+	int32_t n;
+
+	if((nbytes % rp->lbsize) || nbytes > maxiosize){
+		if(debug){
+			if (nbytes % rp->lbsize)
+				fprint(2, "scuzz: i/o size %ld %% %ld != 0\n",
+					nbytes, rp->lbsize);
+			else
+				fprint(2, "scuzz: i/o size %ld > %ld\n",
+					nbytes, maxiosize);
+		}
+		rp->status = Status_BADARG;
+		return -1;
+	}
+
+	/* set up scsi read cmd */
+	cmd[0] = ScmdRead;
+	if(rp->flags & Fseqdev)
+		rp->cmd.count = seqdevrw(rp, cmd, nbytes);
+	else
+		rp->cmd.count = dirdevrw(rp, cmd, nbytes);
+	rp->cmd.p = cmd;
+	rp->data.p = buf;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+
+	/* issue it */
+	n = SRrequest(rp);
+	if(n != -1){			/* it worked? */
+		rp->offset += n / rp->lbsize;
+		return n;
+	}
+
+	/* request failed; maybe we just read a short record? */
+	if (exabyte) {
+		fprint(2, "read error\n");
+		rp->status = STcheck;
+		return n;
+	}
+	if(rp->status != Status_SD || !(rp->sense[0] & Sd0valid))
+		return -1;
+	/* compute # of bytes not read */
+	n = GETBELONG(rp->sense+3) * rp->lbsize;
+	if (debug)
+		fprint(2,
+	"SRread: request failed with sense data; sense byte count %ld\n",
+			n);
+	if(!(rp->flags & Fseqdev))
+		return -1;
+
+	/* device is a tape or something similar */
+	if (rp->sense[2] == Sd2filemark || rp->sense[2] == 0x08 ||
+	    (rp->sense[2] & Sd2ili && n > 0))
+		rp->data.count = nbytes - n;
+	else
+		return -1;
+	n = rp->data.count;
+	if (!rp->readblock++ || debug)
+		fprint(2, "SRread: tape data count %ld%s\n", n,
+			(rp->sense[2] & Sd2ili? " with ILI": ""));
+	rp->status = STok;
+	rp->offset += n / rp->lbsize;
+	return n;
+}
+
+int32_t
+SRwrite(ScsiReq *rp, void *buf, int32_t nbytes)
+{
+	uint8_t cmd[10];
+	int32_t n;
+
+	if((nbytes % rp->lbsize) || nbytes > maxiosize){
+		if(debug){
+			if (nbytes % rp->lbsize)
+				fprint(2, "scuzz: i/o size %ld %% %ld != 0\n",
+					nbytes, rp->lbsize);
+			else
+				fprint(2, "scuzz: i/o size %ld > %ld\n",
+					nbytes, maxiosize);
+		}
+		rp->status = Status_BADARG;
+		return -1;
+	}
+
+	/* set up scsi write cmd */
+	cmd[0] = ScmdWrite;
+	if(rp->flags & Fseqdev)
+		rp->cmd.count = seqdevrw(rp, cmd, nbytes);
+	else
+		rp->cmd.count = dirdevrw(rp, cmd, nbytes);
+	rp->cmd.p = cmd;
+	rp->data.p = buf;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+
+	/* issue it */
+	if((n = SRrequest(rp)) == -1){
+		if (exabyte) {
+			fprint(2, "write error\n");
+			rp->status = STcheck;
+			return n;
+		}
+		if(rp->status != Status_SD || rp->sense[2] != Sd2eom)
+			return -1;
+		if(rp->sense[0] & Sd0valid){
+			n -= GETBELONG(rp->sense+3) * rp->lbsize;
+			rp->data.count = nbytes - n;
+		}
+		else
+			rp->data.count = nbytes;
+		n = rp->data.count;
+	}
+	rp->offset += n / rp->lbsize;
+	return n;
+}
+
+int32_t
+SRseek(ScsiReq *rp, int32_t offset, int type)
+{
+	uint8_t cmd[10];
+
+	switch(type){
+
+	case 0:
+		break;
+
+	case 1:
+		offset += rp->offset;
+		if(offset >= 0)
+			break;
+		/*FALLTHROUGH*/
+
+	default:
+		if(debug)
+			fprint(2, "scuzz: seek failed\n");
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	memset(cmd, 0, sizeof cmd);
+	if(offset <= Max24off && (rp->flags & Frw10) == 0){
+		cmd[0] = ScmdSeek;
+		PUTBE24(cmd+1, offset & Max24off);
+		rp->cmd.count = 6;
+	}else{
+		cmd[0] = ScmdExtseek;
+		PUTBELONG(cmd+2, offset);
+		rp->cmd.count = 10;
+	}
+	rp->cmd.p = cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	SRrequest(rp);
+	if(rp->status == STok)
+		return rp->offset = offset;
+	return -1;
+}
+
+int32_t
+SRfilemark(ScsiReq *rp, uint32_t howmany)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdFmark;
+	PUTBE24(cmd+2, howmany);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRspace(ScsiReq *rp, uint8_t code, int32_t howmany)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdSpace;
+	cmd[1] = code;
+	PUTBE24(cmd+2, howmany);
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	/*
+	 * what about rp->offset?
+	 */
+	return SRrequest(rp);
+}
+
+int32_t
+SRinquiry(ScsiReq *rp)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdInq;
+	cmd[4] = sizeof rp->inquiry;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	memset(rp->inquiry, 0, sizeof rp->inquiry);
+	rp->data.p = rp->inquiry;
+	rp->data.count = sizeof rp->inquiry;
+	rp->data.write = 0;
+	if(SRrequest(rp) >= 0){
+		rp->flags |= Finqok;
+		return 0;
+	}
+	rp->flags &= ~Finqok;
+	return -1;
+}
+
+int32_t
+SRmodeselect6(ScsiReq *rp, uint8_t *list, int32_t nbytes)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMselect6;
+	if((rp->flags & Finqok) && (rp->inquiry[2] & 0x07) >= 2)
+		cmd[1] = 0x10;
+	cmd[4] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRmodeselect10(ScsiReq *rp, uint8_t *list, int32_t nbytes)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	if((rp->flags & Finqok) && (rp->inquiry[2] & 0x07) >= 2)
+		cmd[1] = 0x10;
+	cmd[0] = ScmdMselect10;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRmodesense6(ScsiReq *rp, uint8_t page, uint8_t *list, int32_t nbytes)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMsense6;
+	cmd[2] = page;
+	cmd[4] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRmodesense10(ScsiReq *rp, uint8_t page, uint8_t *list, int32_t nbytes)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdMsense10;
+	cmd[2] = page;
+	cmd[7] = nbytes>>8;
+	cmd[8] = nbytes;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = list;
+	rp->data.count = nbytes;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+int32_t
+SRstart(ScsiReq *rp, uint8_t code)
+{
+	uint8_t cmd[6];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdStart;
+	cmd[4] = code;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = cmd;
+	rp->data.count = 0;
+	rp->data.write = 1;
+	return SRrequest(rp);
+}
+
+int32_t
+SRrcapacity(ScsiReq *rp, uint8_t *data)
+{
+	uint8_t cmd[10];
+
+	memset(cmd, 0, sizeof cmd);
+	cmd[0] = ScmdRcapacity;
+	rp->cmd.p = cmd;
+	rp->cmd.count = sizeof cmd;
+	rp->data.p = data;
+	rp->data.count = 8;
+	rp->data.write = 0;
+	return SRrequest(rp);
+}
+
+static int32_t
+request(int fd, ScsiPtr *cmd, ScsiPtr *data, int *status)
+{
+	int32_t n, r;
+	char buf[16];
+
+	/* this was an experiment but it seems to be a good idea */
+	*status = STok;
+
+	/* send SCSI command */
+	if(write(fd, cmd->p, cmd->count) != cmd->count){
+		fprint(2, "scsireq: write cmd: %r\n");
+		*status = Status_SW;
+		return -1;
+	}
+
+	/* read or write actual data */
+	werrstr("");
+	if(data->write)
+		n = write(fd, data->p, data->count);
+	else {
+		n = read(fd, data->p, data->count);
+		if (n < 0)
+			memset(data->p, 0, data->count);
+		else if (n < data->count)
+			memset(data->p + n, 0, data->count - n);
+	}
+	if (n != data->count && n <= 0) {
+		if (debug)
+			fprint(2,
+	"request: tried to %s %ld bytes of data for cmd 0x%x but got %r\n",
+				(data->write? "write": "read"),
+				data->count, cmd->p[0]);
+	} else if (n != data->count && (data->write || debug))
+		fprint(2, "request: %s %ld of %ld bytes of actual data\n",
+			(data->write? "wrote": "read"), n, data->count);
+
+	/* read status */
+	buf[0] = '\0';
+	r = read(fd, buf, sizeof buf-1);
+	if((exabyte && r <= 0) || (!exabyte && r < 0)){
+		fprint(2, "scsireq: read status: %r\n");
+		*status = Status_SW;
+		return -1;
+	}
+	if (r >= 0)
+		buf[r] = '\0';
+	*status = atoi(buf);
+	if(n < 0 && (exabyte || *status != STcheck))
+		fprint(2, "scsireq: status 0x%2.2X: data transfer: %r\n",
+			*status);
+	return n;
+}
+
+int32_t
+SRrequest(ScsiReq *rp)
+{
+	int32_t n;
+	int status;
+
+retry:
+	if(rp->flags&Fusb)
+		n = umsrequest(rp->umsc, &rp->cmd, &rp->data, &status);
+	else
+		n = request(rp->fd, &rp->cmd, &rp->data, &status);
+	switch(rp->status = status){
+
+	case STok:
+		rp->data.count = n;
+		break;
+
+	case STcheck:
+		if(rp->cmd.p[0] != ScmdRsense && SRreqsense(rp) != -1)
+			rp->status = Status_SD;
+		if (exabyte)
+			fprint(2, "SRrequest: STcheck, returning -1\n");
+		return -1;
+
+	case STbusy:
+		sleep(1000);
+		goto retry;
+
+	default:
+		fprint(2, "status 0x%2.2X\n", status);
+		return -1;
+	}
+	return n;
+}
+
+int
+SRclose(ScsiReq *rp)
+{
+	if((rp->flags & Fopen) == 0){
+		if(debug)
+			fprint(2, "scuzz: closing closed file\n");
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	close(rp->fd);
+	rp->flags = 0;
+	return 0;
+}
+
+uint
+mkascq(ScsiReq *r)
+{
+	uint8_t *u;
+
+	u = r->sense;
+	return u[2]<<16 | u[12]<<8 | u[13];
+}
+
+static int
+dirdevopen(ScsiReq *rp)
+{
+	uint32_t blocks;
+	uint8_t data[8];
+
+	if(SRstart(rp, 1) == -1)
+		/*
+		 * it's okay for removable media to say
+		 * "check condition: medium not present".
+		 * 3a is "medium not present".
+		 */
+		return rp->inquiry[1] & 0x80 && (mkascq(rp) >> 8) == 0x023a?
+			0: -1;
+	memset(data, 0, sizeof data);
+	if(SRrcapacity(rp, data) == -1)
+		return -1;
+	rp->lbsize = GETBELONG(data+4);
+	blocks =     GETBELONG(data);
+	if(debug)
+		fprint(2, "scuzz: dirdevopen: logical block size %lu, "
+			"# blocks %lu\n", rp->lbsize, blocks);
+	/* some newer dev's don't support 6-byte commands */
+	if(blocks > Max24off && !force6bytecmds)
+		rp->flags |= Frw10;
+	return 0;
+}
+
+static int
+seqdevopen(ScsiReq *rp)
+{
+	uint8_t mode[16], limits[6];
+
+	if(SRrblimits(rp, limits) == -1)
+		return -1;
+	if(limits[1] == 0 && limits[2] == limits[4] && limits[3] == limits[5]){
+		rp->flags |= Fbfixed;
+		rp->lbsize = limits[4]<<8 | limits[5];
+		if(debug)
+			fprint(2, "scuzz: seqdevopen: logical block size %lu\n",
+				rp->lbsize);
+		return 0;
+	}
+	/*
+	 * On some older hardware the optional 10-byte
+	 * modeselect command isn't implemented.
+	 */
+	if (force6bytecmds)
+		rp->flags |= Fmode6;
+	if(!(rp->flags & Fmode6)){
+		/* try 10-byte command first */
+		memset(mode, 0, sizeof mode);
+		mode[3] = 0x10;		/* device-specific param. */
+		mode[7] = 8;		/* block descriptor length */
+		/*
+		 * exabytes can't handle this, and
+		 * modeselect(10) is optional.
+		 */
+		if(SRmodeselect10(rp, mode, sizeof mode) != -1){
+			rp->lbsize = 1;
+			return 0;	/* success */
+		}
+		/* can't do 10-byte commands, back off to 6-byte ones */
+		rp->flags |= Fmode6;
+	}
+
+	/* 6-byte command */
+	memset(mode, 0, sizeof mode);
+	mode[2] = 0x10;		/* device-specific param. */
+	mode[3] = 8;		/* block descriptor length */
+	/*
+	 * bsd sez exabytes need this bit (NBE: no busy enable) in
+	 * vendor-specific page (0), but so far we haven't needed it.
+	mode[12] |= 8;
+	 */
+	if(SRmodeselect6(rp, mode, 4+8) == -1)
+		return -1;
+	rp->lbsize = 1;
+	return 0;
+}
+
+static int
+wormdevopen(ScsiReq *rp)
+{
+	int32_t status;
+	uint8_t list[MaxDirData];
+
+	if (SRstart(rp, 1) == -1 ||
+	    (status = SRmodesense10(rp, Allmodepages, list, sizeof list)) == -1)
+		return -1;
+	/* nbytes = list[0]<<8 | list[1]; */
+
+	/* # of bytes of block descriptors of 8 bytes each; not even 1? */
+	if((list[6]<<8 | list[7]) < 8)
+		rp->lbsize = 2048;
+	else
+		/* last 3 bytes of block 0 descriptor */
+		rp->lbsize = GETBE24(list+13);
+	if(debug)
+		fprint(2, "scuzz: wormdevopen: logical block size %lu\n",
+			rp->lbsize);
+	return status;
+}
+
+int
+SRopenraw(ScsiReq *rp, char *unit)
+{
+	char name[128];
+
+	if(rp->flags & Fopen){
+		if(debug)
+			fprint(2, "scuzz: opening open file\n");
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	memset(rp, 0, sizeof *rp);
+	rp->unit = unit;
+
+	sprint(name, "%s/raw", unit);
+
+	if((rp->fd = open(name, ORDWR)) == -1){
+		rp->status = STtimeout;
+		return -1;
+	}
+	rp->flags = Fopen;
+	return 0;
+}
+
+int
+SRopen(ScsiReq *rp, char *unit)
+{
+	if(SRopenraw(rp, unit) == -1)
+		return -1;
+	SRready(rp);
+	if(SRinquiry(rp) >= 0){
+		switch(rp->inquiry[0]){
+
+		default:
+			fprint(2, "unknown device type 0x%.2x\n", rp->inquiry[0]);
+			rp->status = Status_SW;
+			break;
+
+		case 0x00:	/* Direct access (disk) */
+		case 0x05:	/* CD-ROM */
+		case 0x07:	/* rewriteable MO */
+			if(dirdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case 0x01:	/* Sequential eg: tape */
+			rp->flags |= Fseqdev;
+			if(seqdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case 0x02:	/* Printer */
+			rp->flags |= Fprintdev;
+			return 0;
+
+		case 0x04:	/* Worm */
+			rp->flags |= Fwormdev;
+			if(wormdevopen(rp) == -1)
+				break;
+			return 0;
+
+		case 0x08:	/* medium-changer */
+			rp->flags |= Fchanger;
+			return 0;
+		}
+	}
+	SRclose(rp);
+	return -1;
+}

+ 160 - 0
sys/src/cmd/scuzz/scsireq.h

@@ -0,0 +1,160 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+/* this file is also included by usb/disk and cdfs */
+typedef struct Umsc Umsc;
+//#pragma incomplete Umsc
+
+enum {					/* fundamental constants/defaults */
+	NTargetID	= 8,		/* number of target IDs */
+	CtlrID		= 7,		/* default controller target ID */
+	MaxDirData	= 255,		/* max. direct data returned */
+	LBsize		= 512,		/* default logical-block size */
+};
+
+typedef struct {
+	unsigned char	*p;
+	int32_t	count;
+	unsigned char	write;
+} ScsiPtr;
+
+typedef struct {
+	int	flags;
+	char	*unit;			/* unit directory */
+	int	lun;
+	uint32_t	lbsize;
+	uint32_t	offset;			/* in blocks of lbsize bytes */
+	int	fd;
+	Umsc	*umsc;			/* lun */
+	ScsiPtr	cmd;
+	ScsiPtr	data;
+	int	status;			/* returned status */
+	unsigned char	sense[MaxDirData];	/* returned sense data */
+	unsigned char	inquiry[MaxDirData];	/* returned inquiry data */
+	int	readblock;		/* flag: read a block since open */
+} ScsiReq;
+
+enum {					/* software flags */
+	Fopen		= 0x0001,	/* open */
+	Fseqdev		= 0x0002,	/* sequential-access device */
+	Fwritten	= 0x0004,	/* device written */
+	Fronly		= 0x0008,	/* device is read-only */
+	Fwormdev	= 0x0010,	/* write-once read-multiple device */
+	Fprintdev	= 0x0020,	/* printer */
+	Fbfixed		= 0x0040,	/* fixed block size */
+	Fchanger	= 0x0080,	/* medium-changer device */
+	Finqok		= 0x0100,	/* inquiry data is OK */
+	Fmode6		= 0x0200,	/* use 6-byte modeselect */
+	Frw10		= 0x0400,	/* use 10-byte read/write */
+	Fusb		= 0x0800,	/* USB transparent scsi */
+};
+
+enum {
+	STnomem		=-4,		/* buffer allocation failed */
+	STharderr	=-3,		/* controller error of some kind */
+	STtimeout	=-2,		/* bus timeout */
+	STok		= 0,		/* good */
+	STcheck		= 0x02,		/* check condition */
+	STcondmet	= 0x04,		/* condition met/good */
+	STbusy		= 0x08,		/* busy */
+	STintok		= 0x10,		/* intermediate/good */
+	STintcondmet	= 0x14,		/* intermediate/condition met/good */
+	STresconf	= 0x18,		/* reservation conflict */
+	STterminated	= 0x22,		/* command terminated */
+	STqfull		= 0x28,		/* queue full */
+};
+
+enum {					/* status */
+	Status_SD	= 0x80,		/* sense-data available */
+	Status_SW	= 0x83,		/* internal software error */
+	Status_BADARG	= 0x84,		/* bad argument to request */
+	Status_RO	= 0x85,		/* device is read-only */
+};
+
+enum {
+	/* sense data byte 0 */
+	Sd0valid	= 0x80,		/* valid sense data present */
+
+	/* sense data byte 2 */
+	/* incorrect-length indicator, difference in bytes 3—6 */
+	Sd2ili		= 0x20,
+	Sd2eom		= 0x40,		/* end of medium (tape) */
+	Sd2filemark	= 0x80,		/* at a filemark (tape) */
+
+	/* command byte 1 */
+	Cmd1fixed	= 1,		/* use fixed-length blocks */
+	Cmd1sili	= 2,		/* don't set Sd2ili */
+
+	/* limit of block #s in 24-bit ccbs */
+	Max24off	= (1<<21) - 1,	/* 2⁲ⁱ - 1 */
+
+	/* mode pages */
+	Allmodepages = 0x3F,
+};
+
+/* p arguments should be of type unsigned char* */
+#define GETBELONG(p) ((uint32_t)(p)[0]<<24 | (uint32_t)(p)[1]<<16 | (p)[2]<<8 | (p)[3])
+#define PUTBELONG(p, ul) ((p)[0] = (ul)>>24, (p)[1] = (ul)>>16, \
+			  (p)[2] = (ul)>>8,  (p)[3] = (ul))
+#define GETBE24(p)	((uint32_t)(p)[0]<<16 | (p)[1]<<8 | (p)[2])
+#define PUTBE24(p, ul)	((p)[0] = (ul)>>16, (p)[1] = (ul)>>8, (p)[2] = (ul))
+
+extern int32_t maxiosize;
+
+int32_t	SRready(ScsiReq*);
+int32_t	SRrewind(ScsiReq*);
+int32_t	SRreqsense(ScsiReq*);
+int32_t	SRformat(ScsiReq*);
+int32_t	SRrblimits(ScsiReq*, unsigned char*);
+int32_t	SRread(ScsiReq*, void*, int32_t);
+int32_t	SRwrite(ScsiReq*, void*, int32_t);
+int32_t	SRseek(ScsiReq*, int32_t, int);
+int32_t	SRfilemark(ScsiReq*, uint32_t);
+int32_t	SRspace(ScsiReq*, unsigned char, int32_t);
+int32_t	SRinquiry(ScsiReq*);
+int32_t	SRmodeselect6(ScsiReq*, unsigned char*, int32_t);
+int32_t	SRmodeselect10(ScsiReq*, unsigned char*, int32_t);
+int32_t	SRmodesense6(ScsiReq*, unsigned char, unsigned char*, int32_t);
+int32_t	SRmodesense10(ScsiReq*, unsigned char, unsigned char*, int32_t);
+int32_t	SRstart(ScsiReq*, unsigned char);
+int32_t	SRrcapacity(ScsiReq*, unsigned char*);
+
+int32_t	SRblank(ScsiReq*, unsigned char, unsigned char);	/* MMC CD-R/CD-RW commands */
+int32_t	SRsynccache(ScsiReq*);
+int32_t	SRTOC(ScsiReq*, void*, int, unsigned char, unsigned char);
+int32_t	SRrdiscinfo(ScsiReq*, void*, int);
+int32_t	SRrtrackinfo(ScsiReq*, void*, int, int);
+
+int32_t	SRcdpause(ScsiReq*, int);		/* MMC CD audio commands */
+int32_t	SRcdstop(ScsiReq*);
+int32_t	SRcdload(ScsiReq*, int, int);
+int32_t	SRcdplay(ScsiReq*, int, int32_t, int32_t);
+int32_t	SRcdstatus(ScsiReq*, unsigned char*, int);
+int32_t	SRgetconf(ScsiReq*, unsigned char*, int);
+
+/*	old CD-R/CD-RW commands */
+int32_t	SRfwaddr(ScsiReq*, unsigned char, unsigned char, unsigned char, unsigned char*);
+int32_t	SRtreserve(ScsiReq*, int32_t);
+int32_t	SRtinfo(ScsiReq*, unsigned char, unsigned char*);
+int32_t	SRwtrack(ScsiReq*, void*, int32_t, unsigned char, unsigned char);
+int32_t	SRmload(ScsiReq*, unsigned char);
+int32_t	SRfixation(ScsiReq*, unsigned char);
+
+int32_t	SReinitialise(ScsiReq*);		/* CHANGER commands */
+int32_t	SRestatus(ScsiReq*, unsigned char, unsigned char*, int);
+int32_t	SRmmove(ScsiReq*, int, int, int, int);
+
+int32_t	SRrequest(ScsiReq*);
+int	SRclose(ScsiReq*);
+int	SRopenraw(ScsiReq*, char*);
+int	SRopen(ScsiReq*, char*);
+
+void	makesense(ScsiReq*);
+
+int32_t	umsrequest(struct Umsc*, ScsiPtr*, ScsiPtr*, int*);

+ 1740 - 0
sys/src/cmd/scuzz/scuzz.c

@@ -0,0 +1,1740 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+#include "scsireq.h"
+
+enum {					/* fundamental constants/defaults */
+	/*
+	 * default & maximum `maximum i/o size'; overridden by -m.
+	 * limits kernel memory consumption.
+	 * 240K is exabyte maximum block size.
+	 */
+	MaxIOsize	= 240*1024,
+};
+
+#define MIN(a, b)	((a) < (b) ? (a): (b))
+
+static char rwbuf[MaxIOsize];
+static int verbose = 1;
+
+Biobuf bin, bout;
+int32_t maxiosize = MaxIOsize;
+int exabyte = 0;
+int force6bytecmds = 0;
+
+typedef struct {
+	char *name;
+	int32_t (*f)(ScsiReq *, int, char *[]);
+	int open;
+	char *help;
+} ScsiCmd;
+
+static ScsiCmd scsicmds[];
+
+static int64_t
+vlmin(int64_t a, int64_t b)
+{
+	if (a < b)
+		return a;
+	else
+		return b;
+}
+
+static int32_t
+cmdready(ScsiReq *rp, int argc, char *argv[])
+{
+	USED(argc), USED(argv);
+	return SRready(rp);
+}
+
+static int32_t
+cmdrewind(ScsiReq *rp, int argc, char *argv[])
+{
+	USED(argc), USED(argv);
+	return SRrewind(rp);
+}
+
+static int32_t
+cmdreqsense(ScsiReq *rp, int argc, char *argv[])
+{
+	int32_t nbytes;
+
+	USED(argc), USED(argv);
+	if((nbytes = SRreqsense(rp)) != -1)
+		makesense(rp);
+	return nbytes;
+}
+
+static int32_t
+cmdformat(ScsiReq *rp, int argc, char *argv[])
+{
+	USED(argc), USED(argv);
+	return SRformat(rp);
+}
+
+static int32_t
+cmdrblimits(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t l[6];
+	int32_t n;
+
+	USED(argc), USED(argv);
+	if((n = SRrblimits(rp, l)) == -1)
+		return -1;
+	Bprint(&bout, " %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+		l[0], l[1], l[2], l[3], l[4], l[5]);
+	return n;
+}
+
+static int
+mkfile(char *file, int omode, int *pid)
+{
+	int fd[2];
+
+	if(*file != '|'){
+		*pid = -1;
+		if(omode == OWRITE)
+			return create(file, OWRITE, 0666);
+		else if(omode == OREAD)
+			return open(file, OREAD);
+		return -1;
+	}
+
+	file++;
+	if(*file == 0 || pipe(fd) == -1)
+		return -1;
+	if((*pid = fork()) == -1){
+		close(fd[0]);
+		close(fd[1]);
+		return -1;
+	}
+	if(*pid == 0){
+		switch(omode){
+
+		case OREAD:
+			dup(fd[0], 1);
+			break;
+
+		case OWRITE:
+			dup(fd[0], 0);
+			break;
+		}
+		close(fd[0]);
+		close(fd[1]);
+		execl("/bin/rc", "rc", "-c", file, nil);
+		exits("exec");
+	}
+	close(fd[0]);
+	return fd[1];
+}
+
+int
+waitfor(int pid)
+{
+	int msg;
+	Waitmsg *w;
+
+	while((w = wait()) != nil){
+		if(w->pid != pid){
+			free(w);
+			continue;
+		}
+		msg = (w->msg[0] != '\0');
+		free(w);
+		return msg;
+	}
+	return -1;
+}
+
+static int32_t
+cmdread(ScsiReq *rp, int argc, char *argv[])
+{
+	int32_t n, iosize, prevsize = 0;
+	int64_t nbytes, total;
+	int fd, pid;
+	char *p;
+
+	iosize = maxiosize;
+	nbytes = ~0ULL >> 1;
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		nbytes = strtoll(argv[1], &p, 0);
+		if(nbytes == 0 && p == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 1:
+		if((fd = mkfile(argv[0], OWRITE, &pid)) == -1){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		break;
+	}
+	print("device native block size=%lu\n", rp->lbsize);
+	total = 0;
+	while(nbytes){
+		n = vlmin(nbytes, iosize);
+		if((n = SRread(rp, rwbuf, n)) == -1){
+			if(total == 0)
+				total = -1;
+			break;
+		}
+		if (n == 0)
+			break;
+		if (prevsize != n) {
+			print("tape block size=%ld\n", n);
+			prevsize = n;
+		}
+		if(write(fd, rwbuf, n) != n){
+			if(total == 0)
+				total = -1;
+			if(rp->status == STok)
+				rp->status = Status_SW;
+			break;
+		}
+		nbytes -= n;
+		total += n;
+	}
+	close(fd);
+	if(pid >= 0 && waitfor(pid)){
+		rp->status = Status_SW;
+		return -1;
+	}
+	return total;
+}
+
+static int32_t
+cmdwrite(ScsiReq *rp, int argc, char *argv[])
+{
+	int32_t n, prevsize = 0;
+	int64_t nbytes, total;
+	int fd, pid;
+	char *p;
+
+	nbytes = ~0ULL >> 1;
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		nbytes = strtoll(argv[1], &p, 0);
+		if(nbytes == 0 && p == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 1:
+		if((fd = mkfile(argv[0], OREAD, &pid)) == -1){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		break;
+	}
+	total = 0;
+	while(nbytes){
+		n = vlmin(nbytes, maxiosize);
+		if((n = read(fd, rwbuf, n)) == -1){
+			if(total == 0)
+				total = -1;
+			break;
+		}
+		if (n == 0)
+			break;
+		if (prevsize != n) {
+			print("tape block size=%ld\n", n);
+			prevsize = n;
+		}
+		if(SRwrite(rp, rwbuf, n) != n){
+			if(total == 0)
+				total = -1;
+			if(rp->status == STok)
+				rp->status = Status_SW;
+			break;
+		}
+		nbytes -= n;
+		total += n;
+	}
+	close(fd);
+	if(pid >= 0 && waitfor(pid)){
+		rp->status = Status_SW;
+		return -1;
+	}
+	return total;
+}
+
+static int32_t
+cmdseek(ScsiReq *rp, int argc, char *argv[])
+{
+	char *p;
+	int32_t offset;
+	int type;
+
+	type = 0;
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		if((type = strtol(argv[1], &p, 0)) == 0 && p == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 1:
+		if((offset = strtol(argv[0], &p, 0)) == 0 && p == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		break;
+	}
+	return SRseek(rp, offset, type);
+}
+
+static int32_t
+cmdfilemark(ScsiReq *rp, int argc, char *argv[])
+{
+	char *p;
+	uint32_t howmany;
+
+	howmany = 1;
+	if(argc && (howmany = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	return SRfilemark(rp, howmany);
+}
+
+static int32_t
+cmdspace(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t code;
+	int32_t howmany;
+	char option, *p;
+
+	code = 0x00;
+	howmany = 1;
+	while(argc && (*argv)[0] == '-'){
+		while((option = *++argv[0])){
+			switch(option){
+
+			case '-':
+				break;
+
+			case 'b':
+				code = 0x00;
+				break;
+
+			case 'f':
+				code = 0x01;
+				break;
+
+			default:
+				rp->status = Status_BADARG;
+				return -1;
+			}
+			break;
+		}
+		argc--; argv++;
+		if(option == '-')
+			break;
+	}
+	if(argc && ((howmany = strtol(argv[0], &p, 0)) == 0 && p == argv[0])){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	return SRspace(rp, code, howmany);
+}
+
+static int32_t
+cmdinquiry(ScsiReq *rp, int argc, char *argv[])
+{
+	int32_t status;
+	int i, n;
+	uint8_t *p;
+
+	USED(argc), USED(argv);
+	if((status = SRinquiry(rp)) != -1){
+		n = rp->inquiry[4]+4;
+		for(i = 0; i < MIN(8, n); i++)
+			Bprint(&bout, " %2.2X", rp->inquiry[i]);
+		p = &rp->inquiry[8];
+		n = MIN(n, sizeof(rp->inquiry)-8);
+		while(n && (*p == ' ' || *p == '\t' || *p == '\n')){
+			n--;
+			p++;
+		}
+		Bprint(&bout, "\t%.*s\n", n, (char*)p);
+	}
+	return status;
+}
+
+static int32_t
+cmdmodeselect6(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t list[MaxDirData];
+	int32_t nbytes, ul;
+	char *p;
+
+	memset(list, 0, sizeof list);
+	for(nbytes = 0; argc; argc--, argv++, nbytes++){
+		if((ul = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		list[nbytes] = ul;
+
+	}
+	if(!(rp->flags & Finqok) && SRinquiry(rp) == -1)
+		Bprint(&bout, "warning: couldn't determine whether SCSI-1/SCSI-2 mode");
+	return SRmodeselect6(rp, list, nbytes);
+}
+
+static int32_t
+cmdmodeselect10(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t list[MaxDirData];
+	int32_t nbytes, ul;
+	char *p;
+
+	memset(list, 0, sizeof list);
+	for(nbytes = 0; argc; argc--, argv++, nbytes++){
+		if((ul = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		list[nbytes] = ul;
+
+	}
+	if(!(rp->flags & Finqok) && SRinquiry(rp) == -1)
+		Bprint(&bout, "warning: couldn't determine whether SCSI-1/SCSI-2 mode");
+	return SRmodeselect10(rp, list, nbytes);
+}
+
+static int32_t
+cmdmodesense6(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t list[MaxDirData], *lp, page;
+	int32_t i, n, nbytes, status;
+	char *p;
+
+	nbytes = sizeof list;
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		if((nbytes = strtoul(argv[1], &p, 0)) == 0 && p == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 1:
+		if((page = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		break;
+
+	case 0:
+		page = Allmodepages;
+		break;
+	}
+	if((status = SRmodesense6(rp, page, list, nbytes)) == -1)
+		return -1;
+	lp = list;
+	nbytes = list[0];
+	Bprint(&bout, " Header\n   ");
+	for(i = 0; i < 4; i++){				/* header */
+		Bprint(&bout, " %2.2X", *lp);
+		lp++;
+	}
+	Bputc(&bout, '\n');
+
+	if(list[3]){					/* block descriptors */
+		for(n = 0; n < list[3]/8; n++){
+			Bprint(&bout, " Block %ld\n   ", n);
+			for(i = 0; i < 8; i++)
+				Bprint(&bout, " %2.2X", lp[i]);
+			Bprint(&bout, "    (density %2.2X", lp[0]);
+			Bprint(&bout, " blocks %d", (lp[1]<<16)|(lp[2]<<8)|lp[3]);
+			Bprint(&bout, " length %d)", (lp[5]<<16)|(lp[6]<<8)|lp[7]);
+			lp += 8;
+			nbytes -= 8;
+			Bputc(&bout, '\n');
+		}
+	}
+
+	while(nbytes > 0){				/* pages */
+		i = *(lp+1);
+		nbytes -= i+2;
+		Bprint(&bout, " Page %2.2X %d\n   ", *lp & 0x3F, *(lp+1));
+		lp += 2;
+		for(n = 0; n < i; n++){
+			if(n && ((n & 0x0F) == 0))
+				Bprint(&bout, "\n   ");
+			Bprint(&bout, " %2.2X", *lp);
+			lp++;
+		}
+		if(n && (n & 0x0F))
+			Bputc(&bout, '\n');
+	}
+	return status;
+}
+
+static int32_t
+cmdmodesense10(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t *list, *lp, page;
+	int32_t blen, i, n, nbytes, status;
+	char *p;
+
+	nbytes = MaxDirData;
+	switch(argc){
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		if((nbytes = strtoul(argv[1], &p, 0)) == 0 && p == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+	case 1:
+		if((page = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		break;
+
+	case 0:
+		page = Allmodepages;
+		break;
+	}
+	list = malloc(nbytes);
+	if(list == 0){
+		rp->status = STnomem;
+		return -1;
+	}
+	if((status = SRmodesense10(rp, page, list, nbytes)) == -1)
+		return -1;
+	lp = list;
+	nbytes = ((list[0]<<8)|list[1]);
+	Bprint(&bout, " Header\n   ");
+	for(i = 0; i < 8; i++){				/* header */
+		Bprint(&bout, " %2.2X", *lp);
+		lp++;
+	}
+	Bputc(&bout, '\n');
+
+	blen = (list[6]<<8)|list[7];
+	if(blen){					/* block descriptors */
+		for(n = 0; n < blen/8; n++){
+			Bprint(&bout, " Block %ld\n   ", n);
+			for(i = 0; i < 8; i++)
+				Bprint(&bout, " %2.2X", lp[i]);
+			Bprint(&bout, "    (density %2.2X", lp[0]);
+			Bprint(&bout, " blocks %d", (lp[1]<<16)|(lp[2]<<8)|lp[3]);
+			Bprint(&bout, " length %d)", (lp[5]<<16)|(lp[6]<<8)|lp[7]);
+			lp += 8;
+			nbytes -= 8;
+			Bputc(&bout, '\n');
+		}
+	}
+
+	/*
+	 * Special for ATA drives, page 0 is the drive info in 16-bit
+	 * chunks, little-endian, 256 in total. No decoding for now.
+	 */
+	if(page == 0){
+		for(n = 0; n < nbytes; n += 2){
+			if(n && ((n & 0x1F) == 0))
+				Bprint(&bout, "\n");
+			Bprint(&bout, " %4.4X", (*(lp+1)<<8)|*lp);
+			lp += 2;
+		}
+		Bputc(&bout, '\n');
+	}
+	else
+		while(nbytes > 0){				/* pages */
+			i = *(lp+1);
+			nbytes -= i+2;
+			Bprint(&bout, " Page %2.2X %d\n   ", *lp & 0x3F, lp[1]);
+			lp += 2;
+			for(n = 0; n < i; n++){
+				if(n && ((n & 0x0F) == 0))
+					Bprint(&bout, "\n   ");
+				Bprint(&bout, " %2.2X", *lp);
+				lp++;
+			}
+			if(n && (n & 0x0F))
+				Bputc(&bout, '\n');
+		}
+	free(list);
+	return status;
+}
+
+static int32_t
+start(ScsiReq *rp, int argc, char *argv[], uint8_t code)
+{
+	char *p;
+
+	if(argc && (code = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	return SRstart(rp, code);
+}
+
+static int32_t
+cmdstart(ScsiReq *rp, int argc, char *argv[])
+{
+	return start(rp, argc, argv, 1);
+}
+
+static int32_t
+cmdstop(ScsiReq *rp, int argc, char *argv[])
+{
+	return start(rp, argc, argv, 0);
+}
+
+static int32_t
+cmdeject(ScsiReq *rp, int argc, char *argv[])
+{
+	return start(rp, argc, argv, 2);
+}
+
+static int32_t
+cmdingest(ScsiReq *rp, int argc, char *argv[])
+{
+	return start(rp, argc, argv, 3);
+}
+
+static int32_t
+cmdcapacity(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t d[8];
+	int32_t n;
+
+	USED(argc), USED(argv);
+	if((n = SRrcapacity(rp, d)) == -1)
+		return -1;
+	Bprint(&bout, " %u %u\n",
+		d[0]<<24|d[1]<<16|d[2]<<8|d[3],
+		d[4]<<24|d[5]<<16|d[6]<<8|d[7]);
+	return n;
+}
+
+static int32_t
+cmdblank(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t type, track;
+	char *sp;
+
+	type = track = 0;
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		if((type = strtoul(argv[1], &sp, 0)) == 0 && sp == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		if(type > 6){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 1:
+		if((track = strtoul(argv[0], &sp, 0)) == 0 && sp == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 0:
+		break;
+	}
+	return SRblank(rp, type, track);
+}
+
+static int32_t
+cmdrtoc(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t d[100*8+4], format, track, *p;
+	char *sp;
+	int32_t n, nbytes;
+	int tdl;
+
+	format = track = 0;
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		if((format = strtoul(argv[1], &sp, 0)) == 0 && sp == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		if(format > 4){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 1:
+		if((track = strtoul(argv[0], &sp, 0)) == 0 && sp == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 0:
+		break;
+	}
+	if((nbytes = SRTOC(rp, d, sizeof d, format, track)) == -1){
+		if(rp->status == STok)
+			Bprint(&bout, "\t(probably empty)\n");
+		return -1;
+	}
+	tdl = (d[0]<<8)|d[1];
+	switch(format){
+
+	case 0:
+		Bprint(&bout, "\ttoc/pma data length: 0x%X\n", tdl);
+		Bprint(&bout, "\tfirst track number: %d\n", d[2]);
+		Bprint(&bout, "\tlast track number: %d\n", d[3]);
+		for(p = &d[4], n = tdl-2; n; n -= 8, p += 8){
+			Bprint(&bout, "\ttrack number: 0x%2.2X\n", p[2]);
+			Bprint(&bout, "\t\tcontrol: 0x%2.2X\n", p[1] & 0x0F);
+			Bprint(&bout, "\t\tblock address: 0x%X\n",
+				(p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7]);
+		}
+		break;
+
+	case 1:
+		Bprint(&bout, "\tsessions data length: 0x%X\n", tdl);
+		Bprint(&bout, "\tnumber of finished sessions: %d\n", d[2]);
+		Bprint(&bout, "\tunfinished session number: %d\n", d[3]);
+		for(p = &d[4], n = tdl-2; n; n -= 8, p += 8){
+			Bprint(&bout, "\tsession number: 0x%2.2X\n", p[0]);
+			Bprint(&bout, "\t\tfirst track number in session: 0x%2.2X\n",
+				p[2]);
+			Bprint(&bout, "\t\tlogical start address: 0x%X\n",
+				(p[5]<<16)|(p[6]<<8)|p[7]);
+		}
+		break;
+
+	case 2:
+		Bprint(&bout, "\tfull TOC data length: 0x%X\n", tdl);
+		Bprint(&bout, "\tnumber of finished sessions: %d\n", d[2]);
+		Bprint(&bout, "\tunfinished session number: %d\n", d[3]);
+		for(p = &d[4], n = tdl-2; n > 0; n -= 11, p += 11){
+			Bprint(&bout, "\tsession number: 0x%2.2X\n", p[0]);
+			Bprint(&bout, "\t\tcontrol: 0x%2.2X\n", p[1] & 0x0F);
+			Bprint(&bout, "\t\tADR: 0x%2.2X\n", (p[1]>>4) & 0x0F);
+			Bprint(&bout, "\t\tTNO: 0x%2.2X\n", p[2]);
+			Bprint(&bout, "\t\tPOINT: 0x%2.2X\n", p[3]);
+			Bprint(&bout, "\t\tMin: 0x%2.2X\n", p[4]);
+			Bprint(&bout, "\t\tSec: 0x%2.2X\n", p[5]);
+			Bprint(&bout, "\t\tFrame: 0x%2.2X\n", p[6]);
+			Bprint(&bout, "\t\tZero: 0x%2.2X\n", p[7]);
+			Bprint(&bout, "\t\tPMIN: 0x%2.2X\n", p[8]);
+			Bprint(&bout, "\t\tPSEC: 0x%2.2X\n", p[9]);
+			Bprint(&bout, "\t\tPFRAME: 0x%2.2X\n", p[10]);
+		}
+		break;
+	case 3:
+		Bprint(&bout, "\tPMA data length: 0x%X\n", tdl);
+		for(p = &d[4], n = tdl-2; n > 0; n -= 11, p += 11){
+			Bprint(&bout, "\t\tcontrol: 0x%2.2X\n", p[1] & 0x0F);
+			Bprint(&bout, "\t\tADR: 0x%2.2X\n", (p[1]>>4) & 0x0F);
+			Bprint(&bout, "\t\tTNO: 0x%2.2X\n", p[2]);
+			Bprint(&bout, "\t\tPOINT: 0x%2.2X\n", p[3]);
+			Bprint(&bout, "\t\tMin: 0x%2.2X\n", p[4]);
+			Bprint(&bout, "\t\tSec: 0x%2.2X\n", p[5]);
+			Bprint(&bout, "\t\tFrame: 0x%2.2X\n", p[6]);
+			Bprint(&bout, "\t\tZero: 0x%2.2X\n", p[7]);
+			Bprint(&bout, "\t\tPMIN: 0x%2.2X\n", p[8]);
+			Bprint(&bout, "\t\tPSEC: 0x%2.2X\n", p[9]);
+			Bprint(&bout, "\t\tPFRAME: 0x%2.2X\n", p[10]);
+		}
+		break;
+
+	case 4:
+		Bprint(&bout, "\tATIP data length: 0x%X\n", tdl);
+		break;
+
+	}
+	for(n = 0; n < nbytes; n++){
+		if(n && ((n & 0x0F) == 0))
+			Bprint(&bout, "\n");
+		Bprint(&bout, " %2.2X", d[n]);
+	}
+	if(n && (n & 0x0F))
+		Bputc(&bout, '\n');
+	return nbytes;
+}
+
+static int32_t
+cmdrdiscinfo(ScsiReq *rp, int argc, char *c[])
+{
+	uint8_t d[MaxDirData];
+	int dl;
+	int32_t n, nbytes;
+
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 0:
+		break;
+	}
+	if((nbytes = SRrdiscinfo(rp, d, sizeof d)) == -1)
+		return -1;
+
+	dl = (d[0]<<8)|d[1];
+	Bprint(&bout, "\tdata length: 0x%X\n", dl);
+	Bprint(&bout, "\tinfo[2] 0x%2.2X\n", d[2]);
+	switch(d[2] & 0x03){
+
+	case 0:
+		Bprint(&bout, "\t\tEmpty\n");
+		break;
+
+	case 1:
+		Bprint(&bout, "\t\tIncomplete disc (Appendable)\n");
+		break;
+
+	case 2:
+		Bprint(&bout, "\t\tComplete (CD-ROM or last session is closed and has no next session pointer)\n");
+		break;
+
+	case 3:
+		Bprint(&bout, "\t\tReserved\n");
+		break;
+	}
+	switch((d[2]>>2) & 0x03){
+
+	case 0:
+		Bprint(&bout, "\t\tEmpty Session\n");
+		break;
+
+	case 1:
+		Bprint(&bout, "\t\tIncomplete Session\n");
+		break;
+
+	case 2:
+		Bprint(&bout, "\t\tReserved\n");
+		break;
+
+	case 3:
+		Bprint(&bout, "\t\tComplete Session (only possible when disc Status is Complete)\n");
+		break;
+	}
+	if(d[2] & 0x10)
+		Bprint(&bout, "\t\tErasable\n");
+	Bprint(&bout, "\tNumber of First Track on Disc %u\n", d[3]);
+	Bprint(&bout, "\tNumber of Sessions %u\n", d[4]);
+	Bprint(&bout, "\tFirst Track Number in Last Session %u\n", d[5]);
+	Bprint(&bout, "\tLast Track Number in Last Session %u\n", d[6]);
+	Bprint(&bout, "\tinfo[7] 0x%2.2X\n", d[7]);
+	if(d[7] & 0x20)
+		Bprint(&bout, "\t\tUnrestricted Use Disc\n");
+	if(d[7] & 0x40)
+		Bprint(&bout, "\t\tDisc Bar Code Valid\n");
+	if(d[7] & 0x80)
+		Bprint(&bout, "\t\tDisc ID Valid\n");
+	Bprint(&bout, "\tinfo[8] 0x%2.2X\n", d[8]);
+	switch(d[8]){
+
+	case 0x00:
+		Bprint(&bout, "\t\tCD-DA or CD-ROM Disc\n");
+		break;
+
+	case 0x10:
+		Bprint(&bout, "\t\tCD-I Disc\n");
+		break;
+
+	case 0x20:
+		Bprint(&bout, "\t\tCD-ROM XA Disc\n");
+		break;
+
+	case 0xFF:
+		Bprint(&bout, "\t\tUndefined\n");
+		break;
+
+	default:
+		Bprint(&bout, "\t\tReserved\n");
+		break;
+	}
+	Bprint(&bout, "\tLast Session lead-in Start Time M/S/F: 0x%2.2X/0x%2.2X/0x%2.2X\n",
+		d[17], d[18], d[19]);
+	Bprint(&bout, "\tLast Possible Start Time for Start of lead-out M/S/F: 0x%2.2X/0x%2.2X/0x%2.2X\n",
+		d[21], d[22], d[23]);
+
+	for(n = 0; n < nbytes; n++){
+		if(n && ((n & 0x0F) == 0))
+			Bprint(&bout, "\n");
+		Bprint(&bout, " %2.2X", d[n]);
+	}
+	if(n && (n & 0x0F))
+		Bputc(&bout, '\n');
+
+	return nbytes;
+}
+
+static int32_t
+cmdrtrackinfo(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t d[MaxDirData], track;
+	char *sp;
+	int32_t n, nbytes;
+	int dl;
+
+	track = 0;
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 1:
+		if((track = strtoul(argv[0], &sp, 0)) == 0 && sp == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 0:
+		break;
+	}
+	if((nbytes = SRrtrackinfo(rp, d, sizeof d, track)) == -1)
+		return -1;
+
+	dl = (d[0]<<8)|d[1];
+	Bprint(&bout, "\tdata length: 0x%X\n", dl);
+	Bprint(&bout, "\\Track Number %d\n", d[2]);
+	Bprint(&bout, "\\Session Number %d\n", d[3]);
+	Bprint(&bout, "\tinfo[4] 0x%2.2X\n", d[5]);
+	Bprint(&bout, "\t\tTrack Mode 0x%2.2X: ", d[5] & 0x0F);
+	switch(d[5] & 0x0F){
+	case 0x00:
+	case 0x02:
+		Bprint(&bout, "2 audio channels without pre-emphasis\n");
+		break;
+	case 0x01:
+	case 0x03:
+		Bprint(&bout, "2 audio channels with pre-emphasis of 50/15µs\n");
+		break;
+	case 0x08:
+	case 0x0A:
+		Bprint(&bout, "audio channels without pre-emphasis (reserved in CD-R/RW)\n");
+		break;
+	case 0x09:
+	case 0x0B:
+		Bprint(&bout, "audio channels with pre-emphasis of 50/15µs (reserved in CD-R/RW)\n");
+		break;
+	case 0x04:
+	case 0x06:
+		Bprint(&bout, "Data track, recorded uninterrupted\n");
+		break;
+	case 0x05:
+	case 0x07:
+		Bprint(&bout, "Data track, recorded incremental\n");
+		break;
+	default:
+		Bprint(&bout, "(mode unknown)\n");
+		break;
+	}
+	if(d[5] & 0x10)
+		Bprint(&bout, "\t\tCopy\n");
+	if(d[5] & 0x20)
+		Bprint(&bout, "\t\tDamage\n");
+	Bprint(&bout, "\tinfo[6] 0x%2.2X\n", d[6]);
+	Bprint(&bout, "\t\tData Mode 0x%2.2X: ", d[6] & 0x0F);
+	switch(d[6] & 0x0F){
+	case 0x01:
+		Bprint(&bout, "Mode 1 (ISO/IEC 10149)\n");
+		break;
+	case 0x02:
+		Bprint(&bout, "Mode 2 (ISO/IEC 10149 or CD-ROM XA)\n");
+		break;
+	case 0x0F:
+		Bprint(&bout, "Data Block Type unknown (no track descriptor block)\n");
+		break;
+	default:
+		Bprint(&bout, "(Reserved)\n");
+		break;
+	}
+	if(d[6] & 0x10)
+		Bprint(&bout, "\t\tFP\n");
+	if(d[6] & 0x20)
+		Bprint(&bout, "\t\tPacket\n");
+	if(d[6] & 0x40)
+		Bprint(&bout, "\t\tBlank\n");
+	if(d[6] & 0x80)
+		Bprint(&bout, "\t\tRT\n");
+	Bprint(&bout, "\tTrack Start Address 0x%8.8X\n",
+		(d[8]<<24)|(d[9]<<16)|(d[10]<<8)|d[11]);
+	if(d[7] & 0x01)
+		Bprint(&bout, "\tNext Writeable Address 0x%8.8X\n",
+			(d[12]<<24)|(d[13]<<16)|(d[14]<<8)|d[15]);
+	Bprint(&bout, "\tFree Blocks 0x%8.8X\n",
+		(d[16]<<24)|(d[17]<<16)|(d[18]<<8)|d[19]);
+	if((d[6] & 0x30) == 0x30)
+		Bprint(&bout, "\tFixed Packet Size 0x%8.8X\n",
+			(d[20]<<24)|(d[21]<<16)|(d[22]<<8)|d[23]);
+	Bprint(&bout, "\tTrack Size 0x%8.8X\n",
+		(d[24]<<24)|(d[25]<<16)|(d[26]<<8)|d[27]);
+
+	for(n = 0; n < nbytes; n++){
+		if(n && ((n & 0x0F) == 0))
+			Bprint(&bout, "\n");
+		Bprint(&bout, " %2.2X", d[n]);
+	}
+	if(n && (n & 0x0F))
+		Bputc(&bout, '\n');
+
+	return nbytes;
+}
+
+static int32_t
+cmdcdpause(ScsiReq *rp, int argc, char *argv[])
+{
+	USED(argc), USED(argv);
+	return SRcdpause(rp, 0);
+}
+
+static int32_t
+cmdcdresume(ScsiReq *rp, int argc, char *argv[])
+{
+	USED(argc), USED(argv);
+	return SRcdpause(rp, 1);
+}
+
+static int32_t
+cmdcdstop(ScsiReq *rp, int argc, char *argv[])
+{
+	USED(argc), USED(argv);
+	return SRcdstop(rp);
+}
+
+static int32_t
+cmdcdplay(ScsiReq *rp, int argc, char *argv[])
+{
+	int32_t length, start;
+	char *sp;
+	int raw;
+
+	raw = 0;
+	start = 0;
+	if(argc && strcmp("-r", argv[0]) == 0){
+		raw = 1;
+		argc--, argv++;
+	}
+
+	length = 0xFFFFFFFF;
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		if(!raw || ((length = strtol(argv[1], &sp, 0)) == 0 && sp == argv[1])){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 1:
+		if((start = strtol(argv[0], &sp, 0)) == 0 && sp == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 0:
+		break;
+	}
+
+	return SRcdplay(rp, raw, start, length);
+}
+
+static int32_t
+cmdcdload(ScsiReq *rp, int argc, char *argv[])
+{
+	char *p;
+	uint32_t slot;
+
+	slot = 0;
+	if(argc && (slot = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	return SRcdload(rp, 1, slot);
+}
+
+static int32_t
+cmdcdunload(ScsiReq *rp, int argc, char *argv[])
+{
+	char *p;
+	uint32_t slot;
+
+	slot = 0;
+	if(argc && (slot = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	return SRcdload(rp, 0, slot);
+}
+
+static int32_t
+cmdcdstatus(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t *list, *lp;
+	int32_t nbytes, status;
+	int i, slots;
+
+	USED(argc), USED(argv);
+
+	nbytes = 4096;
+	list = malloc(nbytes);
+	if(list == 0){
+		rp->status = STnomem;
+		return -1;
+	}
+	status = SRcdstatus(rp, list, nbytes);
+	if(status == -1){
+		free(list);
+		return -1;
+	}
+
+	lp = list;
+	Bprint(&bout, " Header\n   ");
+	for(i = 0; i < 8; i++){				/* header */
+		Bprint(&bout, " %2.2X", *lp);
+		lp++;
+	}
+	Bputc(&bout, '\n');
+
+	slots = ((list[6]<<8)|list[7])/4;
+	Bprint(&bout, " Slots\n   ");
+	while(slots--){
+		Bprint(&bout, " %2.2X %2.2X %2.2X %2.2X\n   ",
+			*lp, *(lp+1), *(lp+2), *(lp+3));
+		lp += 4;
+	}
+
+	free(list);
+	return status;
+}
+
+static int32_t
+cmdeinit(ScsiReq *rp, int argc, char *argv[])
+{
+	USED(argc), USED(argv);
+	return SReinitialise(rp);
+}
+
+static int32_t
+cmdmmove(ScsiReq *rp, int argc, char *argv[])
+{
+	int transport, source, destination, invert;
+	char *p;
+
+	invert = 0;
+
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 4:
+		if((invert = strtoul(argv[3], &p, 0)) == 0 && p == argv[3]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 3:
+		if((transport = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		if((source = strtoul(argv[1], &p, 0)) == 0 && p == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		if((destination = strtoul(argv[2], &p, 0)) == 0 && p == argv[2]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		break;
+	}
+
+	return SRmmove(rp, transport, source, destination, invert);
+}
+
+static int32_t
+cmdestatus(ScsiReq *rp, int argc, char *argv[])
+{
+	uint8_t *list, *lp, type;
+	int32_t d, i, n, nbytes, status;
+	char *p;
+
+	type = 0;
+	nbytes = 4096;
+
+	switch(argc){
+
+	default:
+		rp->status = Status_BADARG;
+		return -1;
+
+	case 2:
+		if((nbytes = strtoul(argv[1], &p, 0)) == 0 && p == argv[1]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		/*FALLTHROUGH*/
+
+	case 1:
+		if((type = strtoul(argv[0], &p, 0)) == 0 && p == argv[0]){
+			rp->status = Status_BADARG;
+			return -1;
+		}
+		break;
+
+	case 0:
+		break;
+	}
+
+	list = malloc(nbytes);
+	if(list == 0){
+		rp->status = STnomem;
+		return -1;
+	}
+	status = SRestatus(rp, type, list, nbytes);
+	if(status == -1){
+		free(list);
+		return -1;
+	}
+
+	lp = list;
+	nbytes = ((lp[5]<<16)|(lp[6]<<8)|lp[7])-8;
+	Bprint(&bout, " Header\n   ");
+	for(i = 0; i < 8; i++){				/* header */
+		Bprint(&bout, " %2.2X", *lp);
+		lp++;
+	}
+	Bputc(&bout, '\n');
+
+	while(nbytes > 0){				/* pages */
+		i = ((lp[5]<<16)|(lp[6]<<8)|lp[7]);
+		nbytes -= i+8;
+		Bprint(&bout, " Type");
+		for(n = 0; n < 8; n++)			/* header */
+			Bprint(&bout, " %2.2X", lp[n]);
+		Bprint(&bout, "\n   ");
+		d = (lp[2]<<8)|lp[3];
+		lp += 8;
+		for(n = 0; n < i; n++){
+			if(n && (n % d) == 0)
+				Bprint(&bout, "\n   ");
+			Bprint(&bout, " %2.2X", *lp);
+			lp++;
+		}
+		if(n && (n % d))
+			Bputc(&bout, '\n');
+	}
+
+	free(list);
+	return status;
+}
+
+static int32_t
+cmdhelp(ScsiReq *rp, int argc, char *argv[])
+{
+	ScsiCmd *cp;
+	char *p;
+
+	USED(rp);
+	if(argc)
+		p = argv[0];
+	else
+		p = 0;
+	for(cp = scsicmds; cp->name; cp++){
+		if(p == 0 || strcmp(p, cp->name) == 0)
+			Bprint(&bout, "%s\n", cp->help);
+	}
+	return 0;
+}
+
+static int32_t
+cmdprobe(ScsiReq *rp, int argc, char *argv[])
+{
+	char buf[32];
+	ScsiReq scsireq;
+	char *ctlr, *unit;
+
+	USED(argc), USED(argv);
+	rp->status = STok;
+	scsireq.flags = 0;
+
+	for(ctlr="CDEFGHIJ0123456789abcdef"; *ctlr; ctlr++) {
+		/*
+		 * I can guess how many units you have.
+		 * SATA controllers can have more than two drives each.
+		 */
+		if(*ctlr >= 'C' && *ctlr <= 'D')
+			unit = "01";
+		else if((*ctlr >= '0' && *ctlr <= '9')
+		     || (*ctlr >= 'a' && *ctlr <= 'f'))
+			unit = "0123456789abcdef";	/* allow wide scsi */
+		else
+			unit = "01234567";
+
+		for(; *unit; unit++){
+			sprint(buf, "/dev/sd%c%c", *ctlr, *unit);
+			if(SRopenraw(&scsireq, buf) == -1)
+				continue;
+			SRreqsense(&scsireq);
+			switch(scsireq.status){
+			case STok:
+			case Status_SD:
+				Bprint(&bout, "%s: ", buf);
+				cmdinquiry(&scsireq, 0, 0);
+				break;
+			}
+			SRclose(&scsireq);
+		}
+	}
+	return 0;
+}
+
+static int32_t
+cmdclose(ScsiReq *rp, int argc, char *argv[])
+{
+	USED(argc), USED(argv);
+	return SRclose(rp);
+}
+
+static int32_t
+cmdopen(ScsiReq *rp, int argc, char *argv[])
+{
+	int raw;
+	int32_t status;
+
+	raw = 0;
+	if(argc && strcmp("-r", argv[0]) == 0){
+		raw = 1;
+		argc--, argv++;
+	}
+	if(argc != 1){
+		rp->status = Status_BADARG;
+		return -1;
+	}
+	if(raw == 0){
+		if((status = SRopen(rp, argv[0])) != -1 && verbose)
+			Bprint(&bout, "%sblock size: %ld\n",
+				rp->flags&Fbfixed? "fixed ": "", rp->lbsize);
+	}
+	else {
+		status = SRopenraw(rp, argv[0]);
+		rp->lbsize = 512;
+	}
+	return status;
+}
+
+static ScsiCmd scsicmds[] = {
+	{ "ready",	cmdready,	1,		/*[0x00]*/
+	  "ready",
+	},
+	{ "rewind",	cmdrewind,	1,		/*[0x01]*/
+	  "rewind",
+	},
+	{ "rezero",	cmdrewind,	1,		/*[0x01]*/
+	  "rezero",
+	},
+	{ "reqsense",	cmdreqsense,	1,		/*[0x03]*/
+	  "reqsense",
+	},
+	{ "format",	cmdformat,	0,		/*[0x04]*/
+	  "format",
+	},
+	{ "rblimits",	cmdrblimits,	1,		/*[0x05]*/
+	  "rblimits",
+	},
+	{ "read",	cmdread,	1,		/*[0x08]*/
+	  "read [|]file [nbytes]",
+	},
+	{ "write",	cmdwrite,	1,		/*[0x0A]*/
+	  "write [|]file [nbytes]",
+	},
+	{ "seek",	cmdseek,	1,		/*[0x0B]*/
+	  "seek offset [whence]",
+	},
+	{ "filemark",	cmdfilemark,	1,		/*[0x10]*/
+	  "filemark [howmany]",
+	},
+	{ "space",	cmdspace,	1,		/*[0x11]*/
+	  "space [-f] [-b] [[--] howmany]",
+	},
+	{ "inquiry",	cmdinquiry,	1,		/*[0x12]*/
+	  "inquiry",
+	},
+	{ "modeselect6",cmdmodeselect6,	1,		/*[0x15] */
+	  "modeselect6 bytes...",
+	},
+	{ "modeselect",	cmdmodeselect10, 1,		/*[0x55] */
+	  "modeselect bytes...",
+	},
+	{ "modesense6",	cmdmodesense6,	1,		/*[0x1A]*/
+	  "modesense6 [page [nbytes]]",
+	},
+	{ "modesense",	cmdmodesense10, 1,		/*[0x5A]*/
+	  "modesense [page [nbytes]]",
+	},
+	{ "start",	cmdstart,	1,		/*[0x1B]*/
+	  "start [code]",
+	},
+	{ "stop",	cmdstop,	1,		/*[0x1B]*/
+	  "stop",
+	},
+	{ "eject",	cmdeject,	1,		/*[0x1B]*/
+	  "eject",
+	},
+	{ "ingest",	cmdingest,	1,		/*[0x1B]*/
+	  "ingest",
+	},
+	{ "capacity",	cmdcapacity,	1,		/*[0x25]*/
+	  "capacity",
+	},
+
+	{ "blank",	cmdblank,	1,		/*[0xA1]*/
+	  "blank [track/LBA [type]]",
+	},
+//	{ "synccache",	cmdsynccache,	1,		/*[0x35]*/
+//	  "synccache",
+//	},
+	{ "rtoc",	cmdrtoc,	1,		/*[0x43]*/
+	  "rtoc [track/session-number [format]]",
+	},
+	{ "rdiscinfo",	cmdrdiscinfo,	1,		/*[0x51]*/
+	  "rdiscinfo",
+	},
+	{ "rtrackinfo",	cmdrtrackinfo,	1,		/*[0x52]*/
+	  "rtrackinfo [track]",
+	},
+
+	{ "cdpause",	cmdcdpause,	1,		/*[0x4B]*/
+	  "cdpause",
+	},
+	{ "cdresume",	cmdcdresume,	1,		/*[0x4B]*/
+	  "cdresume",
+	},
+	{ "cdstop",	cmdcdstop,	1,		/*[0x4E]*/
+	  "cdstop",
+	},
+	{ "cdplay",	cmdcdplay,	1,		/*[0xA5]*/
+	  "cdplay [track-number] or [-r [LBA [length]]]",
+	},
+	{ "cdload",	cmdcdload,	1,		/*[0xA6*/
+	  "cdload [slot]",
+	},
+	{ "cdunload",	cmdcdunload,	1,		/*[0xA6]*/
+	  "cdunload [slot]",
+	},
+	{ "cdstatus",	cmdcdstatus,	1,		/*[0xBD]*/
+	  "cdstatus",
+	},
+//	{ "getconf",	cmdgetconf,	1,		/*[0x46]*/
+//	  "getconf",
+//	},
+
+//	{ "fwaddr",	cmdfwaddr,	1,		/*[0xE2]*/
+//	  "fwaddr [track [mode [npa]]]",
+//	},
+//	{ "treserve",	cmdtreserve,	1,		/*[0xE4]*/
+//	  "treserve nbytes",
+//	},
+//	{ "trackinfo",	cmdtrackinfo,	1,		/*[0xE5]*/
+//	  "trackinfo [track]",
+//	},
+//	{ "wtrack",	cmdwtrack,	1,		/*[0xE6]*/
+//	  "wtrack [|]file [nbytes [track [mode]]]",
+//	},
+//	{ "load",	cmdload,	1,		/*[0xE7]*/
+//	  "load",
+//	},
+//	{ "unload",	cmdunload,	1,		/*[0xE7]*/
+//	  "unload",
+//	},
+//	{ "fixation",	cmdfixation,	1,		/*[0xE9]*/
+//	  "fixation [toc-type]",
+//	},
+	{ "einit",	cmdeinit,	1,		/*[0x07]*/
+	  "einit",
+	},
+	{ "estatus",	cmdestatus,	1,		/*[0xB8]*/
+	  "estatus",
+	},
+	{ "mmove",	cmdmmove,	1,		/*[0xA5]*/
+	  "mmove transport source destination [invert]",
+	},
+
+	{ "help",	cmdhelp,	0,
+	  "help",
+	},
+	{ "probe",	cmdprobe,	0,
+	  "probe",
+	},
+	{ "close",	cmdclose,	1,
+	  "close",
+	},
+	{ "open",	cmdopen,	0,
+	  "open [-r] sddev",
+	},
+	{ 0, 0 },
+};
+
+#define	SEP(c)	(((c)==' ')||((c)=='\t')||((c)=='\n'))
+
+static char *
+tokenise(char *s, char **start, char **end)
+{
+	char *to;
+	Rune r;
+	int n;
+
+	while(*s && SEP(*s))				/* skip leading white space */
+		s++;
+	to = *start = s;
+	while(*s){
+		n = chartorune(&r, s);
+		if(SEP(r)){
+			if(to != *start)		/* we have data */
+				break;
+			s += n;				/* null string - keep looking */
+			while(*s && SEP(*s))
+				s++;
+			to = *start = s;
+		}
+		else if(r == '\''){
+			s += n;				/* skip leading quote */
+			while(*s){
+				n = chartorune(&r, s);
+				if(r == '\''){
+					if(s[1] != '\'')
+						break;
+					s++;		/* embedded quote */
+				}
+				while (n--)
+					*to++ = *s++;
+			}
+			if(!*s)				/* no trailing quote */
+				break;
+			s++;				/* skip trailing quote */
+		}
+		else  {
+			while(n--)
+				*to++ = *s++;
+		}
+	}
+	*end = to;
+	return s;
+}
+
+static int
+parse(char *s, char *fields[], int nfields)
+{
+	int c, argc;
+	char *start, *end;
+
+	argc = 0;
+	c = *s;
+	while(c){
+		s = tokenise(s, &start, &end);
+		c = *s++;
+		if(*start == 0)
+			break;
+		if(argc >= nfields-1)
+			return -1;
+		*end = 0;
+		fields[argc++] = start;
+	}
+	fields[argc] = 0;
+	return argc;
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-6eq] [-m maxiosize] [[-r] /dev/sdXX]\n", argv0);
+	exits("usage");
+}
+
+static struct {
+	int	status;
+	char*	description;
+} description[] = {
+	{STnomem,		"buffer allocation failed"},
+	{STtimeout,		"bus timeout"},
+	{STharderr,		"controller error of some kind"},
+	{STok,  "good"},
+	{STcheck,		"check condition"},
+	{STcondmet,		"condition met/good"},
+	{STbusy,  "busy "},
+	{STintok,		"intermediate/good"},
+	{STintcondmet,		"intermediate/condition met/good"},
+	{STresconf,		"reservation conflict"},
+	{STterminated,		"command terminated"},
+	{STqfull,		"queue full"},
+
+	{Status_SD,		"sense-data available"},
+	{Status_SW,		"internal software error"},
+	{Status_BADARG,		"bad argument to request"},
+
+	{0, 0},
+};
+
+void
+main(int argc, char *argv[])
+{
+	ScsiReq target;
+	char *ap, *av[256];
+	int ac, i, raw = 0;
+	ScsiCmd *cp;
+	long status;
+
+	ARGBEGIN {
+	case 'e':
+		exabyte = 1;
+		/* fallthrough */
+	case '6':
+		force6bytecmds = 1;
+		break;
+	case 'm':
+		ap = ARGF();
+		if(ap == nil)
+			usage();
+		maxiosize = atol(ap);
+		if(maxiosize < 512 || maxiosize > MaxIOsize)
+			sysfatal("max-xfer < 512 or > %d", MaxIOsize);
+		break;
+	case 'r':			/* must be last option and not bundled */
+		raw++;
+		break;
+	case 'q':
+		verbose = 0;
+		break;
+	default:
+		usage();
+	} ARGEND
+
+	if(Binit(&bin, 0, OREAD) == Beof || Binit(&bout, 1, OWRITE) == Beof){
+		fprint(2, "%s: can't init bio: %r\n", argv0);
+		exits("Binit");
+	}
+
+	memset(&target, 0, sizeof target);
+	if (raw) {			/* hack for -r */
+		++argc;
+		--argv;
+	}
+	if(argc && cmdopen(&target, argc, argv) == -1) {
+		fprint(2, "open failed\n");
+		usage();
+	}
+	Bflush(&bout);
+
+	while((ap = Brdline(&bin, '\n'))){
+		ap[Blinelen(&bin)-1] = 0;
+		switch(ac = parse(ap, av, nelem(av))){
+
+		default:
+			for(cp = scsicmds; cp->name; cp++){
+				if(strcmp(cp->name, av[0]) == 0)
+					break;
+			}
+			if(cp->name == 0){
+				Bprint(&bout, "eh?\n");
+				break;
+			}
+			if((target.flags & Fopen) == 0 && cp->open){
+				Bprint(&bout, "no current target\n");
+				break;
+			}
+			if((status = (*cp->f)(&target, ac-1, &av[1])) != -1){
+				if(verbose)
+					Bprint(&bout, "ok %ld\n", status);
+				break;
+			}
+			for(i = 0; description[i].description; i++){
+				if(target.status != description[i].status)
+					continue;
+				if(target.status == Status_SD)
+					makesense(&target);
+				else
+					Bprint(&bout, "%s\n", description[i].description);
+				break;
+			}
+			break;
+
+		case -1:
+			Bprint(&bout, "eh?\n");
+			break;
+
+		case 0:
+			break;
+		}
+		Bflush(&bout);
+	}
+	exits(0);
+}
+
+/* USB mass storage fake */
+int32_t
+umsrequest(Umsc *umsc, ScsiPtr *cmd, ScsiPtr *data, int *status)
+{
+	USED(umsc), USED(data), USED(cmd);
+	*status = STharderr;
+	return -1;
+}

+ 53 - 0
sys/src/cmd/scuzz/sense.c

@@ -0,0 +1,53 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <disk.h>
+#include "scsireq.h"
+
+extern Biobuf bout;
+
+static char* key[16] = {
+	"no sense",
+	"recovered error",
+	"not ready",
+	"medium error",
+	"hardware error",
+	"illegal request",
+	"unit attention",
+	"data protect",
+	"blank check",
+	"vendor specific",
+	"copy aborted",
+	"aborted command",
+	"equal",
+	"volume overflow",
+	"miscompare",
+	"reserved",
+};
+
+/*
+ * use libdisk to read /sys/lib/scsicodes
+ */
+void
+makesense(ScsiReq *rp)
+{
+	char *s;
+	int i;
+
+	Bprint(&bout, "sense data: %s", key[rp->sense[2] & 0x0F]);
+	if(rp->sense[7] >= 5 && (s = scsierror(rp->sense[0xc], rp->sense[0xd])))
+		Bprint(&bout, ": %s", s);
+	Bprint(&bout, "\n\t");
+	for(i = 0; i < 8+rp->sense[7]; i++)
+		Bprint(&bout, " %2.2x", rp->sense[i]);
+	Bprint(&bout, "\n");
+}