/*
 *	n e t u t l - netlnk
 */

/*)LIBRARY
*/

#ifdef	DOCUMENTATION

Title	netlnk	network link functions for Decus C
Index	netlnk	network link functions for Decus C

synopsis
	.s.nf
	  _#include <stdnet.h>
	.s
	  NIOV *opn_net(nfp)
	  int (*nfp)();
	.s
	  clsnet()
	.s
	  NIOV *con_net(dest_node, dest_task)
	  char *dest_node, *dest_task;
	.s
	  NIOV *con_obj(dest_node, dest_obj)
	  char *dest_node;
	  int dest_obj;
	.s
	  NIOV *acc_net(con_buf_ptr)
	  struct a_conblk *con_buf_ptr;
	.s
	  dsc_net(np, msg)
	  NIOV *np;
	  char *msg;
	.s.f
Description

	This group of functions allows openning a Decnet network,
	reading network data, and establishing and maintaining
	links.
	.s
	Opn_net() connects a task to the network. It must be issued
	before any other network calls. See the DECnet documentation
	on OPN$.
	.s
	Opn_net() takes one argument, the address of a function
	to handle network messages. If the argument is NULL, network
	messages will still be dequeued from the netword data queue,
	but will be discarded.
	.s
	If specified, the network message function will be called
	as follows:
	.s.nf
		netmsg(iosb, msg)
		int iosb[2];	/* stat block from GND$ call */
		char *msg;	/* message from GND$ */

	.s.f
	You should be familiar with how GND$ returns data before
	using this function. The msg pointer points to a buffer
	large enough to accept a full connect request message
	and can be passed directly to the acc_net() function.
	.s
	Your netmsg() function is called at ast state, and the
	buffer pointed to by msg is allocated dynamically. It
	is freed when your function returns. If you need to save
	any data from the message buffer, copy it to your own
	buffer before returning.
	.s
	Cls_net() closes the network data queue and returns the
	NIOV data structure and lun allocated for the link.
	Closing the network link while any data links are still
	up will cause unpredictable problems.
	.s
	Con_net() will attempt to connect the the remote node and
	task specified in the arguments. If it succeeds, it returns
	an NIOV pointer. If the connect fails, NULL is returned and
	the error condition is returned in $$nerr. You should declare
	$$nerr as:
	.s.nf
		extern int $$nerr;
	.s.f
	if you wish to use it. The pointer returned by con_net()
	is required for network IO functions such as put_net() and
	get_net().
	.s
	Con_obj() is similar to con_net(), but is used to connect
	to remote number network objects.
	.s
	Acc_net() will attempt to accept a connect request from
	another node. It is normally called with a connect buffer
	retrieved from the task's network data queue. 
	.s
	Acc_net() returns an NIOV pointer if successful, and NULL
	on error. The error condition is availible from $$nerr.
	.s
	Dsc_net() disconnects a logical link. The NIOV pointer
	must point to an open logical link, if not, unpredicatable
	errors will result. 
	.s
	You may pass a message to the other
	end of the link when disconnecting by passing a pointer to
	the message in msg. Set the msg argument to 0 if no message
	is needed.
	.s
	Dsc_net() frees the NIOV structure and lun used for the
	link. The memory area pointed to by np is returned to free
	memory, don't try to reuse np.

Implementation Details

	These routines try to mimic the Decus C interface to the 
	file system. NIOV is described in stdio.h.
	.s
	NIOV data structures are allocated with malloc and freed
	as necessary.
	.s
	Be sure you understand how the user supplied network 
	message function is called. The current implimentation
	is an attempt to provide the greatest flexibility while
	still hiding as much of the gory network detail as possible.
	.s
	The internal function getmsg() will check for the NT.DSC,
	NT.ABT, and NT.ABO and free the appropriate NIOV structures
	and luns AFTER calling the user supplied message function.

Bugs

	The routines try to do some error checking, but if a bad
	NIOV pointer is passed to a function, many strange things
	could happen, not the least of which is clobbering random
	areas of memory. Be careful.

Edit History

	  01 05/08/85 hjj fixed bad check for NDA in gndw.
#endif

#include <stdio.h>
#include <cx.h>
#include <stdnet.h>

