/* @(#)osr.c	19.1 (ES0-DMD) 02/25/03 13:56:14 */
/*===========================================================================
  Copyright (C) 1995 European Southern Observatory (ESO)
 
  This program is free software; you can redistribute it and/or 
  modify it under the terms of the GNU General Public License as 
  published by the Free Software Foundation; either version 2 of 
  the License, or (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public 
  License along with this program; if not, write to the Free 
  Software Foundation, Inc., 675 Massachusetss Ave, Cambridge, 
  MA 02139, USA.
 
  Corresponding concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*+++++++++++   
.TYPE           Module
.LANGUAGE       C
.IDENTIFICATION osr
.AUTHOR         Francois Ochsenbein [ESO-IPG], Philippe Defert [ST-ECF]
.KEYWORDS       X25 communication management
.ENVIRONMENT    VAX/VMS, PSI
.COMMENTS       Functions in this module allow to connect, clear,
		read and write messages over X25.
		The connection number is a number starting with 200.
\begin{TeX}

\end{TeX}

.VERSION 1.0	17-Nov-1986: Creation
.VERSION 1.1	16-Dec-1986: Some functions are defined as ``static"
.VERSION 1.2	28-Jan-1987: New X25 structure; acquire time at
			each echange of message.
.VERSION 2.0	03-Feb-1988: Transformed into osr routines
.VERSION 2.1	30-Mar-1989: Transformed input to ASCII...
				(IBM computers don't)
				Force userdata!
.VERSION 2.2	11-May-1989: Added osrwait.
.VERSION 2.3	05-Sep-1989: Changed some `unclever' macros...
---------------------------------*/

#define DEBUG 0 		/* Debugging option	*/

#define MAX_IPCC	8	/* Maximal Number of Connections */

#include <osdefos.h>	
#include <osremote.h>
#include <macrogen.h>
#include <errno.h>	/* System	*/

#define  X25_BUFSIZE	256	/* Size of input buffer	*/
#define  X25_START	200	/* Starting x25 number  */

MID_EXTERN int oserror;
MID_EXTERN char *oserrmsg;
MID_EXTERN int vmserror;

char *oshenv();

struct X25_struct {		/* X25 packet for communication	*/
	char    *dte;		/* Destination name		*/
	char    *net;		/* Network     name		*/
	char    *mbxname;	/* Mailbox     name		*/
	char    *mbxbuf;	/* Mailbox     buffer		*/
	char 	*inbuf;		/* Read over X25 buffer		*/
	int	inbytes;	/* Bytes in inbuf		*/
	int     nw_unit;	/* Number  of NW device		*/
	short int nw_chan;	/* Channel to NW device		*/
	short int mbx_chan;	/* Channel to Mailbox		*/
     /*	unsigned long int efn;	/* Event flag			*/
	};

# include <descrip.h>	/* System	*/
# include <psidef.h>	/* Not in system...	*/
# include <dvidef.h>	/* System	*/
# include <ssdef.h>	/* System	*/
# include <iodef.h>	/* System	*/
# include <nfbdef.h>	/* System	*/
# include <msgdef.h>	/* System	*/
# define MBX_BUFSIZE	256		/* Mailbox size			*/
# define MBX_NAME	"X25S_MBA"	/* Mail box logical name	*/
# define L	dsc$w_length
# define S	dsc$a_pointer

typedef struct dsc$descriptor descript;
# define void_string	{0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL_PTR(char)}   
# define InitDescript(text) 	\
			{sizeof(text)-1, DSC$K_DTYPE_T, DSC$K_CLASS_S,text}   

# define SET_STRING(dsc,str,l)	dsc.S = str, dsc.L = l		\
				 /* Set address & length of a string    */

  typedef struct {		/* Quad structure for descriptor */
	long int len;
	char *s; } QUAD;
  struct item {
	unsigned short int len;
	unsigned short int item_code;
	void * addr;
	long * return_len;
	};

  static unsigned short int io_status [4];	/* status quadword for qio */
  static long returned_length;

  static descript nw_name = InitDescript("_NWA0:");
  					/* NW device name 		*/
  static descript mbx = void_string;	/* Mail box name descriptor	*/
  static QUAD  ncb = 			/* ncb descriptor		*/
	{0, 0};

  static struct {		/* Used with the GETDVI system service	*/
	struct item item_1;
	long int end;
	}     getdvi= {
		{sizeof(int),DVI$_UNIT,0,&returned_length},
		 0 };

