Taming the UDA50

Chris Torek chris at umcp-cs.UUCP
Sun Jan 26 15:18:46 AEST 1986


For those of you who do not have time to read this because your
RA81 is dying: `event 0353' means you have a `drive detected drive
error'---something is wrong with the RA81.  `event 0350' is bad
news: a hard ECC error.  Without a better driver, or a standalone
bad block forwarding program, you will have to have DEC reformat
the disk.

--------

There has been a lot of concern about UDA50 error messages, and
what to do about them.  Not much is generally known about this;
and the standard 4.2 and 4.3 BSD Unix UDA50 drivers do a terrible
job of reporting and correcting errors.  This is my attempt to shed
a bit of light on the matter.

Before I even begin, I need to make a few disclaimers.  I have not
read any DEC manuals on this stuff; all my information comes from
suspect sources like working drivers and Emulex manuals.  To quote
the Emulex SC41/MS manual, `a comprehensive description of MSCP
may be ordered from DEC's Software Distribution Center, Order
Administration/Processing, 20 Forbes Rd., Northboro, MA 01532'.
(Of course if I had read that I would have to worry about copyrights
and such, and besides, *you* try to get a University to pay for
useful things . . . .  :-/ )

First, let me define a few terms.  (I always hate not knowing what
each acronym stands for.)

	MSCP - DEC's Mass Storage Control Protocol.  This defines
	the set of commands and responses from UDA50s (and incidentally
	from TU81 controllers as well).

	SDI - Storage Disk Interconnect.

	DSA - Digital Storage Architecture (or maybe Disk Storage,
	or something like that).

	RCT - Replacement and Caching Tables.  These define where
	bad block replacements are and so forth.  There are multiple
	copies of these: four on RA81s, 10 on CDC 9771 drives with
	the Emulex SC41/MS controller.

	LBN - Logical Block Number.  This is just like <cyl,trk,sec>
	on other drives, mashed together.  In fact, at least on
	RA81s, LBNs start at the outside edge of the disk and work
	in just like <cyl*M+trk*N+sec> on any other drive.  However,
	they are only piecewise continuous, because of RBNs:

	RBN - Replacement Block Number.  Replacement blocks are
	scattered about the disk, usually one per track.  Replacements
	are normally allocated on the same track as the bad block,
	if possible, to avoid seek or head switch delays.

	DBN - Diagnostic Block Number.  On RA81s the diagnostic
	blocks are separate from the logical and replacement blocks,
	I think.  I suspect they are on the innermost cylinders,
	but only because that is the best place to put test blocks
	since the data density is highest there.

When an MSCP controller has trouble with a drive, it does several
things.  It tries to correct the problem itself; and it (usually)
reports the problem to the host.  This report is called an `error
datagram'.  It looks like this:

     0		     1			 2		     3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |         Message Length        |    Credits    | Virt Circ ID  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                    Command Reference Number                   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |          Unit Number          |       Sequence Number         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |     Format    |      Flags    |         Event Code            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    +                         Controller ID                         +
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Ctlr Software | Ctlr Hardware |        Multi-Unit Code        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    +                            Unit ID                            +
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Unit Software | Unit Hardware |            Group              |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                     Volume Serial Number                      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                            Header                             |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    +                                                               +
    |                    SDI Status Information                     |
    +                                                               +
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The first four bytes are part of the `device specific header'; the
remainder is the same for all MSCP devices.  The message length is
just the length in bytes of the complete datagram.  The field called
`credits' really consists of four bits of credits (bits 0-3) and
four bits of message type (bits 4-7).  For error datagrams the
message type is `datagram' (1).  (0 is Sequential, 2 is credits,
3-14 are undefined, and 15 is reserved for maintenance; these
have a different format for the remainder of the packet.)

[For those of you who have not realized it yet, MSCP is modeled on
networking packets, so it has circuit IDs and sequence numbers and
all that rot.  Makes the code bigger, so DEC can sell more memory
:-).  But back to the packet format:]

Next is the command reference number, which I presume is just the
sequence number of the original command that caused the error.
Unix ignores it.  After this is two bytes of unit number; this is
just the drive number off the RA81 or whatever.  Then there is the
sequence number, also ignored.  After this is the format.  This
defines what kind of error occurred.  Currently defined format
numbers are:

	Controller Error		0
		(who knows)
	Host Memory Access Error	1
		(could not get to Vax memory; the attempted
		address is in the lower longword of the unit ID)
	Disk Transfer Error		2
		(ECC errors and the like; normal stuff)
	SDI Error			3
		(who knows)
	Small Disk Error		4
		(who knows.. `group' contains the cylinder
		number involved)

