/*	ZTserver (network object) for ZTNS & ZTDRIVER ...
	w.j.m. jun 1989
	mod 3-jul-1989 wjm: unless UNSAFE_AFTER_ERROR is defined as 1,
			don't take unknown position that serious,
			just set MT$M_LOST bit.
	fix 19-sep-1989 wjm: properly set/clear MT$M_LOST in ZT's iosb
	mod 28-dec-1990 wjm: VMS V5.4 *always* does end-of-volume recognition,
				no longer dependent upon FOREIGN MOUNT.
			     Old behaviour now only with PRE_V54 defined.
	fix 14-jan-1991 wjm: fix(?) file count returned when reverse
				SKIPFILE hits BOT (required for 5.4 MTAAACP)
*/

#ifndef TRACE
#define TRACE 1
#endif

#ifndef UNSAFE_AFTER_ERROR
#define UNSAFE_AFTER_ERROR 0
#endif

#ifndef PRE_V54		/* might be conditional on UCBDEF or IODEF;	   */
#define PRE_V54 0	/* e.g. UCB$L_SHAD & IO$V_MOVEFILE are new in V5.4 */
#endif

#include "ztns.h"	/* includes IOSB */

/* NOTE: following ZT definitions must match ZTDEF.MAR & ZTSERVER.MAR *********/

typedef struct ZTmsg {
	IOSB iosb;	/* i.e. status, count, devdep */
	long record;
	unsigned short ucbsts;
	unsigned short fill1;
	unsigned long devchar;
	unsigned short func;
	unsigned short bcnt;
	union {
		short w[4];
		unsigned short uw[4];
		unsigned long ul[2];
	} media;
} ZTmsg;
globalref struct {
	unsigned short l;
	unsigned short fill1;
	ZTmsg *p;
} ZT_MSGDSC;

globalref struct {
	unsigned long bct;
	unsigned char *addr;
} ZT_BUFDSC;

extern unsigned ZT_INIT(),ZT_WAIT(),ZT_TOUSER(),ZT_FRUSER(),ZT_REQCOM();

/******* end of ZT definitions ************************************************/


#include ssdef
#include lnmdef

#include "mtdef.h"
#include "iodef.h"	/* my own! */
#include "ucbdef.h"	/* note VMS version dependency */
#if PRE_V54
#include devdef
#endif

#include stdio
#include stddef
#include string
#include descrip
typedef struct dsc$descriptor DESCR;

typedef struct VMS_ITEM {
	unsigned short size;
	unsigned short code;
	void *bufp;
	int *lenp;
} VMS_ITEM;

#define CHECK(x) do {unsigned s=x; if(!(s&1)) lib$stop(s);} while(0)
#define FEHLER(m) do {$DESCRIPTOR(d,m); Fehler(&d);} while(0)
#define MIN(a,b) ((a) < (b)) ? (a) : (b)

extern void lib$stop();
extern unsigned sys$assign(),sys$trnlnm();
extern unsigned sys$qiow(int,int,int,
			IOSB*,void*,int,
			void*,int,int,int,int,int);


static void ns_init(void);	/* forward */
static void ns_req(int,int);	/* forward */
static IOSB ns_iosb;

static unsigned char *bufp;
static unsigned long *bufbctp;

static ZTmsg *msgp;

static unsigned short fcode;
static unsigned short fmodif;

static enum {no,yes,dunno} aftertm;	/* tape positioned after mark? -
					shortcut to save tape movement */

static int/*logical*/ pos_unsafe;	/* set after (hard) error,
					/* #if UNSAFE_AFTER_ERROR:
					/*     inhibits all operations
					/*     that move the tape forward,
					/*     since UCB$L_RECORD may be wrong*/

/*****/

static void do_init()
{
	ns_init();	/* must set up  ns_iosb.devdep */

	aftertm = (ns_iosb.devdep & MT$M_BOT) ? no: dunno;
	pos_unsafe = !(ns_iosb.devdep & MT$M_BOT);
}


static void do_nop()
{
	/* all modifiers ignored */

	/* give 'ns' a chance to update 'devdep' */
	ns_req(IO$_SKIPRECORD,0);	/* skip 0 blocks */
	if((ns_iosb.status & 1) ||
	   (ns_iosb.status == SS$_ENDOFTAPE) ||
	   (ns_iosb.status == SS$_BEGOFTAPE)) {
			/* o.k. */
	} else {
		pos_unsafe = 1;
	}
	/* 'aftertm' did not change, I hope ... */
}


