/*	first test of ZTDRIVER ...
	w.j.m. jun 1989
	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 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

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

typedef struct ZTmsg {
	unsigned short iosts;
	unsigned short iobct;
	unsigned long devdepend;
	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 "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;

#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)

void tape_init(void);
unsigned tape_writemark(void),
	tape_nop(void),
	tape_rewind(int/*logical*/ /*unload*/),
	tape_skiprec(int /*+-#blocks*/,int * /*#blocks skipped*/),
	tape_compare(int/*logical*/ /*reverse*/),
	tape_read(int/*logical*/ /*reverse*/,int/*logical*/ /*check*/),
	tape_write(int/*logical*/ /*check*/);

/* data shared with 'tape' routines */
unsigned char *bufp;
unsigned long *bufbctp;
int /*logical*/ hw_online,hw_medonline,hw_bot,hw_eof,hw_eot,hw_hwl;


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 void do_init()
{
	tape_init();
	if(hw_bot) {
		aftertm = no;
	} else {
		aftertm = dunno;
	}
}

static void do_writemark()
{
	/* fmodif: ... */

	msgp->iosts = tape_writemark();
	if(msgp->iosts & 1) {
		msgp->record ++;
		aftertm = yes;
	}
}

static void do_nop()
{
	/* give 'tape' a chance to update the state */
	msgp->iosts = tape_nop();

	/* 'aftertm' did not change, I hope ... */
}

static void do_rewind(int unload)
{
	/* fmodif: IO$M_NOWAIT ignored */

	msgp->iosts = tape_rewind(unload);
	msgp->record = 0;

#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 */
{
	unsigned status;
	int sca;


	if(aftertm != dunno) return SS$_NORMAL;	/* nothing to be done */

	status = tape_skiprec(-1,&sca);
	msgp->record -= sca;
	if(sca != 1) {
		if(!hw_bot) {
			return SS$_TAPEPOSLOST;
		} else {
			aftertm = no;
			return SS$_NORMAL;
		}
	}
	status = tape_skiprec(1,&sca);
	msgp->record += sca;
	if(sca != 1) {
		return SS$_TAPEPOSLOST;
	}
	aftertm = hw_eof;
	return SS$_NORMAL;
}

static void	/* "end of volume" recognized */
set_eov()	/* backspace over 2nd eof mark, decrement spacing count etc. */
{
	unsigned status;
	int sca;


	status = tape_skiprec(-1,&sca);
	msgp->record -= sca;
	msgp->iobct --;
	if(!(status & 1) || !hw_eof || sca != 1) {
		msgp->iosts = SS$_TAPEPOSLOST;
		aftertm = dunno;
	} else {
		/* aftertm remains == yes */
		msgp->iosts = SS$_ENDOFVOLUME;
	}
}

static void do_skiprec()
{
	int sc,sca;
	

	/* fmodif: ... */

	sc = msgp->media.w[0];
	if(sc == 0) {
		do_nop();
		return;
	} else if(sc > 0) {					/* FORWARD */
#if PRE_V54
		int/*logical*/ no_acp;


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

		msgp->iosts = tape_skiprec(sc,&sca);
		msgp->record += sca;
		msgp->iobct = sca;

		if(!(msgp->iosts & 1)) {
			aftertm = dunno;
		} else if(hw_eof) {
			aftertm = yes;
			if(
#if PRE_V54
			   no_acp &&
#endif
				     (sca == 1)) {
				/* "end of volume recognition" */
				set_eov();
			} else {
				msgp->iosts = SS$_ENDOFFILE;
			}
		} else {
			aftertm = no;
		}
	} else {						/* BACKWARD */
		msgp->iosts = tape_skiprec(sc,&sca);
		msgp->record -= sca;
		msgp->iobct = sca;

		aftertm = dunno;
		if((msgp->iosts & 1) && hw_eof) {
			msgp->iosts = SS$_ENDOFFILE;
		}
	}
}

static void do_skipfile()
{
	int sc,sca;
	
	/* fmodif: ... */

	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 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
			msgp->iosts = set_aftertm();
			if(!(msgp->iosts & 1)) return;	/* ... tapeposlost */
			if(aftertm == yes) {
				prevtm = msgp->record;
			} else {
				prevtm = -2;
			}
#if PRE_V54
		}
#endif

		msgp->iobct = 0;
		do {
			do {
				msgp->iosts = tape_skiprec(0x7FFF,&sca);
				msgp->record += sca;
				if(!(msgp->iosts & 1)) {
					aftertm = dunno;
					return;
				}
			} while(!hw_eof);

			msgp->iobct ++;
			sc --;

#if PRE_V54
			if(no_acp) {
#endif
				if(msgp->record == prevtm+1) {
					aftertm = yes;
					set_eov();
					return;
				} else {
					prevtm = msgp->record;
				}
#if PRE_V54
			}
#endif
		} while(sc > 0);
		aftertm = yes;
	} else {						/* BACKWARD */
		msgp->iobct = 0;
		do {
			do {
				msgp->iosts = tape_skiprec(-0x7FFF,&sca);
				msgp->record -= sca;
				if(!(msgp->iosts & 1)) {
					aftertm = dunno;
					return;
				}
			} while(!hw_eof && !hw_bot);

#if 0	/* I had this in the pre-5.4 version, 		*/
	/* but TMDRIVER has always counted differently	*/
			msgp->iobct ++;
#else
			if(hw_bot) break;		/** don't count BOT **/
			msgp->iobct ++;			/** (from TMDRIVER) **/
#endif
			sc ++;

		} while(sc < 0 && !hw_bot);
		aftertm = dunno;
	}
}