Other values are undefined.  The normal kinds of errors cause
Disk Transfer Error reports, so I will describe the remainder
of the fields as they apply to these.

After the format come flags:

	Operation Successful		0x80
	Operation Continuing		0x40
	Sequence Number Reset		0x01

(These are all I know about; there may be others.)  The first two
indicate that the error was corrected or is now being corrected,
and that no host action is required (though a later report may say
otherwise).  I am not sure what `Sequence Number Reset' means.

After the flags comes an `event code'.  This is composed of two
subfields specifying a major and minor code.  The major code is
contained in the lower five bits, and the minor in the rest, except
for code 6 (write protected), where it is in bits 12-15 only.  The
major and minor codes are broken up as follows:

	Major	Minor
	----	----
	  0		Success
		  0		normal
		  1		spin down ignored
		  2		still connected
		  4		duplicate unit number
		  8		already on line
		 16		still on line
	  1		Invalid command
	  2		Command aborted
	  3		Unit off line
		  0		unknown drive
		  1		not mounted
		  2		inoperative
		  4		duplicate
		  8		in diagnosis
	  4		Unit available
	  5		Media format error
		  0		FCT unreadable - EDC
		  1		invalid sector header
		  2		not 512 byte sectors
		  3		not formatted
		  4		FCT ECC
	  6		Write protected
		  1		via hardware
		  2		via software
	  7		Compare error
	  8		Data error
		  0		forced error
		  2		header compare
		  3		sync timeout
		  7		uncorrectable ECC
		  8		1 symbol ECC
		  9		2 symbol ECC
		 10		3 symbol ECC
		 11		4 symbol ECC
		 12		5 symbol ECC
		 13		6 symbol ECC
		 14		7 symbol ECC
		 15		8 symbol ECC
	  9		Host buffer access error
		  1		odd transfer address
		  2		odd transfer count
		  3		non-existent memory
		  4		memory parity error
	 10		Controller error
		  1		serdes overrun
		  2		EDC
		  3		inconsistent internal data structures
	 11		Drive error
		  1		SDI command timeout
		  2		controller detected protocol error
		  3		positioner error
		  4		lost read/write ready
		  5		drive clock dropout
		  6		lost receiver ready
		  7		drive detected error
		  8		controller detected pulse or parity error

