/* serve real tape, for ZT2/ZTserver */
/* w.j.m. jun 1989 */

/* implicit inputs:
	logical name ZT_TAPE points to a tape unit mounted foreign
	logical name ZT_NETOBJECT has equivalence `node::"0=name/'
*/

#define T_LOGNAME "ZT_TAPE"
#define N_LOGNAME "ZT_NETOBJECT"


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

#include ssdef
#include lnmdef
#include "iodef.h"	/* my own! */
#include "mtdef.h"

#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)
#define CHECKX(x) do {unsigned s=x; if(!(s&1)) sys$exit(s);} while(0)
#define MIN(a,b) ((a) < (b)) ? (a) : (b)

extern unsigned lib$get_foreign(),lib$stop();
extern unsigned sys$exit(),sys$assign(),sys$wake(),sys$hiber(),sys$trnlnm();
extern unsigned sys$qiow(int,int,int,
			IOSB*,void*,int,
			void*,int,int,int,int,int);
extern unsigned sys$qio(int,int,int,
			IOSB*,void*,int,
			void*,int,int,int,int,int);

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


/***** tape data *****/
static IOSB t_iosb;
static short t_chan;
static unsigned char t_buffer[0xFFFF];

static unsigned short ti_iofunc = 0;	/* request function, !=0 while busy */
static int ti_count;			/* request count */

static unsigned char *ti_datap = NULL;	/* input 'DMA' pointer,
						==NULL unless data expected */
static int ti_datacnt = 0;		/* input 'DMA' counter */

static int/*logical*/ ti_replyexp = 0;	/* IOREPLY expected */
static unsigned short ti_replysts;	/* status from same */

/***** network data *****/
static short n_chan;

	/* ZT -> NS */
static IOSB ni_iosb;
static ZTNS_MSG ni_buf;
static int ni_seq = 1;

	/* NS -> ZT (our output) */
static IOSB no_iosb;
static ZTNS_MSG no_buf;
static int no_seq = 1;


/*****/

static void net_ast();		/*forward*/

static void net_read()
{
	CHECKX(sys$qio(1,n_chan,IO$_READVBLK,
		&ni_iosb,net_ast,0,
		&ni_buf,sizeof(ni_buf),0,0,0,0));
}


static void net_send(fct)
int fct;	/* ZTNS_F_xxxx */
{
	no_buf.seq = no_seq++;
	no_buf.func = fct;

	CHECKX(sys$qiow(0,n_chan,IO$_WRITEVBLK,
		&no_iosb,0,0,
		&no_buf,
		((fct == ZTNS_F_DATA) ? (ZTNS_LEN1 + no_buf.count) : ZTNS_LEN1),
		0,0,0,0));
	CHECKX(no_iosb.status);
}


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


/* translate n_logname */

	{
		$DESCRIPTOR(lnmtabdsc,"LNM$FILE_DEV");
		$DESCRIPTOR(lognamedsc,N_LOGNAME);
		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));
	}

/* open connection */

	if(ncb_dsc.dsc$w_length+2+1+16+1 > sizeof(ncb)) {
		FEHLER("ncb too small");
	}

	/* clear conn.id & ncb info */
	memset(ncb+ncb_dsc.dsc$w_length,0,2+1+16);
	ncb_dsc.dsc$w_length += 2+1+16;

	/* add trailing quotemark */
	ncb[ncb_dsc.dsc$w_length] = '\"';
	ncb_dsc.dsc$w_length += 1;

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


/* start async. read from net */

	net_read();
}


static void net_ast()	/* here when we receive net msg */
{
/* check I/O */
	CHECKX(ni_iosb.status);

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

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

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

	  case ZTNS_F_IOREPLY:
		if(ti_replyexp == 0) {
			FEHLER("prot.error: unexpected IOREPLY");
		}
		ti_replyexp = 0;
		ti_replysts = ni_buf.iosb.status;
		CHECK(sys$wake(0,0));
		break;

	  case ZTNS_F_IOREQ:
		if(ti_iofunc != 0) {	/* still busy ... */
			FEHLER("prot.error: unexpected IOREQ");
		}
		ti_iofunc = ni_buf.iofunc;
		ti_count = ni_buf.count;
		CHECK(sys$wake(0,0));
		break;

	  case ZTNS_F_DATA:
		if(ti_datap == NULL) {
			FEHLER("prot.error: unexpected DATA");
		}
		if(ni_iosb.count != ZTNS_LEN1 + ni_buf.count) {
			FEHLER("prot.error: bad DATA msg length");
		}
		if(ni_buf.count > ti_datacnt) {
			FEHLER("prot.error: too much data");
		}

		memcpy(ti_datap,ni_buf.data,ni_buf.count);
		ti_datacnt -= ni_buf.count;
		ti_datap += ni_buf.count;

		if(ti_datacnt == 0) {	/* data transfer complete */
			ti_datap = NULL;
			CHECK(sys$wake(0,0));
		}
		break;
	}

/* wait for next msg */
	net_read();
}


/*****/

static void tap_invfun()	/* unsupported function or modifier */
{
	t_iosb.status = SS$_ILLIOFUNC;
	t_iosb.count = 0;
	/* leave devdepend! */
}


static void tap_0par()		/* REWIND, REWINDOFF, WRITEOF */
{
	CHECK(sys$qiow(0,t_chan,ti_iofunc,
			&t_iosb,0,0,
			0,0,0,0,0,0));
}