# define CheckVMS	if (!(vmserror&1))	{oserror = EINTR; FINISH;}
# define ioCheckVMS	if (iotest())		FINISH

typedef struct X25_struct X25;
#define NULL_X25		NULL_PTR(X25)

static X25 *Connections[MAX_IPCC] = {0,0,0,0}, *x25 = NULL_X25;
static int nx25 = X25_START;	/* Number of used connections	*/

#define FINISH			goto FIN

/*=====================================================================
		VMS-specific Routines
 *=====================================================================*/

/*=====================================================================*/
static int iotest()
/*+++
.PURPOSE Test the status and the i/o control block
.RETURNS 0 (OK) / -1 (Bad)
.REMARKS *** For internal use ***
---*/
{
  if (vmserror & 1)	
  	if ((vmserror = io_status[0]) == SS$_CLEARED)
  		oserrmsg = "Connection Cleared by remote DTE.";

  if (!(vmserror&1))
  	oserror = (oserrmsg ? -1 : EINTR);

  return(oserror);
}

/*=====================================================================*/
static int rncb(ptr,dte,subdte,user,net)
/*+++
.PURPOSE Create the NCB (Network Control Block)
.RETURNS Length used in NCB
.REMARKS ***No check on the length of ncb buffer is performed!***
---*/
	unsigned char *ptr;	/* OUT: resulting NCB		*/
	unsigned char *dte;	/* IN: The Destination address	*/
	unsigned char *subdte;	/* IN: Subdestination		*/
	unsigned char *user;	/* IN: User data		*/
	unsigned char *net;	/* IN: Network name		*/
{
	MID_RSTATIC  unsigned char user_x29[] = {1,0,0,0};
	MID_REGISTER unsigned char *p;
	MID_REGISTER struct header {
		unsigned short int len;
		unsigned short int item;
		unsigned char count;
		} *ps;

  p = ptr;

						/*  remote DTE address  */
  ps = (struct header *)p, p = (unsigned char *)(ps+1);
  ps->count = strlen(dte);
  ps->item  = PSI$C_NCB_REMDTE;
  p        += oscopy(p, dte, ps->count);
  ps->len   = p - (unsigned char *)ps;

  if (subdte != NULL_PTR(unsigned char))	/*   remote subaddress  */
  {	ps = (struct header *)p, p = (unsigned char *)(ps+1);
	ps->count = strlen(subdte);
	ps->item  = PSI$C_NCB_REMSUBADR;
	p        += oscopy(p, subdte, ps->count);
	ps->len   = p - (unsigned char *)ps;
  }

  if_not(user)	user = (unsigned char *)"";	/***** Force User Data ****/
  {	ps = (struct header *)p, p = (unsigned char *)(ps+1);
	ps->count = strlen(user) + sizeof(user_x29);
	ps->item  = PSI$C_NCB_USERDATA;
	p	 += oscopy(p, user_x29, sizeof(user_x29));
	p	 += oscopy(p, user, ps->count - sizeof(user_x29));
	ps->len   = p - (unsigned char *)ps;
  }

  if (net != NULL_PTR(unsigned char))		/* Local Network name	*/
  {	ps = (struct header *)p, p = (unsigned char *)(ps+1);
	ps->count = strlen(net);
	ps->item  = PSI$C_NCB_LOCNET;
	p        += oscopy(p, net, ps->count);
	ps->len   = p - (unsigned char *)ps;
  }
 
  return(p - ptr);
}

/*=====================================================================
			Internal Routines
 *=====================================================================*/

static int getx25(xid)
/*+++
.PURPOSE Retrieve the X25 packet.
.RETURNS 0 / -1 (error)
.REMARKS 
------*/
	int xid;	/* IN: Circuit Number	*/
{
  oserrmsg = NULL_PTR(char);
  oserror  = 0;
  x25      = NULL_X25;
  
  if ((xid < X25_START) || (xid > nx25))	FINISH;
  x25 = Connections[xid-X25_START];
  
  FIN: if (x25 == NULL_X25)
  {	oserror = -1;
  	oserrmsg = "Bad X25-circuit Number";
  }
  return(oserror);
}