extern int $$nlun;	/* number of luns available (C RTL) */

int $$nerr;		/* define $$nerr here. We assume any code */
			/* using $$nerr will call one of these routines */

NIOV *$$$ndq;		/* pointer to network que niov */

static int (*netmsg)();	/* pointer to network queue msg handler */

/*
	functions
*/

NIOV *opn_net(nfp)
int (*nfp)();		/* pointer to msg handler */
{
  NIOV *np_alo();	/* returns a pointer to a new NIOV */
  extern word cmpast();
  extern word netast();

  if (($$$ndq = np_alo()) == NULL) return(NULL);

  $$nerr = opnwnt($$$ndq->lun, $$$ndq->efn, $$$ndq->iosb, 0, 0, 0);
  dirtst($$nerr, $$$ndq->iosb, "opnw failed");

  if (nfp) netmsg = nfp;
  else netmsg = NULL;

  $$nerr = spawnt($$$ndq->lun, $$$ndq->efn, $$$ndq->iosb, &cmpast, &netast);
  dirtst($$nerr, $$$ndq->iosb, "spaw failed");

  return($$$ndq);
}

#ifdef LUNDEBUG
pniov(msg, np)
NIOV *np;
{
  printf("%s", msg);
  printf("lun %d efn %d rem_nam |%.8s| rem_tsk |%.8s| type %d\n",
  	np->lun, np->efn, np->rem_nam, np->rem_task, np->type);
  printf("status %o iosb %o %o\n",
	np->status, np->iosb[0], np->iosb[1]);
  printf("$$luns[0-7] %o, %o, %o, %o, %o, %o, %o, %o\n",
  	$$luns[0], $$luns[1], $$luns[2], $$luns[3], $$luns[4], 
  	$$luns[5], $$luns[6], $$luns[7]);
  printf("$$luns[8-15] %o, %o, %o, %o, %o, %o, %o, %o\n",
  	$$luns[8], $$luns[9], $$luns[10], $$luns[11], $$luns[12], 
  	$$luns[13], $$luns[14], $$luns[15]); 
}
#endif

cls_net()
{
  dsar();
  $$nerr = clswnt($$$ndq->lun, $$$ndq->efn, $$$ndq->iosb, 0);
  dirtst($$nerr, $$$ndq->iosb, "clsw failed");
  np_fre($$$ndq);		/* free up NIOV and lun */

  enar();
  return(1);
}

NIOV *con_net(dest_node, dest_task)
char *dest_node, *dest_task;
{
  NIOV *np;
  struct c_conblk *conp;

  if ((np = np_alo()) == NULL) return(NULL);

  if ((conp = malloc(sizeof (struct c_conblk))) == NULL) {
    $$nerr = IE_NBF;	/* no room left */
    return(NULL);
  }

  conb1(conp, dest_node, dest_task);	/* fill con req block */

  $$nerr = conwnt(np->lun, np->efn, np->iosb, 0, 
  	conp, (sizeof (struct c_conblk)), 0, 0, 0, 0);

  if ($$nerr < 0) {
    np_fre(np);		/* free up NIOV and lun */
    np = NULL;
  }
  else if (($$nerr = np->iosb[0]) != 1) { /* check status */
    np_fre(np);		/* free up NIOV and lun */
    np = NULL;
  }

  if (np) {	/* fill in niov data if connect succeeded */
    np->type = conp->n_rot;	/* copy type */
    if (np->type == 0) {		/* if remote is a task, not object */
      copy(np->rem_nam, conp->n_rnd, 6);	/* copy remote node */
      copy(np->rem_task, conp->n_rde, 
  	((conp->n_rdec <= 16)?conp->n_rdec:16)); /* copy remote task name */
    }
    np->status |= LINKUP;
  }
  mfree(conp);		/* give back con block */

  return(np);
}