static void tap_skiprec()	/* skip records, forward/backward */
{
	CHECK(sys$qiow(0,t_chan,IO$_SKIPRECORD,
			&t_iosb,0,0,
			ti_count,0,0,0,0,0));

/* must undo "end-of-volume" recognition */
	if(t_iosb.status == SS$_ENDOFVOLUME) {
		unsigned char dummybuf[80];

		if(t_iosb.count != 0) FEHLER("skipcount !=0 on e.o.v.");

		CHECK(sys$qiow(0,t_chan,IO$_READVBLK,
				&t_iosb,0,0,
				dummybuf,sizeof(dummybuf),0,0,0,0));
		if(t_iosb.status != SS$_ENDOFFILE) {
			/* tape position lost since EOF must be next */
			t_iosb.status = SS$_TAPEPOSLOST;
		}
		t_iosb.count = 1;
	}
}


static void tap_read_wchk(wchk)	/* wchk==0: read, possibly w/datacheck */
int wchk;			/* wchk==1: simulated writecheck */
{
	CHECK(sys$qiow(0,t_chan,wchk ? IO$_READLBLK : ti_iofunc,
			&t_iosb,0,0,
			t_buffer,ti_count,0,0,0,0));
	if(t_iosb.count > 0) {
		int bcnt,c;
		unsigned char *bpt;

		no_buf.count = bcnt = MIN(t_iosb.count,ti_count);
		net_send(wchk ? ZTNS_F_C_DATA : ZTNS_F_R_DATA);
		bpt = t_buffer;
		do {
			no_buf.count = c = MIN(bcnt,ZTNS_DATALEN);
			bcnt -= c;
			memcpy(no_buf.data,bpt,c);
			bpt += c;
			if(bcnt == 0) {	/* last message */
				ti_replyexp = 1;
			}
			net_send(ZTNS_F_DATA);
		} while(bcnt > 0);
		CHECK(sys$hiber());	/* wait for reply */

		if(wchk) {	/* CMP may have error = SS$_DATACHECK */
			if((ti_replysts & 1) == 0) {
				t_iosb.status = ti_replysts;
			}
		} else {	/* GET must be o.k. */
			CHECKX(ti_replysts);
		}
	}
}


static void tap_write()		/* write, possibly w/datacheck */
{
	if(ti_count > 0) {
		no_buf.count = ti_datacnt = ti_count;
		ti_datap = t_buffer;
		net_send(ZTNS_F_W_DATA);

		CHECK(sys$hiber());	/* wait for data */
	}

	CHECK(sys$qiow(0,t_chan,ti_iofunc,
			&t_iosb,0,0,
			t_buffer,ti_count,0,0,0,0));
}


static unsigned ztns()		/* the action routine */
{
	int/*logical*/ ok = 1;
	unsigned fcode,fmodif;


	while(ok) {

/* sleep waiting for function */
		CHECK(sys$hiber());

/* check & dispatch function */
		fcode = ti_iofunc & IO$M_FCODE;
		fmodif = ti_iofunc & IO$M_FMODIFIERS;
		switch(fcode) {

		  case IO$_WRITEOF:	/* no modifiers */
			if(fmodif != 0) {
				tap_invfun();
			} else {
				tap_0par();
			}
			break;

		  case IO$_REWIND:	/* mod: NOWAIT */
		  case IO$_REWINDOFF:	/* mod: NOWAIT */
			if((fmodif & ~IO$M_NOWAIT) != 0) {
				tap_invfun();
			} else {
				tap_0par();
			}
			break;

		  case IO$_SKIPRECORD:	/* no modifiers */
			if(fmodif != 0) {
				tap_invfun();
			} else {
				tap_skiprec();
			}
			break;

		  case IO$_READLBLK:	/* mod: datacheck */
					/* note: datacheck is not end-to-end */
			if((fmodif & ~IO$M_DATACHECK) != 0) {
				tap_invfun();
			} else {
				tap_read_wchk(0);
			}
			break;

		  case IO$_WRITELBLK:	/* mod: datacheck */
					/* note: datacheck is not end-to-end */
			if((fmodif & ~IO$M_DATACHECK) != 0) {
				tap_invfun();
			} else {
				tap_write();
			}
			break;

		  case IO$_WRITECHECK:	/* no modifiers */
					/* note: this PHYSICAL I/O, 
					simulated as end-to-end check */
			if(fmodif != 0) {
				tap_invfun();
			} else {
				tap_read_wchk(1);
			}
			break;

		  default:
			tap_invfun();
		}

/* fudge */
		if(t_iosb.status == SS$_VOLINV) {	
			/* 'real' VALID bit apparently clear,
			/* since we can't change that,
			/* say MEDOFL (manual intervention required) */
			t_iosb.status = SS$_MEDOFL;
		}

/* say we're done */
		ti_iofunc = 0;

/* return result from t_iosb */
		no_buf.iosb = t_iosb;
		net_send(ZTNS_F_IOREPLY);
	}

	return 1;	/* not reached */
}

		
/*****/

static unsigned main()
{
	static $DESCRIPTOR(tape_dsc,T_LOGNAME);


/* assign channel to tape */
	CHECK(sys$assign(&tape_dsc,&t_chan,0,0));

/* rewind tape. this MUST succeed, otherwise something is wrong */
/* in particular, this checks that the tape is mounted foreign */
	CHECK(sys$qiow(0,t_chan,IO$_REWIND,
			&t_iosb,0,0,
			0,0,0,0,0,0));
	CHECK(t_iosb.status);

/* start the network connection & remote ZTserver */
	net_start();

/* do our work ... */

	return ztns();
}