(I do not know what an `FCT' is---format control table?---but I
would guess that it describes the media to the UDA50.  I suspect
`serdes' is a serialiser/deserialiser box for converting bytes to
bits and back.  I have no idea what `EDC' means.)

Anything not listed in the table is classed `unknown'.  Some may be
reserved; I have no way of knowing what they really are.

Anyway, to move on, after the event code is a quadword giving the
controller ID number, the two bytes giving the software and hardware
version numbers of the controller.  Then comes a `multi-unit code',
whatever that may be, then another quadword giving the unit ID
number, then the software and hardware version numbers of the unit.
After this is the `group', which for a disk format error is a retry
number and a level or count.  The retry number is the first (lower)
byte, the count in the second.  This is followed by the volume
serial number off the drive, then something called the `header'.

The header identifies the bad block and gives a code indicating
whether this is a normal block or a replacement.  The code is in
the upper four bits (28-31) and should be either 0 (logical block)
or 6 (replacement block).  The remaining codes are undefined.  The
lower 28 bits are the block number.

This is followed by 12 bytes of `SDI status information'.  I do not
know what this is for; and the Unix drivers ignore it.

Given the above information, you can decode error packets.  I am
not sure if the 4.2 UDA driver prints everything you need.  The
4.3 driver prints the unit number, the group (count*256+retry),
the header (0+LBN or 6<<28 + RBN), and the event code.

At the end of this posting are two routines.  The first, which is
a regular utility program, takes a `hdr' and `event' as printed by
4.3 (that is, in hex and octal respectively) and prints out the
LBN or RBN and the code & subcode.  The latter is a replacement
uderror() plus a new routine.  I believe it will fit right in on
a plain 4.2 or 4.3 machine, though I have none to test it on.  It
prints the error messages in a (hopefully) more readable format.

-------

Ok.  You have a bad block.  Now what?  Well, if your driver is
fancy enough, it will forward it for you.  This process is horribly
complex, and I will not attempt to describe it here.  What I *will*
do is provide some information on the format of RCTs.

An RCT consists of some number of 512 byte sectors (the number
varies with each drive; RA81s have 765 sectors per RCT) with several
functions.  The first two sectors are `scratch' blocks used during
the forwarding operation, with 0 holding status and 1 the data from
the block being replaced.  Blocks 2 through (RCT size)-2 are RCT
itself; the last block ((RCT size)-1) contains only null RCT entries.

RCT blocks are addressed by giving a logical block number that is
greater than any of the normal logical blocks.  For example, an
RA81 has 891072 blocks, numbered 0 through 891071; and its first
RCT's scratch block is addressed as LBN 891072.  Since each RCT is
765 blocks, the next RCT begins at block 891837.

Each RCT sector contains 128 replacement entries.  These are
longwords whose upper four bits contain a code, and lower 28 bits
contain the logical block number of the bad block being replaced.
The replacement block number is simply the index of the replacement
entry.  For example, the fifth entry in the second RCT sector (which
is really RCT sector 4) corresponds to replacement block 132.  (Got
that?)

The code numbers for replacement entries are as follows:

	Code	Name		Description
	----	----		-----------
	  0	unused		This RBN is free.
	  2	primary		Allocated, and is the first choice
				for the block that was replaced.
	  3	secondary	Allocated, but is not the first
				choice.
	  4	bad		This RBN is unusable.
	  5	altbad		Alternate RBN unusable.
	 11	null		This entry is beyond the last RBN.

Other codes are currently undefined.

Primary replacements are those that are on the same track as the
block being replaced.  Why there is any distinction between primary
and non-primary replacements I do not know; perhaps VMS uses the
information when allocating contiguous files.  Code 5 is not listed
in the Emulex manual, nor is it used by the bad-block-forwarding
driver.  It also seems fairly useless.

(Note that a good formatter should test all the replacement blocks
and mark any that are bad.  I do not know how this can be done, as
the only way to write to RBNs that I know of is to use the MSCP
`replace' command, which marks the replaced block as forwarded.)

Well, I hope this is useful...

Chris

--------

: Run this shell script with "sh" not "csh"
PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH
export PATH
all=FALSE
if [ x$1 = x-a ]; then
	all=TRUE
fi
echo 'Extracting udadecode.c'
sed 's/^X//' <<'//go.sysin dd *' >udadecode.c
X/*
 * Copyright (C) 1986 University of Maryland Computer Science Department
 *
 * This code may be freely distributed so long as the copyright notice
 * and this note remain intact.  Distributed 25 January 1986 by Chris
 * Torek.
 */

#include <stdio.h>
#include <sys/types.h>

X/*ARGSUSED*/
main(argc, argv)
	int argc;
	char **argv;
{
	u_long hdr;
	u_short event;
	char buf[BUFSIZ];

	(void) printf("Type control-D to exit\n");
	for (;;) {
		(void) printf("Enter `hdr' (hex) and `event' (octal): ");
		(void) fflush(stdout);
		if (fgets(buf, sizeof buf, stdin) == NULL) {
			(void) printf("\n");
			exit(0);
		}
		if (sscanf(buf, "%lx %ho", &hdr, &event) < 2)
			(void) printf("input conversion error; try again\n");
		else
			decode(hdr, event);
	}
}

X/*
 * Messages for the various subcodes.
 */
char unknown_msg[] = "unknown subcode";

char *succ_msgs[] = {
	"normal", "spin down ignored", "still connected",
	unknown_msg, "dup. unit #", unknown_msg, unknown_msg,
	unknown_msg, "already online", unknown_msg,
	unknown_msg, unknown_msg, unknown_msg, unknown_msg,
	unknown_msg, unknown_msg, "still online"
};
char *offl_msgs[] = {
	"unknown drive", "not mounted", "inoperative",
	unknown_msg, "duplicate", unknown_msg, unknown_msg,
	unknown_msg, "in diagnosis"
};
char *wrprot_msgs[] = {
	unknown_msg, "hardware", "software"
};
char *data_msgs[] = {
	"forced error", unknown_msg, "header compare", 
	"sync timeout", unknown_msg, unknown_msg,     
	unknown_msg, "uncorrectable ecc",
	"1 symbol ecc", "2 symbol ecc", "3 symbol ecc", "4 symbol ecc", 
	"5 symbol ecc", "6 symbol ecc", "7 symbol ecc", "8 symbol ecc", 
};
char *media_fmt_msgs[] = {
	"fct unread - edc", "invalid sector header", "not 512 sectors",
	"not formatted", "fct ecc"
} ;
char *host_buffer_msgs[] = {
	unknown_msg, "odd xfer addr", "odd xfer count", 
	"non-exist. memory", "memory parity"
};
char *cntlr_msgs[] = {
	unknown_msg, "serdes overrun", "edc", 
	"inconsistant internal data struct"
};
char *drive_msgs[] = {
	unknown_msg, "sdi command timeout", "ctlr detected protocol", 
	"positioner", "lost rd/wr ready", "drive clock dropout", 
	"lost recvr ready", "drive detected error", 
	"ctlr detected pulse or parity"
};

X/*
 * The following table correlates message codes with the
 * decoding strings.
 */
struct code_decode {
	char	*cdc_msg;
	int	cdc_nsubcodes;
	char	**cdc_submsgs;
} code_decode[] = {
#define	SC(m)	sizeof (m) / sizeof (m[0]), m
	"- success",			SC(succ_msgs),
	"invalid command",		0, 0,
	"command aborted",		0, 0,
	"- unit offline",		SC(offl_msgs),
	"unit available",		0, 0,
	"media format error",		SC(media_fmt_msgs),
	"write protected",		SC(wrprot_msgs),
	"compare error",		0, 0,
	"data error",			SC(data_msgs),
	"host buffer access error",	SC(host_buffer_msgs),
	"controller error",		SC(cntlr_msgs),
	"drive error",			SC(drive_msgs),
#undef SC
};

#define CODE(c)		((c) & 0x1f)
#define SUBCODE(c)	((CODE(c) != 6 ? (c) >> 5 : (c) >> 12) & 0x7ff)

decode(hdr, event)
	u_long hdr;
	u_short event;
{
	register struct code_decode *cdc;
	int c, sc;
	char *cm, *scm;
	/*
	 * For bad blocks, mp->mslg_hdr identifies a code and the logical
	 * block number.  Code 0 is a regular block; code 6 is a replacement
	 * block.  The remaining codes are currently undefined.  The code
	 * is in the upper four bits of mslg_hdr (bits 0-27 are the lbn).
	 */
	static char *codemsg[16] = {
		"lbn", "code 1", "code 2", "code 3",
		"code 4", "code 5", "rbn", "code 7",
		"code 8", "code 9", "code 10", "code 11",
		"code 12", "code 13", "code 14", "code 15"
	};

	(void) printf("%s %d: ", codemsg[hdr >> 28], hdr & 0xffffff);

	c = CODE(event);
	sc = SUBCODE(event);
	if (c >= sizeof code_decode / sizeof code_decode[0])
		cm = "- unknown code", scm = "??";
	else {
		cdc = &code_decode[c];
		cm = cdc->cdc_msg;
		if (sc >= cdc->cdc_nsubcodes)
			scm = unknown_msg;
		else
			scm = cdc->cdc_submsgs[sc];
	}
	(void) printf("%s %s (code %d, subcode %d)\n", scm, cm, c, sc);
}
//go.sysin dd *
if [ `wc -c < udadecode.c` != 3948 ]; then
	made=FALSE
	echo 'error transmitting "udadecode.c" --'
	echo 'length should be 3948, not' `wc -c < udadecode.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 udadecode.c
	echo -n '	'; ls -ld udadecode.c
fi
echo 'Extracting uderror'
sed 's/^X//' <<'//go.sysin dd *' >uderror
X/*
 * Process a UDA50 error log message
 *
 * For now, just log the error on the console.
 * Only minimal decoding is done, only "useful"
 * information is printed.  Eventually should
 * send message to an error logger.
 */

#define CODE(c)		((c) & 0x1f)
#define SUBCODE(c)	((CODE(c) != 6 ? (c) >> 5 : (c) >> 12) & 0x7ff)

uderror(um, mp)
	register struct uba_ctlr *um;
	register struct mslg *mp;
{
	int issoft = mp->mslg_flags & (M_LF_SUCC|M_LF_CONT);
	/*
	 * For bad blocks, mp->mslg_hdr identifies a code and the logical
	 * block number.  Code 0 is a regular block; code 6 is a replacement
	 * block.  The remaining codes are currently undefined.  The code
	 * is in the upper four bits of mslg_hdr (bits 0-27 are the lbn).
	 */
	static char *codemsg[16] = {
		"lbn", "code 1", "code 2", "code 3",
		"code 4", "code 5", "rbn", "code 7",
		"code 8", "code 9", "code 10", "code 11",
		"code 12", "code 13", "code 14", "code 15"
	};
#define BADCODE(h)	(codemsg[(unsigned)(h) >> 28])
#define BADLBN(h)	((h) & 0xfffffff)

	if (!softerrormsgs && issoft)
		return;
	printf("uda%d: %s error datagram, ", um->um_ctlr,
	    issoft ? "soft" : "hard");
	switch (mp->mslg_format & 0377) {

	case M_FM_CNTERR:
		break;

	case M_FM_BUSADDR:
		printf("memory addr 0x%x", *(long *)&mp->mslg_busaddr);
		break;

	case M_FM_DISKTRN:
		printf("ra%d: retry %d count %d, %s %d",
		    mp->mslg_unit, mp->mslg_group & 0xff,
		    (mp->mslg_group >> 8) & 0xff,
		    BADCODE(mp->mslg_hdr), BADLBN(mp->mslg_hdr));
		break;

	case M_FM_SDI:
		printf("ra%d: %s %d", mp->mslg_unit,
		    BADCODE(mp->mslg_hdr), BADLBN(mp->mslg_hdr));
		break;

	case M_FM_SMLDSK:
		printf("ra%d: small disk error, cyl %d",
		    mp->mslg_unit, mp->mslg_sdecyl);
		break;

	default:
		printf("ra%d: unknown error, format 0%o",
		    mp->mslg_unit, mp->mslg_format);
	}
	printf("%s\n", mp->mslg_flags & M_LF_CONT ? "; continuing" : "");
	udputstatus((struct mscp *)mp);
	if (udaerror) {
		register long *p = (long *)mp;
		register int i;

		for (i = 0; i < mp->mslg_header.uda_msglen+4; i += sizeof(*p))
			printf("0x%x ", *p++);
		printf("\n");
	}
#undef BADCODE
#undef BADLBN
}

X/*
 * Messages for the various subcodes.
 */
static char unknown_msg[] = "unknown subcode";

static char *succ_msgs[] = {
	"normal", "spin down ignored", "still connected",
	unknown_msg, "dup. unit #", unknown_msg, unknown_msg,
	unknown_msg, "already online", unknown_msg,
	unknown_msg, unknown_msg, unknown_msg, unknown_msg,
	unknown_msg, unknown_msg, "still online"
};
static char *offl_msgs[] = {
	"unknown drive", "not mounted", "inoperative",
	unknown_msg, "duplicate", unknown_msg, unknown_msg,
	unknown_msg, "in diagnosis"
};
static char *wrprot_msgs[] = {
	unknown_msg, "hardware", "software"
};
static char *data_msgs[] = {
	"forced error", unknown_msg, "header compare", 
	"sync timeout", unknown_msg, unknown_msg,     
	unknown_msg, "uncorrectable ecc",
	"1 symbol ecc", "2 symbol ecc", "3 symbol ecc", "4 symbol ecc", 
	"5 symbol ecc", "6 symbol ecc", "7 symbol ecc", "8 symbol ecc", 
};
static char *media_fmt_msgs[] = {
	"fct unread - edc", "invalid sector header", "not 512 sectors",
	"not formatted", "fct ecc"
} ;
static char *host_buffer_msgs[] = {
	unknown_msg, "odd xfer addr", "odd xfer count", 
	"non-exist. memory", "memory parity"
};
static char *cntlr_msgs[] = {
	unknown_msg, "serdes overrun", "edc", 
	"inconsistant internal data struct"
};
static char *drive_msgs[] = {
	unknown_msg, "sdi command timeout", "ctlr detected protocol", 
	"positioner", "lost rd/wr ready", "drive clock dropout", 
	"lost recvr ready", "drive detected error", 
	"ctlr detected pulse or parity"
};

X/*
 * The following table correlates message codes with the
 * decoding strings.
 */
struct code_decode {
	char	*cdc_msg;
	int	cdc_nsubcodes;
	char	**cdc_submsgs;
} code_decode[] = {
#define	SC(m)	sizeof (m) / sizeof (m[0]), m
	"- success",			SC(succ_msgs),
	"invalid command",		0, 0,
	"command aborted",		0, 0,
	"- unit offline",		SC(offl_msgs),
	"unit available",		0, 0,
	"media format error",		SC(media_fmt_msgs),
	"write protected",		SC(wrprot_msgs),
	"compare error",		0, 0,
	"data error",			SC(data_msgs),
	"host buffer access error",	SC(host_buffer_msgs),
	"controller error",		SC(cntlr_msgs),
	"drive error",			SC(drive_msgs),
#undef SC
};

udputstatus(mp)
	struct mscp *mp;
{
	register int event = ((struct mslg *)mp)->mslg_event;
	register struct code_decode *cdc;
	int c, sc;
	char *cm, *scm;

	c = CODE(event);
	sc = SUBCODE(event);
	if (c >= sizeof code_decode / sizeof code_decode[0])
		cm = "- unknown code", scm = "??";
	else {
		cdc = &code_decode[c];
		cm = cdc->cdc_msg;
		if (sc >= cdc->cdc_nsubcodes)
			scm = unknown_msg;
		else
			scm = cdc->cdc_submsgs[sc];
	}
	printf("%s %s (code %d, subcode %d)\n", scm, cm, c, sc);
}
//go.sysin dd *
if [ `wc -c < uderror` != 4787 ]; then
	made=FALSE
	echo 'error transmitting "uderror" --'
	echo 'length should be 4787, not' `wc -c < uderror`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 uderror
	echo -n '	'; ls -ld uderror
fi
exit 0
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 4251)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris at umcp-cs		ARPA:	chris at mimsy.umd.edu



More information about the Comp.unix.wizards mailing list