static void do_rewind(int unload)
{
	/* fmodif: IO$M_NOWAIT, others ignored */
	unsigned short nsfct;


	if(unload) {
		nsfct = IO$_REWINDOFF;
	} else {
		nsfct = IO$_REWIND;
	}
	nsfct |= (fmodif & IO$M_NOWAIT);
	ns_req(nsfct,0);
	
	msgp->record = 0;	/* always */
	aftertm = no;

	if((ns_iosb.status & 1) ||
	   (ns_iosb.status == SS$_BEGOFTAPE)) {
		/* o.k. */
	} else {
		pos_unsafe = 1;
	}

#if 0	/* this works together with a VMS V.5 addition:
	   if tape is mounted foreign & MT$M_ENAUTOPACK is set,
	   then set UCB$M_VALID on "medium online" interrupt */
	/*...................................................*/
	if(!hw_medonline) {
		msgp->ucbsts &= ~UCB$M_VALID;
	}
#endif	/*...................................................*/
}


static unsigned set_aftertm()	/* ggf. find out if tape is positioned
					after a tape mark */
{
	if(aftertm != dunno) return SS$_NORMAL;	/* nothing to be done */

	ns_req(IO$_SKIPRECORD,-1);
	msgp->record -= ns_iosb.count;
	if(ns_iosb.count != 1) {
		if((ns_iosb.devdep & MT$M_BOT) == 0) {
			pos_unsafe = 1;
			return SS$_TAPEPOSLOST;
		} else {
			pos_unsafe = 0;	/* we're at BOT! */
			aftertm = no;
			return SS$_NORMAL;
		}
	}
	aftertm = (ns_iosb.status == SS$_ENDOFFILE) ? yes : no;

	ns_req(IO$_SKIPRECORD,1);
	msgp->record += ns_iosb.count;
	if(ns_iosb.count != 1) {
		pos_unsafe = 1;
		return SS$_TAPEPOSLOST;
	}
	return SS$_NORMAL;
}

static void	/* "end of volume" recognized */
set_eov(sca)	/* backspace over 2nd eof mark, decrement spacing count etc. */
int sca;		/* spacing count so far (>0) */
{
	ns_req(IO$_SKIPRECORD,-1);
	if(ns_iosb.count == 1 && ns_iosb.status == SS$_ENDOFFILE) { /* o.k. */
		msgp->record --;
		ns_iosb.status = SS$_ENDOFVOLUME;
		/* aftertm remains == yes */
	} else {
		pos_unsafe = 1;
		ns_iosb.status = SS$_TAPEPOSLOST;
		aftertm = dunno;
	}
	ns_iosb.count = sca - 1;	/* final count */
}


static void do_skiprec()
{
	/* all modifiers ignored */
	int sc;


	sc = msgp->media.w[0];

	if(sc == 0) {
		do_nop();
		return;

	} else if(sc > 0) {					/* FORWARD */
#if PRE_V54
		int/*logical*/ no_acp;
#endif

#if UNSAFE_AFTER_ERROR
		if(pos_unsafe) return;
#endif

#if PRE_V54
		no_acp =	/* not mounted, or mounted foreign */
			((msgp->devchar & DEV$M_MNT) == 0) ||
			((msgp->devchar & DEV$M_FOR) != 0);
		if(no_acp) {
#endif
			if((set_aftertm() & 1) == 0) {
				ns_iosb.status = SS$_TAPEPOSLOST;
				ns_iosb.count = 0;
				pos_unsafe = 1;
				return;
			}
#if PRE_V54
		}
#endif

		ns_req(IO$_SKIPRECORD,sc);
		msgp->record += ns_iosb.count;

		if((ns_iosb.status & 1) ||
		   ns_iosb.status == SS$_ENDOFTAPE) {
			aftertm = no;
		} else if(ns_iosb.status == SS$_ENDOFFILE) {
			aftertm = yes;
			if(
#if PRE_V54
			   no_acp && 
#endif
				     (ns_iosb.count == 1)) {
				/* "end of volume recognition" */
				set_eov(1);
			}
		} else {
			aftertm = dunno;
			pos_unsafe = 1;
		}

	} else {						/* BACKWARD */
		ns_req(IO$_SKIPRECORD,sc);
		msgp->record -= ns_iosb.count;

		aftertm = dunno;
		if(ns_iosb.status == SS$_ENDOFFILE) {
					/* o.k. */
		} else if((ns_iosb.status & 1) ||
			   ns_iosb.status == SS$_BEGOFTAPE) {
			if(sc + ns_iosb.count == 0) {
					/* o.k. */
			   ns_iosb.status = SS$_NORMAL;
			} else {
				if(ns_iosb.devdep & MT$M_BOT) {
					/* terminated due to B.O.T. */
					ns_iosb.status = SS$_BEGOFTAPE;
				} else {
					/* must not happen! */
					/* ??? */
				}
			}
		} else {		/* error */
			pos_unsafe = 1;
		}
	}
}