/*=====================================================================
			osr Routines
 *=====================================================================*/

/*=====================================================================*/
int osrinfo(xid, buf)
/*+++
.PURPOSE Get info about a connection
.RETURNS 0 / -1 (error). However, the net name is always filled.
.REMARKS 
------*/
	int xid;		/* IN: The Circuit Number	*/
	struct remstat *buf;	/* OUT: The info	*/
{

  getx25(xid);
  
  if(x25)
  	buf->dte = x25->dte, 		buf->net = x25->net, 
  	buf->device = x25->mbxname,	buf->unit= x25->nw_unit;
  else 
  	buf->dte = "",	
	buf->net = oshenv("PSI$NETWORK", "LNM$SYSTEM_TABLE"),
  	buf->device = MBX_NAME,		buf->unit = 0;

  return(x25 ? 0 : -1);
}

/*=====================================================================*/
int osropen(dte,subdte,user,net)
/*+++
.PURPOSE Connect to specified remote address.
\begin{TeX}
	The operations are as follows:
	\begin{enumerate}
	\item	Create the MailBox, logical name `{\tt X25S\_MB}$x$'.
	\item	Connect the Mailbox to NWA0 channel; 
	\item	Retrieve the {\tt NW}$x$ unit, via a {\tt GETDVI}
	\item	Build up the NCB (Network Control Block)
	\item	Set up the virtual circuit, using {\tt IO\$\_ACCESS}
	\end{enumerate}
\end{TeX}
.RETURNS Connection facility as a positive number / -1 on error 
	(oserror explains)
.REMARKS The destination may be found
	using oshenv(symbolic_destination, "PSI$DTE_TABLE"),
	and the user data via oshenv(dest_U, "PSI$DTE_TABLE").
------*/
	char *dte;	/* IN: The Destination address	*/
	char *subdte;	/* IN: Subdestination (or NULL)	*/
	char *user;	/* IN: User data (or NULL)	*/
	char *net;	/* IN: Network name (or NULL)	*/
{
	register int i;

  oserrmsg = NULL_PTR(char);		/* Error Indicator	*/
  oserror  = 0;
  x25 = NULL_X25;

	/* Check if there are not too many connections	*/

  for (i = 0; i < ITEMS(Connections); i++)  
  	if (!Connections[i])	break;
  if (i >= ITEMS(Connections))
  {	oserrmsg = "Too many Connections";
  	FINISH;
  }

  if (!dte)			/* No address	*/
  {	oserrmsg = "No DTE address";
	FINISH;
  }

			/* Get Network Name if not specified	*/
  if (!net) net = oshenv("PSI$NETWORK", "LNM$SYSTEM_TABLE");
  if (!net)			/* No network	*/
  {	oserrmsg = "No Network Available";
	FINISH;
  }

	/* Allocate the X25 structure	*/

  x25 = (X25 *) osmmget(sizeof(X25) + X25_BUFSIZE + MBX_BUFSIZE + 2 + 
  	sizeof(MBX_NAME) + strlen(net) + strlen(dte));
  if (oserror)	FINISH;
  x25->inbuf 	= (char *)(x25+1);
  x25->mbxbuf 	= x25->inbuf  + X25_BUFSIZE;
  x25->dte 	= x25->mbxbuf + MBX_BUFSIZE;
  x25->net 	= x25->dte + oscopuc(x25->dte, dte, 1000, EOS) + 1;
  x25->mbxname 	= x25->net + oscopuc(x25->net, net, 1000, EOS) + 1;
  x25->inbytes  = 0;
  oscopy(x25->mbxname, MBX_NAME, sizeof(MBX_NAME)); /* Copy Mailbox name */
  x25->mbx_chan	= 0;
  x25->nw_chan	= 0;

	/*   2) Create a MailBox	*/

			  		/* Create a new mailbox name	*/
  *(x25->mbxname + sizeof(MBX_NAME) - 2) = 'A' + i;
  SET_STRING(mbx, x25->mbxname, sizeof(MBX_NAME)-1);
  vmserror = SYS$CREMBX(0,&x25->mbx_chan,0,0,0,0,&mbx);
  CheckVMS;

	/*  3) Assign channel to NW  (associated with mailbox)	*/

  vmserror = SYS$ASSIGN(&nw_name,&x25->nw_chan,0,&mbx);
  CheckVMS;

	/* 4) Retrieve the actual NW number (not required, but... */

  getdvi.item_1.addr = &(x25->nw_unit);
  vmserror = sys$getdviw(0,x25->nw_chan,0,&getdvi,io_status,0,0,0);
  ioCheckVMS;

	/* 5) Construct the NCB	*/

  ncb.s   = x25->inbuf;
  ncb.len = rncb(ncb.s, dte, subdte, user, net);

	/* 6) Ask for setup virtual circuit : send the initial packet */

  vmserror = SYS$QIOW(0,x25->nw_chan,IO$_ACCESS,io_status,0,0,
             0,&ncb,0,0,0,0);
  CheckVMS;
   
	/*  If request correctly queued, decode the result from io_status */

  vmserror = io_status[0];
  switch(vmserror)
  { case SS$_NORMAL:
	switch(io_status[2])
	{ case PSI$M_STS_REMDTELNG: 
		oserrmsg = ("Remote DTE address too long"); FINISH;
	  case PSI$M_STS_USERLNG: 
		oserrmsg = ("Too much user data supplied"); FINISH;
	} break;
    case SS$_ABORT: case SS$_IVDEVNAM:
	oserror = EINTR;
	switch(io_status[2])
	{ case PSI$C_ERR_UNKNOWN:
		oserrmsg = ("PSN Unknown error."); break;
	  case PSI$C_ERR_FACLNG:
		oserrmsg = ("PSN Facilities too long."); break;
	  case PSI$C_ERR_INVITEM:
		oserrmsg = ("PSN Invalid item code"); break;
	  case PSI$C_ERR_CONFLICT:
		oserrmsg = ("PSN Conflicting items"); break;
	  case PSI$C_ERR_BADPARM:
		oserrmsg = ("PSN Bad parameters specified"); break;
	  case PSI$C_ERR_NOTRANS:
		oserrmsg = ("PSN No translation for name"); break;
	  case PSI$C_ERR_RECURLMT:
		oserrmsg = ("Recursion limit reached"); break;
	  case PSI$C_ERR_INVNUM:
		oserrmsg = ("Invalid ASCII number"); break;
	  case PSI$C_ERR_NOICI:
		oserrmsg = ("No internal identifier specified"); break;
	  case PSI$C_ERR_MANYICI:
		oserrmsg = ("More than one internal call identifier"); break;
	  case PSI$C_ERR_NOTIMP:
		oserrmsg = ("Non implemented feature"); break;
	  case PSI$C_ERR_NOLINES:
		oserrmsg = ("No lines available for the call"); break;
	  case PSI$C_ERR_NOSUCHLINE:
		oserrmsg = ("No such line"); break;
	  case PSI$C_ERR_NOSUCHPVC:
		oserrmsg = ("Specified PVC unknown"); break;
	  case PSI$C_ERR_NOSUCHNET:
		oserrmsg = ("Specified PSN unknown"); break;
	  case PSI$C_ERR_NOLOCAL:
		oserrmsg = ("ACP is out of local workspace"); break;
	  case PSI$C_ERR_NONONPAG:
		oserrmsg = ("Insufficient free non-paged pool"); break;
	  case PSI$C_ERR_NOL3:
		oserrmsg = ("Internal error"); break;
	  case PSI$C_ERR_BADNAME:
		oserrmsg = ("Bad counted string"); break;
	  case PSI$C_ERR_L3ERR:
		oserrmsg = ("Error returned from level 3"); break;
	} FINISH;
    case SS$_CLEARED: 
	oserrmsg = ("Remote DTE rejects the call...");
    default:			
	oserror = EINTR;
	FINISH;
  }

	/* WAIT FOR ANSWER FROM REMOTE	*/
	/* ===========================  */

  vmserror = SYS$QIOW(0,x25->mbx_chan,IO$_READVBLK|IO$M_TIMED,io_status,0,0,
	x25->mbxbuf, MBX_BUFSIZE,4,0,0,0);
  ioCheckVMS;

  if ((*(x25->mbxbuf) ==  MSG$_CONNECT) || (*(x25->mbxbuf) == MSG$_INCDAT))
	/* 	OK	TRACE_ED_STRING("Connected to ",x25->name)	*/ ;
  else 
  {	oserrmsg = "Connection rejected.";
	FINISH;
  }

  Connections[i] = x25;
  i += X25_START;		/* Actual connection number */
  if (i > nx25)		nx25 = i;


  FIN:			/* If connection failed, clear 	*/
  if (oserrmsg)		oserror = -1;
  if (oserror)		/* Failed  */
  {	if (x25){
		if(x25->nw_chan)  SYS$DASSGN(x25->nw_chan);
		if(x25->mbx_chan) SYS$DASSGN(x25->mbx_chan);
		osmmfree(x25);
	}
  	i = -1;
  }
#if DEBUG
	printf("Connection Summary: dte=%s, net=%s, mailbox=%s, nw#%d\n",
		x25->dte, x25->net, x25->mbxname, x25->nw_unit);
#endif
  return(i);
}