NIOV *con_obj(dest_node, dest_obj)
char *dest_node;
int dest_obj;
{
  NIOV *np;
  struct c_conblk *conp;

  if ((np = np_alo()) == NULL) return(NULL);

  if ((conp = malloc(sizeof (struct c_conblk))) == NULL) {
    $$nerr = IE_NBF;	/* no room left */
    return(NULL);
  }

  conb0(conp, dest_node, dest_obj);	/* fill con req block */

  $$nerr = conwnt(np->lun, np->efn, np->iosb, 0, 
  	conp, (sizeof (struct c_conblk)), 0, 0, 0, 0);

  if ($$nerr < 0) {
    np_fre(np);		/* free up NIOV and lun */
    np = NULL;
  }
  else if (($$nerr = np->iosb[0]) != 1) { /* check status */
    np_fre(np);		/* free up NIOV and lun */
    np = NULL;
  }

  if (np) {	/* fill in niov data if connect succeeded */
    np->type = conp->n_rot;	/* copy type */
    if (np->type == 0) {		/* if remote is a task, not object */
      copy(np->rem_nam, conp->n_rnd, 6);	/* copy remoet node */
      copy(np->rem_task, conp->n_rde, 
  	((conp->n_rdec <= 16)?conp->n_rdec:16)); /* copy remote task name */
    }
    np->status |= LINKUP;
  }
  mfree(conp);		/* give back con block */

  return(np);
}

NIOV *acc_net(cp)
struct a_conblk *cp;
{
  struct a_conblk *acp;
  NIOV *np;
  int aloc_flag;

  aloc_flag = 0;	/* no memory allocated yet */
  if (cp) acp = cp;
  else {
    if ((acp = malloc(sizeof (struct a_conblk))) == NULL) {
      $$nerr = IE_NBF;	/* no room left */
      return(NULL);
    }
    aloc_flag = 1;	/* mark memory allocated */
  }

  if ((np = np_alo()) == NULL) {
    if (aloc_flag) mfree(acp);
    return(NULL);
  }
  $$nerr = accwnt(np->lun, np->efn, np->iosb, 0, 
		acp, (sizeof (struct a_conblk)), 0, 0);

  if ($$nerr < 0) {
    np_fre(np);		/* free up NIOV and lun */
    np = NULL;
  }
  else if (($$nerr = np->iosb[0]) != 1) { /* check status */
    np_fre(np);		/* free up NIOV and lun */
    np = NULL;
  }

  if (np) {	/* if no error */
    np->type = acp->n_sot;	/* copy type */
    if (np->type == 0) {		/* if remote is a task, not object */
      copy(np->rem_nam, acp->n_snd, 6);	/* copy remote node */
      copy(np->rem_task, acp->n_sde, 
	  ((acp->n_sdec <= 16)?acp->n_sdec:16));	/* copy remote task name */
    }
    np->status |= LINKUP;
  }
  if (aloc_flag) mfree(acp);
  return(np);
}    

dsc_net(np, msg)
NIOV *np;
char *msg;
{
  if (np == 0) return(0);

  $$nerr = dscwnt(np->lun, np->efn, np->iosb, 0, 
		msg, ((strlen(msg) <= 16)?strlen(msg):16));

  if ($$nerr == 1) $$nerr = np->iosb[0];  /* copy completion status */

  np_fre(np);		/* free up NIOV and lun */
  np = NULL;
  return(($$nerr == 1)?1:0);
}

/**************************************************************
  internal functions from here on
**************************************************************/

static NIOV *np_alo()		/* allocate an NIOV with a free lun */
{
  NIOV *np;
  unsigned *lp, *a_lun();

  if ((np = malloc(sizeof NIOV)) == NULL) { /* get an NIOV */
    $$nerr = IE_NBF;	/* no room left */
    return(NULL);
  }

  zero(np, sizeof NIOV);

  if ((lp = a_lun(&np->lun)) == NULL) { /* get a free lun */
    $$nerr = IE_ILU;	/* no luns left */
    return(NULL);
  }

  *lp = np;		/* insert the NIOV in the lun table */
  np->efn = np->lun;	/* lun and efn are the same */

  dirtst(alunx(np->lun, "NS", 0), 0, "link alun failed");

  return(np);
}

static np_fre(np)
NIOV *np;
{
  $$luns[np->lun-2] = 0;	/* zero out lun slot */
  mfree(np);
}