static void do_skipfile()
{
	/* all modifiers ignored */
	int sc,sca;
	

	sc = msgp->media.w[0];
	if(sc == 0) {
		do_nop();
		return;
	} else if(sc > 0) {					/* FORWARD */
#if PRE_V54
		int/*logical*/ no_acp;
#endif
		int prevtm;		/* 'record' after tape mark, or -2 */

#if UNSAFE_AFTER_ERROR
		if(pos_unsafe) return;
#endif

#if PRE_V54
		no_acp =	/* not mounted, or mounted foreign */
			((msgp->devchar & DEV$M_MNT) == 0) ||
			((msgp->devchar & DEV$M_FOR) != 0);
		if(no_acp) {
#endif
			if((set_aftertm() & 1) == 0) {
				ns_iosb.status = SS$_TAPEPOSLOST;
				pos_unsafe = 1;
				return;
			}
			if(aftertm == yes) {
				prevtm = msgp->record;
			} else {
				prevtm = -2;
			}
#if PRE_V54
		}
#endif

		sca = 0;
		do {
			do {
				ns_req(IO$_SKIPRECORD,0x7FFF);
				msgp->record += ns_iosb.count;
			} while((ns_iosb.status & 1) ||
				(ns_iosb.status == SS$_ENDOFTAPE));

			if((ns_iosb.status == SS$_ENDOFFILE)) {
					/* o.k. */
				sca ++;
				sc --;

#if PRE_V54
				if(no_acp) {
#endif
					if(msgp->record == prevtm+1) {
						aftertm = yes;
						set_eov(sca);
						return;
					} else {
						prevtm = msgp->record;
					}
#if PRE_V54
				}
#endif
			}
		} while((sc > 0) &&
			(ns_iosb.status == SS$_ENDOFFILE));

		if(ns_iosb.status == SS$_ENDOFFILE) {
				/* o.k. */
			ns_iosb.status = SS$_NORMAL;
		} else {	/* error */
			aftertm = dunno;
			pos_unsafe = 1;
		}
		ns_iosb.count = sca;

	} else {						/* BACKWARD */
			/* NOTE: It is not clear what status is returned
			/* on the attempt to skip backward over B.O.T.
			/* We allow for both success and SS$_BEGOFTAPE, and
			/* we do return SS$_BEGOFTAPE on skip beyond B.O.T. */
		int scb;	/* count of blocks skipped w/i file */

		aftertm = dunno;
		sca = 0;
		do {
			scb = 0;
			do {
				ns_req(IO$_SKIPRECORD,-0x7FFF);
				msgp->record -= ns_iosb.count;
				scb += ns_iosb.count;
			} while((ns_iosb.status & 1) &&
				(ns_iosb.count > 0));

			if((scb > 0) &&		/* anything skipped ? */
			   ((ns_iosb.status & 1) ||
			    (ns_iosb.status == SS$_ENDOFFILE) ||
			    (ns_iosb.status == SS$_BEGOFTAPE))) {
#if 0	/* I had this in the pre-5.4 version, 		*/
	/* but TMDRIVER has always counted differently	*/
				sca ++;
#else
				if(!(ns_iosb.devdep & MT$M_BOT)) sca ++;
				/** don't count BOT (from TMDRIVER) **/
#endif
				sc ++;
			}
		} while((sc < 0) &&
			(scb > 0) &&	/* stop if nothing was skipped */
			((ns_iosb.status & 1) ||
			 (ns_iosb.status == SS$_ENDOFFILE)));

		if(ns_iosb.status == SS$_ENDOFFILE) {
					/* all files skipped */
			ns_iosb.status = SS$_NORMAL;
		} else if((ns_iosb.status & 1) ||
			  (ns_iosb.status == SS$_BEGOFTAPE)) {
			if(sc == 0) {	/* all files skipped */
				ns_iosb.status = SS$_NORMAL;
			} else if(ns_iosb.devdep & MT$M_BOT) {
					/* stopped due to BOT */
				ns_iosb.status = SS$_BEGOFTAPE;
			} else {
					/* must not happen! */
					/* ??? */
			}
		} else {		/* error */
			ns_iosb.status = SS$_TAPEPOSLOST;
			pos_unsafe = 1;
		}
		ns_iosb.count = sca;
	}
}