/*=====================================================================*/
int osrclose(xid)
/*+++
.PURPOSE Clear a virtual circuit
.RETURNS 0 / -1 (use osmsg for explanation)
.REMARKS 
---*/
	int xid;	/* IN: The circuit to clear	*/
{
  if (getx25(xid))	FINISH;

  vmserror = SYS$QIOW(0,x25->nw_chan,IO$_DEACCESS,&io_status,0,0,0,0,0,0,0,0);
  iotest();

  SYS$DASSGN(x25->nw_chan);
  SYS$DASSGN(x25->mbx_chan);
  
  FIN:
  if(x25)	osmmfree(x25), Connections[xid-X25_START] = NULL_X25;
  if(oserrmsg)	oserror = -1;

  return (oserror ? -1 : 0);
}

/*=====================================================================*/
int osrend()
/*+++
.PURPOSE Clear all virtual circuits
.RETURNS 0 / -1 (use osmsg for explanation)
.REMARKS 
---*/
{
	register int i;
	
  for (i = nx25; --i >= X25_START; )
  	osrclose(i);

  return (oserror ? -1 : 0);
}

/*=====================================================================*/
static int rin()
/*+++
.PURPOSE Check if something is to be read over X25
.RETURNS Length of input / -1 (line cleared)
.REMARKS Reading is performed via the internal buffer associated
	to the x25 packet.
---*/
{
	register int len;
	register char *p;
  
	/* Look first if there is something in the internal buffer */

  len = x25->inbytes;
  if (len > 0)			FINISH;

	/* Read without wait	*/

/*  vmserror = SYS$QIOW(0, x25->nw_chan, IO$_READVBLK|IO$M_NOW, */
  vmserror = SYS$QIO(0, x25->nw_chan, IO$_READVBLK|IO$M_NOW, 
  	io_status, 0,0,	x25->inbuf, X25_BUFSIZE, 0,0,0,0);
  CheckVMS;

  len = io_status[1];

  switch(io_status[0])
  { case SS$_NODATA:	len = 0;		break;
    case SS$_NORMAL: 				break;
    default:		ioCheckVMS;
  }
  x25->inbytes = len;
		
  for (p = x25->inbuf + len; p > x25->inbuf; )	/* Transform to ASCII	*/
  	*(--p) &= 0x7f;

  FIN:
  if (oserrmsg)	oserror = -1;
  return(oserror ? -1 : len);
}