static unsigned *a_lun(lnum)
int *lnum;
{
  unsigned *lp;		/* lun address pointer */
  int lcnt;

  lp = $$luns;
  for (lcnt = 0; lcnt < $$nlun; lcnt++, lp++) 
    if (*lp == NULL) {
      *lnum = lcnt + 2;		/* free luns start at lun 2 */
      return(lp);
    }

  return(NULL);
}

static int conb1(conp, node, task)  /* fmt 1 connect block */
struct c_conblk *conp;
char *node, *task;
{
  int i, len;

  zero(conp, (sizeof (struct c_conblk)));
  fill(conp->n_rnd, 040, 6);	/* put spaces in node name */
  fill(conp->n_rde, 040, 16);	/* and destination task */

  len = strlen(node);
  copy(conp->n_rnd, node, (len > 6 ? 6 : len));
  for (i = 0; i < 6; i++) conp->n_rnd[i] = toupper(conp->n_rnd[i]);

  conp->n_rdec = ((len = strlen(task)) > 16) ? 16 : len;
  copy(conp->n_rde, task, conp->n_rdec);
  for (i = 0; i < 16; i++) conp->n_rde[i] = toupper(conp->n_rde[i]);

  conp->n_rfm = 1;	/* type 1 descriptor */
  conp->n_rot = 0;	/* object type 0 */

  return(1);
}

static int conb0(conp, node, obj)  /* fmt 0 connect block */
struct c_conblk *conp;
char *node;
int obj;
{
  int i, len;

  zero(conp, (sizeof (struct c_conblk)));
  fill(conp->n_rnd, 040, 6);	/* put spaces in node name */

  len = strlen(node);
  copy(conp->n_rnd, node, (len > 6 ? 6 : len));
  for (i = 0; i < 6; i++) conp->n_rnd[i] = toupper(conp->n_rnd[i]);

  conp->n_rfm = 0;	/* type 0 descriptor */
  conp->n_rot = obj;	/* fill in object type */

  return(1);
}

static word cmpast()
{
  byte *piosb;	/* pointer  to iosb */

  astset();
  piosb = gtdp(0);	/* point to iosb */
  dirtst(1, piosb, "spaw failed");
  piosb += 2;		/* move 1 word */
  if (*piosb > 0) get_msg(*piosb);
  astx(1);
}

static word netast()
{
  astset();
  get_msg(1);
  astx(0);
}

static int get_msg(msgcnt)
int msgcnt;
{
  byte *bp;
  struct a_conblk *acp;

  if ((acp = malloc(sizeof (struct a_conblk))) == NULL) {
    $$nerr = IE_NBF;	/* no room left */
    return(NULL);
  }

  bp = $$$ndq->iosb;

  while (msgcnt-- > 0) {

    $$nerr = gndwnt($$$ndq->lun,  $$$ndq->efn, $$$ndq->iosb, 0, 
	acp, (sizeof (struct a_conblk)));
    if (($$nerr == 1) && ($$$ndq->iosb[0] == (IE_NDA & 0377))) 
			return(1);	/* no data waiting */
    dirtst($$nerr, $$$ndq->iosb, "gndw failed");

    switch (*(bp+1)) { 
	case NT_DSC:
	case NT_ABT:
	case NT_ABO:	{
  		if (netmsg != 0) (*netmsg)($$$ndq->iosb, acp);
  		nl_fre(*(bp+3));
		break;
		}    
	default: {
  		if (netmsg != 0) (*netmsg)($$$ndq->iosb, acp);
		}
	}

  }
  mfree(acp);
  return(1);
}

static nl_fre(lun)	/* free NIOV and lun given lun */
int lun;
{
  NIOV *np;

  np = $$luns[lun-2];		/* get possible np */
  if (np->lun == lun) np_fre(np);	/* free if ok */
  else fatal("attempt to free noexistant lun in netlnk getmsg", 0, 0);
}

static int dirtst(dstat, pio, msg)
int dstat;
char *pio, *msg;
{
  if ((dstat >= 0) && ((pio == 0) || (*pio == 1))) return(0);
  fatal(msg, dstat, pio);
}

static int fatal(msg, dsw, fiosb)
char *msg;
word dsw, fiosb[2];
{
  tmsg('F', "%s: \ndsw:%o, fiosb:%o:%o\n", 
			msg, dsw, fiosb[0], fiosb[1]);
  exit(4);
}