static void do_writecheck()
{
	/* modifiers: IO$M_REVERSE rejected, others ignored */


	if((fmodif & IO$M_REVERSE) != 0) {	/* not supported so far */
		ns_iosb.status = SS$_BADPARAM;
		return;
	}

#if UNSAFE_AFTER_ERROR
	if(pos_unsafe) return;
#endif

	CHECK(ZT_FRUSER());

	ns_req(IO$_WRITECHECK,msgp->bcnt);
	if((ns_iosb.status & 1) ||
	   (ns_iosb.status == SS$_DATAOVERUN) ||
	   (ns_iosb.status == SS$_ENDOFFILE) ||
	   (ns_iosb.status == SS$_DATACHECK)) {
				/* tape moved */
		msgp->record ++;
		aftertm = (ns_iosb.status == SS$_ENDOFFILE) ? yes : no;
	} else {
				/* tape may have moved */
		pos_unsafe = 1;
		/* msgp->record ++; /* better don't count it */
		aftertm = dunno;
	}
}


static void do_read()
{
	/* modifiers: IO$M_REVERSE rejected,
			IO$M_DATACHECK o.k.,
			others ignores */

	if((fmodif & IO$M_REVERSE) != 0) {	/* not supported so far */
		ns_iosb.status = SS$_BADPARAM;
		return;
	}

#if UNSAFE_AFTER_ERROR
	if(pos_unsafe) return;
#endif

	ns_req(IO$_READLBLK | (fmodif & IO$M_DATACHECK),msgp->bcnt);
	if((ns_iosb.status & 1) ||
	   (ns_iosb.status == SS$_DATAOVERUN) ||
	   (ns_iosb.status == SS$_ENDOFFILE)) {
				/* tape moved */
		msgp->record ++;
		aftertm = (ns_iosb.status == SS$_ENDOFFILE) ? yes : no;
	} else {
				/* tape may have moved */
		pos_unsafe = 1;
		/* msgp->record ++; /* better don't count it */
		aftertm = dunno;
	}

	*bufbctp = MIN(ns_iosb.count,msgp->bcnt);
	CHECK(ZT_TOUSER());
}
		
static void do_writemark()
{
	/* all modifiers ignored */

#if UNSAFE_AFTER_ERROR
	if(pos_unsafe) return;
#endif

	ns_req(IO$_WRITEOF,0);
	if((ns_iosb.status & 1) ||
	    ns_iosb.status == SS$_ENDOFTAPE) {
		msgp->record ++;
		aftertm = yes;
	} else {
		/* assume NO tape movement */
		pos_unsafe = 1;
		aftertm = dunno;
	}
}


static void do_write()
{
	/* modifiers: IO$M_DATACHECK o.k., others ignored
		(in particular mysterious IO$M_ERASE flag,
			and IO$M_NOWAIT ("TU81plus only")) */


#if UNSAFE_AFTER_ERROR
	if(pos_unsafe) return;
#endif

	CHECK(ZT_FRUSER());

	ns_req(IO$_WRITELBLK | (fmodif & IO$M_DATACHECK),msgp->bcnt);
	if((ns_iosb.status & 1) ||
	   (ns_iosb.status == SS$_ENDOFTAPE)) {
			/* tape moved */
		msgp->record ++;
		aftertm = no;
	} else {
			/* assume NO tape movement */
		pos_unsafe = 1;
		aftertm = dunno;
	}
}


/*****/

#if TRACE
static void trace_func();	/* forward */
static void trace_result();	/* forward */
#endif