static void do_writecheck()
{
	int /*logical*/ reverse;


	/* fmodif: ... */
	reverse = ((fmodif & IO$M_REVERSE) != 0);
	if(reverse) {				/* not supported so far */
		msgp->iosts = SS$_BADPARAM;
		return;
	}

	CHECK(ZT_FRUSER());

	msgp->iosts = tape_compare(reverse);	/* 'updates' *bufbctp	*/
	if((msgp->iosts & 1) ||
	   (msgp->iosts == SS$_PARITY) ||
	   (msgp->iosts == SS$_DATACHECK)) {	/*** assume tape moved ***/
		if(reverse) {
			msgp->record --;
			aftertm = dunno;
		} else {
			msgp->record ++;
			aftertm = hw_eof;
		}
	}
	if(msgp->iosts & 1) {
		if(hw_eof) {
			msgp->iosts = SS$_ENDOFFILE;
		} else {
			msgp->iobct = *bufbctp;
			if(*bufbctp > msgp->bcnt) {
				msgp->iosts = SS$_DATAOVERUN;
			}
		}
	}
}

static void do_read()
{
	int /*logical*/ reverse, check;


	/* fmodif: ... */
	reverse = ((fmodif & IO$M_REVERSE) != 0);
	if(reverse) {				/* not supported so far */
		msgp->iosts = SS$_BADPARAM;
		return;
	}
	check = ((fmodif & IO$M_DATACHECK) != 0);

	msgp->iosts = tape_read(reverse,check);	/* fills in *bufbctp	*/
	if((msgp->iosts & 1) ||
	   (msgp->iosts == SS$_PARITY) ||
	   (msgp->iosts == SS$_DATACHECK)) {	/*** assume tape moved ***/
		if(reverse) {
			msgp->record --;
			aftertm = dunno;
		} else {
			msgp->record ++;
			aftertm = hw_eof;
		}
	}
	if(msgp->iosts & 1) {
		if(hw_eof) {
			msgp->iosts = SS$_ENDOFFILE;
		} else {
			msgp->iobct = *bufbctp;
			if(*bufbctp > msgp->bcnt) {
				msgp->iosts = SS$_DATAOVERUN;
				*bufbctp = msgp->bcnt;
			}
			CHECK(ZT_TOUSER());
		}
	}
}
		
static void do_write()
{
	int/*logical*/ check;


	/* fmodif: the mysterious IO$M_ERASE flag is simply ignored,
			ditto IO$M_NOWAIT ("TU81plus only") ... */
	check = ((fmodif & IO$M_DATACHECK) != 0);

	CHECK(ZT_FRUSER());
	msgp->iosts = tape_write(check);
	if(msgp->iosts & 1) {
		msgp->record ++;
		msgp->iobct = *bufbctp;
		aftertm = no;
	}
}


/*****/

#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

	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((msgp->iosts & 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:
		msgp->iosts = SS$_ILLIOFUNC;
		break;
	}

/* final processing:
	iosts		- has been set
	iobct		- has been set (or defaulted to 0)
	devdepend	- to be set here
	record		- has been updated (if changed)
	ucbsts		- v_valid has been modified (if changed)
*/

	if(msgp->iosts == SS$_TAPEPOSLOST) {	/* set 'LOST' BIT from status */
		msgp->devdepend |= MT$M_LOST;
	}

	if(hw_bot) {			/* "BOT" */
		msgp->devdepend |= MT$M_BOT;		/* set BOT */
		msgp->devdepend &= ~MT$M_LOST;		/* clear pos. lost */
		aftertm = no;
	} else {
		msgp->devdepend &= ~MT$M_BOT;		/* clear BOT */
	}

	if(hw_eof) {			/* "EOF" */
		msgp->devdepend |= MT$M_EOF;		/* set EOF */
	} else {
		msgp->devdepend &= ~MT$M_EOF;		/* clear EOF */
	}

	if(hw_eot) {			/* "EOT" */
		msgp->devdepend |= MT$M_EOT;		/* set EOT */

		switch(fcode) {			/* usually set iosts=EOT */
		  case IO$_SPACEFILE:
		  case IO$_SKIPFILE:
		  case IO$_SPACERECORD:
		  case IO$_SKIPRECORD:
			if(msgp->media.w[0] >= 0) goto eotsts;
			break;			/* not for space reverse */

		  case IO$_WRITECHECK:
			if(!(fmodif & IO$M_REVERSE)) goto eotsts;
			break;			/* not for writecheck reverse */

		  case IO$_READPBLK:
		  case IO$_READVBLK:
			break;			/* not for read */

		  default:
eotsts:
			if(msgp->iosts & 1) {
				msgp->iosts = SS$_ENDOFTAPE;
			}
			break;
		}
	} else {
		msgp->devdepend &= ~MT$M_EOT;		/* clear EOT */
	}

	if(hw_hwl) {			/* write lock */
		msgp->devdepend |= MT$M_HWL;		/* set HWL */
	} else {
		msgp->devdepend &= ~MT$M_HWL;		/* clear HWL */
	}

#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(hw_online);
}


/*****/

#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->iosts) {
	  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->iosts);
		break;
	}

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

	if(msgp->iobct != 0) {
		fprintf(stdout," iobct=%d",msgp->iobct);
	}

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

#endif /* TRACE */