/*=====================================================================*/
int osrin(xid)
/*+++
.PURPOSE Check if something is to be read over X25
.RETURNS Length of input / -1 (error, see osmsg)
.REMARKS 
---*/
	int xid;	/* IN: The circuit to clear	*/
{
	register int s;

  if (getx25(xid))	FINISH;
  s = rin();
  
  FIN: return(oserror ? -1 : s);
}
  
/*=====================================================================*/
int osrwait(xid, timeout)
/*+++
.PURPOSE Wait for a message over the connection opened with osropen
.RETURNS Length of message (0 if none) / -1 if error (osmsg explains)
.REMARKS The message is not returned. osrread can be used for it.
---*/
	int xid;	/* IN: The connection number	*/
	int  timeout;	/* IN: Max time to wait (s)	*/
{
	register int i, len;

	/* Look first if data are coming --- 
	 * Wait for a maximum of timeout seconds until the answer	*/

  len = osrin(xid);
  if (len)			FINISH;		/* Done ... or error !	*/

  for (i=timeout*4; (--i >= 0) && (len == 0); len = rin() )
	ospwms(250);

  FIN:
  return(oserror ? -1 : len);
}

/*=====================================================================*/
int osrread(xid, buf, lbuf, timeout)
/*+++
.PURPOSE Read a message over the connection opened with osropen
.RETURNS Length of message / -1 if error (osmsg explains)
.REMARKS Return error if timeout! 
---*/
	int xid;	/* IN: The connection number	*/
	char *buf;	/* OUT: Read text		*/
	int  lbuf;	/* IN: Size of buffer		*/
	int  timeout;	/* IN: Max time to wait (s)	*/
{
	register int i, len;

	/* Check first of the buffer is at least one character! 	*/
  
  if (lbuf < 1)
  {	oserrmsg = "osrread Buffer is of negative or zero size!";
  	oserror = -1, len = -1;
  	FINISH;
  }

	/* Use osrwait which terminates as soon as somethinf arrives	*/

  len = osrwait(xid, timeout);

	/* Copy from internal buffer to user-supplied one	*/

  FIN:
  if (len > 0)
  {	len = MIN(x25->inbytes, lbuf);
	oscopy(buf, x25->inbuf, len);
	if ((len == 1) && (*buf == '\01'))	/* Line Cleared	*/
	{	oserrmsg = "Connection Cleared by remote DTE.";
		oserror  = -1;
	}
	i =  x25->inbytes - len;	/* Bytes still in buffer	*/
	if (i > 0 )			/* There are remaining bytes	*/
		oscopy(x25->inbuf, x25->inbuf + len, i);
	x25->inbytes = i;
  } 
  return(oserror ? -1 : len);
}