static void dispatch()
{
	fcode = msgp->func & IO$M_FCODE;
	fmodif = msgp->func & IO$M_FMODIFIERS;

#if TRACE
	trace_func();
#endif

	/* set defaults */
	ns_iosb.count = 0;
	ns_iosb.status = SS$_UNSAFE;


	switch(fcode) {

	  case IO$_WRITEMARK:
	  case IO$_WRITEOF:
		do_writemark();
		break;

	  case IO$_UNLOAD:
	  case IO$_REWINDOFF:
		do_rewind(1);
		break;

	  case IO$_RECAL:
	  case IO$_REWIND:
	  case IO$_AVAILABLE:		/* NOTE: driver cleared VALID */
		do_rewind(0);
		break;
		
	  case IO$_PACKACK:
		do_nop();
		if((ns_iosb.status & 1)) {
			msgp->ucbsts |= UCB$M_VALID;
		}
		break;
		
	  case IO$_SPACEFILE:
	  case IO$_SKIPFILE:
		do_skipfile();
		break;

	  case IO$_SPACERECORD:
	  case IO$_SKIPRECORD:
		do_skiprec();
		break;

	  case IO$_WRITECHECK:
		do_writecheck();
		break;

	  case IO$_WRITEPBLK:
	  case IO$_WRITELBLK:
		do_write();
		break;

	  case IO$_READPBLK:
	  case IO$_READLBLK:
		do_read();
		break;

	  case IO$_NOP:
	  case IO$_SENSECHAR:
	  case IO$_SENSEMODE:
	  case IO$_SETCHAR:		/* treat as no-op */
	  case IO$_SETMODE:		/* treat as no-op */
	  case IO$_DRVCLR:		/* treat as no-op */
	  case IO$_ERASETAPE:		/* treat as no-op */
noop:
		do_nop();
		break;

	  default:
		ns_iosb.status = SS$_ILLIOFUNC;
		break;
	}

/* final processing:
	must copy status & count from ns_iosb to msgp->iosb,
			also 5 devdepend bits
	record		- has been updated (if changed)
	ucbsts		- v_valid has been modified (if changed)
*/

	/* fake 'LOST' BIT */
	if(pos_unsafe || ns_iosb.status == SS$_TAPEPOSLOST) {
		ns_iosb.devdep |= MT$M_LOST;
	}

	if(ns_iosb.devdep & MT$M_BOT) {			/* at "BOT" */
		ns_iosb.devdep &= ~MT$M_LOST;		/* clear pos. lost */
		aftertm = no;
		pos_unsafe = 0;
	}

	msgp->iosb.status = ns_iosb.status;
	msgp->iosb.count = ns_iosb.count;
	{
		unsigned mask = MT$M_BOT | MT$M_EOF | MT$M_EOT | MT$M_HWL |
				MT$M_LOST;

		msgp->iosb.devdep = (msgp->iosb.devdep & (~mask)) |
				    (ns_iosb.devdep & mask);
	}

#if TRACE
	trace_result();
#endif
}


/*****/

main(argc,argv)
int argc; char **argv;
{

#ifdef VAXC
	redirect(&argc,&argv);
#endif

	bufp = ZT_BUFDSC.addr;
	bufbctp = &(ZT_BUFDSC.bct);

	if(ZT_MSGDSC.l != sizeof(ZTmsg)) FEHLER("bad MSGDSC.len");
	msgp = ZT_MSGDSC.p;


	do_init();
	CHECK(ZT_INIT());


	do {
		CHECK(ZT_WAIT());

		dispatch();

		CHECK(ZT_REQCOM());
	} while(1);
}


/*****/

#if TRACE

typedef enum {zeroparm,oneparm,transfer,setchar} TRACE_FTYPE;

static void trace_f(char *fname,TRACE_FTYPE ftype)
{
	int i,fm;

	fprintf(stdout,"***** %s",fname);

	for(fm = fmodif>>IO$V_FMODIFIERS, i = IO$V_FMODIFIERS;
	    (fm != 0) && i < 16;
	    fm >>= 1, i++) {
		if(fm & 1) switch(i) {
		  case IO$V_REVERSE:
			fprintf(stdout,",REVERSE"); break;
		  case IO$V_NOWAIT:
			fprintf(stdout,",NOWAIT"); break;
		  case IO$V_ERASE:
			fprintf(stdout,",ERASE"); break;
		  case IO$V_INHERLOG:
			fprintf(stdout,",INHERLOG"); break;
		  case IO$V_INHEXTGAP:
			fprintf(stdout,",INHEXTGAP"); break;
		  case IO$V_DATACHECK:
			fprintf(stdout,",DATACHECK"); break;
		  case IO$V_INHRETRY:
			fprintf(stdout,",INHRETRY"); break;
		  default:
			fprintf(stdout,",1@%d",i);
			break;
		}
	}

	switch(ftype) {
	  case zeroparm:
		break;
	  case oneparm:
		fprintf(stdout," media=%d",msgp->media.w[0]);
		break;
	  case transfer:
		fprintf(stdout," bcnt=%d",msgp->bcnt);
		break;
	  case setchar:
		fprintf(stdout," media: %04X %04X %04X",
				msgp->media.uw[0],
				msgp->media.uw[1],
				msgp->media.uw[2]);
		break;
	}
	fprintf(stdout," record=%d\n",msgp->record);	  
}

static void trace_func()
{
	switch(fcode) {
	  case IO$_NOP:
		trace_f("IO$_NOP",zeroparm); break;
	  case IO$_UNLOAD:
		trace_f("IO$_UNLOAD",zeroparm); break;
	  case IO$_RECAL:
		trace_f("IO$_RECAL",zeroparm); break;
	  case IO$_DRVCLR:
		trace_f("IO$_DRVCLR",zeroparm); break;
	  case IO$_ERASETAPE:
		trace_f("IO$_ERASETAPE",zeroparm); break;
	  case IO$_PACKACK:
		trace_f("IO$_PACKACK",zeroparm); break;
	  case IO$_AVAILABLE:
		trace_f("IO$_AVAILABLE",zeroparm); break;
	  case IO$_WRITEMARK:
		trace_f("IO$_WRITEMARK",zeroparm); break;
	  case IO$_REWINDOFF:
		trace_f("IO$_REWINDOFF",zeroparm); break;
	  case IO$_REWIND:
		trace_f("IO$_REWIND",zeroparm); break;
	  case IO$_WRITEOF:
		trace_f("IO$_WRITEOF",zeroparm); break;
	  case IO$_SPACEFILE:
		trace_f("IO$_SPACEFILE",oneparm); break;
	  case IO$_SPACERECORD:
		trace_f("IO$_SPACERECORD",oneparm); break;
	  case IO$_SKIPFILE:
		trace_f("IO$_SKIPFILE",oneparm); break;
	  case IO$_SKIPRECORD:
		trace_f("IO$_SKIPRECORD",oneparm); break;
	  case IO$_SENSECHAR:
		trace_f("IO$_SENSECHAR",zeroparm); break;
	  case IO$_SENSEMODE:
		trace_f("IO$_SENSEMODE",zeroparm); break;
	  case IO$_SETCHAR:
		trace_f("IO$_SETCHAR",setchar); break;
	  case IO$_SETMODE:
		trace_f("IO$_SETMODE",setchar); break;
	  case IO$_WRITECHECK:
		trace_f("IO$_WRITECHECK",transfer); break;
	  case IO$_WRITEPBLK:
		trace_f("IO$_WRITEPBLK",transfer); break;
	  case IO$_READPBLK:
		trace_f("IO$_READPBLK",transfer); break;
	  case IO$_WRITELBLK:
		trace_f("IO$_WRITELBLK",transfer); break;
	  case IO$_READLBLK:
		trace_f("IO$_READLBLK",transfer); break;
	  default:
		{	char name[16];

			sprintf(name,"?%d?",fcode);
			trace_f(name,zeroparm);
		}
		break;
	}
}