/*=====================================================================*/
int osrgets(xid, buf, lbuf, timeout)
/*+++
.PURPOSE Read a message over the connection opened with osropen,
	and reformats (suppress NUL characters and carriage-return,
	terminate the string with the EOS)
.RETURNS Length of message / -1 if error (osmsg explains)
.REMARKS 
---*/
	int xid;	/* IN: The connection number	*/
	char *buf;	/* OUT: text to read		*/
	int  lbuf;	/* IN: Size of buffer		*/
	int  timeout;	/* IN: Max time to wait (s)	*/
{
	register int lr;
	register char *p, *q;

	/* p is used as the running pointer in buffer.	*/

  p  = buf, *p = EOS;

  lr = osrread(xid, p, lbuf-1, timeout);	
  if (lr <= 0)		FINISH;		/* Error, or nothing	*/

					/* Suppress NUL and useless
					 * control characters	*/
  for (q = p; --lr >=0; q++)
  {	if (*q == EOS)		continue;
	if (*q == '\r')		continue;
	*(p++) = *q;
  }
  *p = EOS;
  lr = p - buf;

  FIN:
  return(oserror ? -1 : lr);
}

/*=====================================================================*/
int osrwrite(xid, buf, lbuf)
/*+++
.PURPOSE Write a message over X25
.RETURNS Size of written message / -1 (osmsg explains)
.REMARKS 
---*/
	int  xid;	/* IN: The connection number	*/
	char *buf;	/* IN: Text to write		*/
	int  lbuf;	/* IN: Size of buffer		*/
{
  if (getx25(xid))	FINISH;

  vmserror = SYS$QIOW(0, x25->nw_chan, IO$_WRITEVBLK, io_status, 0,0,
	buf, lbuf, 0, 0,0,0,0);
  iotest();

  FIN:
  return(oserror ? -1 : lbuf);
}

/*=====================================================================*/
int osrputs(xid, buf)
/*+++
.PURPOSE Write an EOS-terminated message over the network, 
	adding the Carriage Return
.RETURNS length of written message / -1 (osmsg explains)
.REMARKS 
---*/
	int  xid;	/* IN: The connection number	*/
	char *buf;	/* IN: EOS-terminated Text to write	*/
{
	register char *p;
	register int l;

  l = strlen(buf);
  p = buf + l;
  *p = '\r';

  l = osrwrite(xid, buf, l+1);

  *p = EOS;		/* Reset the EOS */

  return(l);
}