static void trace_result()
{
	fprintf(stdout,"\tiosts=");
	switch(msgp->iosb.status) {
	  case SS$_NORMAL:
		fprintf(stdout,"SS$_NORMAL"); break;
	  case SS$_BADPARAM:
		fprintf(stdout,"SS$_BADPARAM"); break;
	  case SS$_CTRLERR:
		fprintf(stdout,"SS$_CTRLERR"); break;
	  case SS$_DATACHECK:
		fprintf(stdout,"SS$_DATACHECK"); break;
	  case SS$_DEVOFFLINE:
		fprintf(stdout,"SS$_DEVOFFLINE"); break;
	  case SS$_DRVERR:
		fprintf(stdout,"SS$_DRVERR"); break;
	  case SS$_FORMAT:
		fprintf(stdout,"SS$_FORMAT"); break;
	  case SS$_ILLIOFUNC:
		fprintf(stdout,"SS$_ILLIOFUNC"); break;
	  case SS$_MEDOFL:
		fprintf(stdout,"SS$_MEDOFL"); break;
	  case SS$_NONEXDRV:
		fprintf(stdout,"SS$_NONEXDRV"); break;
	  case SS$_PARITY:
		fprintf(stdout,"SS$_PARITY"); break;
	  case SS$_TAPEPOSLOST:
		fprintf(stdout,"SS$_TAPEPOSLOST"); break;
	  case SS$_TIMEOUT:
		fprintf(stdout,"SS$_TIMEOUT"); break;
	  case SS$_UNSAFE:
		fprintf(stdout,"SS$_UNSAFE"); break;
	  case SS$_VOLINV:
		fprintf(stdout,"SS$_VOLINV"); break;
	  case SS$_WRITLCK:
		fprintf(stdout,"SS$_WRITLCK"); break;
	  case SS$_DATAOVERUN:
		fprintf(stdout,"SS$_DATAOVERUN"); break;
	  case SS$_ENDOFFILE:
		fprintf(stdout,"SS$_ENDOFFILE"); break;
	  case SS$_ENDOFTAPE:
		fprintf(stdout,"SS$_ENDOFTAPE"); break;
	  case SS$_ENDOFVOLUME:
		fprintf(stdout,"SS$_ENDOFVOLUME"); break;
	  case SS$_BEGOFTAPE:
		fprintf(stdout,"SS$_BEGOFTAPE"); break;
	  default:
		fprintf(stdout,"%04X",msgp->iosb.status);
		break;
	}

	if(msgp->iosb.devdep & MT$M_BOT) fprintf(stdout,",M_BOT");
	if(msgp->iosb.devdep & MT$M_EOF) fprintf(stdout,",M_EOF");
	if(msgp->iosb.devdep & MT$M_EOT) fprintf(stdout,",M_EOT");
	if(msgp->iosb.devdep & MT$M_HWL) fprintf(stdout,",M_HWL");
	if(msgp->iosb.devdep & MT$M_LOST) fprintf(stdout,",M_LOST");

	if(msgp->iosb.count != 0) {
		fprintf(stdout," iocnt=%d",msgp->iosb.count);
	}

	fprintf(stdout," atm=%c",(aftertm == yes) ? '1' :
					(aftertm == no) ? '0' : '?');
	  
	fprintf(stdout," record=%d\n",msgp->record);	  
}

#endif /* TRACE */


/*****/

static short n_chan;
static IOSB n_iosb;

static int ni_seq = 1;
static int no_seq = 1;

static ZTNS_MSG n_buf;


static void ns_init(void)
{
	char ncb[512];
	DESCR ncb_dsc = {0-0,0,0,ncb};


/* translate SYS$NET */

	{
		$DESCRIPTOR(lnmtabdsc,"LNM$FILE_DEV");
		$DESCRIPTOR(lognamedsc,"SYS$NET");
		VMS_ITEM lnmlist[] =
			{{sizeof(ncb),LNM$_STRING,ncb,&ncb_dsc.dsc$w_length},
			 {0,0,NULL,NULL}};

		CHECK(sys$trnlnm(0,&lnmtabdsc,&lognamedsc,0,lnmlist));
	}

/* assign channel to NET */

	{
		$DESCRIPTOR(netdsc,"_NET:");

		CHECK(sys$assign(&netdsc,&n_chan,0,0));
	}

/* accept connection */

	CHECK(sys$qiow(0,n_chan,IO$_ACCESS,
			&n_iosb,0,0,
			0,&ncb_dsc,0,0,0,0));
	CHECK(n_iosb.status);

/* setup initial DEVDEPEND */

	ns_iosb.devdep = MT$M_BOT;
}


static void ns_req(int iofct,int ct)
{
	int dmafct = 0;
	unsigned dmasts;
	int datacnt = 0;
	unsigned char *datap = NULL;


/* send IOREQ */

	n_buf.seq = no_seq++;
	n_buf.func = ZTNS_F_IOREQ;
	n_buf.count = ct;
	n_buf.iofunc = iofct;

	CHECK(sys$qiow(0,n_chan,IO$_WRITEVBLK,
		&n_iosb,0,0,
		&n_buf,ZTNS_LEN1,0,0,0,0));
	CHECK(n_iosb.status);

/* read msgs, till IOREPLY */

 while(1) {
	CHECK(sys$qiow(0,n_chan,IO$_READVBLK,
		&n_iosb,0,0,
		&n_buf,sizeof(n_buf),0,0,0,0));
	CHECK(n_iosb.status);

/* check seq# */
	if(n_buf.seq != ni_seq++) FEHLER("prot.error: iseq");

/* check minimum length */
	if(n_iosb.count < ZTNS_LEN1) FEHLER("prot.error: msg too short");

/* process low level function */
	switch(n_buf.func) {
	  default:
		FEHLER("prot.error: bad func1");
		return;

	  case ZTNS_F_IOREPLY:
		if(datap != NULL) {
			FEHLER("prot.error: unexpected IOREPLY");
		}
		ns_iosb = n_buf.iosb;
		return;

	  case ZTNS_F_R_DATA:
		if(datap != NULL) {
			FEHLER("prot.error: unexpected R_DATA command");
		}
		*bufbctp = datacnt = (n_buf.count & 0xFFFF);
		datap = bufp;
		dmafct = n_buf.func;
		dmasts = SS$_NORMAL;
		break;

	  case ZTNS_F_C_DATA:
		if(datap != NULL) {
			FEHLER("prot.error: unexpected C_DATA command");
		}
		datacnt = (n_buf.count & 0xFFFF);
		if(*bufbctp < datacnt) {
			FEHLER("prot.error: C_DATA count too big");
		}
		datap = bufp;
		dmafct = n_buf.func;
		dmasts = SS$_NORMAL;
		break;

	  case ZTNS_F_W_DATA:
		if(datap != NULL) {
			FEHLER("prot.error: unexpected W_DATA command");
		}
		datacnt = (n_buf.count & 0xFFFF);
		if(datacnt <= 0) FEHLER("prot.error: DATA count .le.0");
		if(*bufbctp < datacnt) {
			FEHLER("prot.error: W_DATA count too big");
		}
		datap = bufp;

		/* send data */
		do {
			int c;

			n_buf.seq = no_seq++;
			n_buf.func = ZTNS_F_DATA;
			n_buf.count = c = MIN(datacnt,ZTNS_DATALEN);
			datacnt -= c;
			memcpy(n_buf.data,datap,c);
			datap += c;

			CHECK(sys$qiow(0,n_chan,IO$_WRITEVBLK,
				&n_iosb,0,0,
				&n_buf,c + ZTNS_LEN1,0,0,0,0));
			CHECK(n_iosb.status);
		} while(datacnt > 0);

		datap = NULL;
		break;

	  case ZTNS_F_DATA:
		if(datap == NULL) {
			FEHLER("prot.error: unexpected DATA");
		}
		if(n_iosb.count != ZTNS_LEN1 + n_buf.count) {
			FEHLER("prot.error: bad DATA msg length");
		}
		if(n_buf.count > datacnt) {
			FEHLER("prot.error: too much data");
		}

		switch(dmafct) {
		  case ZTNS_F_R_DATA:
			memcpy(datap,n_buf.data,n_buf.count);
			break;

		  case ZTNS_F_C_DATA:
			if(memcmp(datap,n_buf.data,n_buf.count)) {
				dmasts = SS$_DATACHECK;
			}
			break;
		  default:
			FEHLER("prot.error: dmafct bad");
		}
		datacnt -= n_buf.count;
		datap += n_buf.count;

		if(datacnt == 0) {	/* data transfer complete */
			n_buf.seq = no_seq++;
			n_buf.func = ZTNS_F_IOREPLY;
			n_buf.iosb.status = dmasts;

			CHECK(sys$qiow(0,n_chan,IO$_WRITEVBLK,
				&n_iosb,0,0,
				&n_buf,ZTNS_LEN1,0,0,0,0));
			CHECK(n_iosb.status);

			datap = NULL;
		}
		break;
	}
 }
}
