/* @(#)osa.c	19.1 (ES0-DMD) 02/25/03 13:56:11 */
/*===========================================================================
  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
.NAME        osa
.LANGUAGE    C
.AUTHOR      IPG-ESO Garching
.CATEGORY    Host operating system interfaces. Record i/o on disk files.
.ENVIRONMENT	VAX / VMS
.COMMENTS     Handling of RECORD io's. 
         All files are referenced
         by the physical name in the open function. A file identifier
	(number)
         is returned by open functions to be used in later i/o operations.
\begin{TeX}

	The routines return always a non-negative integer number on 
	successful return.
	Otherwise, a value of -1 is set to indicate an error
	condition or the End of File; the variable ``oserror'' 
	contains the numeric error code ($0$ for EOF), 
	which can be converted to a character pointer via osmsg routine.

Access to permanent (terminal) files ia made via QIO's. 
\end{TeX}

.VERSION 0.0 	25-Aug-1986   Definition     J. D. Ponz
.VERSION 1.6 	22-Oct-1987   Written for VMS. Merge with UNIX.
				Tested on Ultrix. (F. Ochsenbein)
.VERSION 2.0 	13-Oct-1988   Compatibility.
.VERSION 2.1 	24-Aug-1990   Small simplifications. (F. Ochsenbein)
------------------------------------------------------------*/

#define DEBUG	0

#include <osdefos.h>
#include <osfile.h>
#include <macrogen.h>
#include <atype.h>
#include <errno.h>	/* System	*/
#include <iodef.h>	/* System	*/
#	define fab$l_fna	name
#	define fab$b_fns	lname
#	define rab$l_ctx	last_op		/* 1 if $FIND */
#	define fab$l_ctx	file_no
#include <rms.h>	/* System	*/
#include <ssdef.h>	/* System	*/
#include <devdef.h>	/* System	*/

MID_EXTERN vmserror;		/* VMS-specific error code */
MID_EXTERN int oserror;
MID_EXTERN char *oserrmsg;

#define	FINISH		goto FIN

			/* Variables shared in local subroutines 	*/

static struct FAB  	*fap;
static struct XABFHC	*fhp;
static struct RAB *fp;
static int TheFile = 0;
	
static unsigned short	old_rfa[3];	/* record's file address */
static unsigned short 	chan;

#define pos_to_rfa(pos)		fp->rab$w_rfa[2] =     pos % fap->fab$w_bls,\
			*(long *)(fp->rab$w_rfa) = 1 + pos / fap->fab$w_bls
struct RAB *vmsrab();
long   vmsize();


		/****************************
		 * General Utility Routines *
	 	 ****************************/

/*==========================================================================*/
static int getfile(fid)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieves the file pointer.
.RETURNS Number of file; the static fp (file pointer) is also defined.
------------------------------------------------------------*/
	int   fid;			/* IN : file identifier */
{
  if (!(fp = vmsrab(fid))) 	FINISH;

  fap = fp->rab$l_fab;
  fhp = (struct XABFHC *)(fap->fab$l_xab);
  TheFile = fap->file_no;

  if (fp->rab$l_ctx == 0xffffffff)
  	chan = fp->rab$w_isi;
  else	chan = 0;

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

/*==========================================================================
	Routines dealing with VMS record-oriented i/o
 *==========================================================================*/

/*==========================================================================*/
static int vmsget(pbuf, nochar)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Interface to SYS$GET
.RETURNS Number of characters of the record / -1 for error
------------------------------------------------------------*/
	char *pbuf;			/* IN : pointer to i/o buffer */
	long nochar;	        	/* IN : number of i/o characters */
{
	register int len;
	short ba[3];

  fp->rab$l_ubf = pbuf, fp->rab$w_usz = nochar;	
  ba[0] = fp->rab$w_rfa[0], ba[1] = fp->rab$w_rfa[1], ba[2] = fp->rab$w_rfa[2];

  vmserror = SYS$GET(fp);
  fp->last_op &= ~1;	/* Last operation is NOT a find */

  switch(vmserror) {
    case RMS$_NORMAL:	len = fp->rab$w_rsz;	/* Length of read block	*/
	break;
    case RMS$_EOF:	len = -1;	fp->rab$b_rac = RAB$C_SEQ;    
			fp->rab$w_rfa[0] = ba[0], 
			fp->rab$w_rfa[1] = ba[1], 
			fp->rab$w_rfa[2] = ba[2];
    	break;
    case RMS$_RTB:	len = fp->rab$l_stv;	/* Actual Record Length */
	break;
    default:		len = -1;	oserror = EIO;	FINISH;
  }

  if (fp->rab$b_rac == RAB$C_RFA)	/* Set RFA to NEXT record	*/
  {	fp->rab$b_rac = RAB$C_SEQ;	/* Random access: Must modify RFA */
  	if (fap->fab$b_fac & FAB$M_PUT)
		if (SYS$FIND(fp) &1)	
			fp->rab$b_rac = RAB$C_RFA, 
			fp->last_op |= 1;	
  }	

  FIN:
  return(len);		/* Length of read block	*/
}
  
/*==========================================================================*/
static int toeof(pos)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Positions the file to END.
.RETURNS Upon successful completion, the file size
	measured in characters, or -1 on error.
.REMARKS The position is the one of the NEXT record.
.METHOD  SYS$FIND on sequential, then SYS$FIND on random access.
------------------------------------------------------------*/
	long pos;	/* IN: The EOF position	*/
{
  if ((fap->fab$b_fac & FAB$M_PUT) && ((fp->rab$l_rop & RAB$M_EOF) == 0))
  {	SYS$FLUSH(fp);
  	SYS$DISCONNECT(fp);
	fp->rab$l_rop |=  RAB$M_EOF;
	SYS$CONNECT(fp);
  	fp->rab$b_rac = RAB$C_SEQ;
  }
  else	fp->rab$b_rac = RAB$C_RFA;
  pos_to_rfa(pos);
  return(0);
}

/*==========================================================================*/
static long vmstell()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Computes the current byte position in the file.
.RETURNS Upon successful completion, the resulting pointer position
	measured in characters, or -1 on error.
.REMARKS The position is the one of the NEXT record.
.METHOD  SYS$FIND on sequential, then SYS$FIND on random access.
------------------------------------------------------------*/
{
	long pos;

  if (*(long *)(fp->rab$w_rfa) == 0)	return(0);

  if ((fp->rab$b_rac == RAB$C_SEQ) && !(fp->last_op & 1))
  {	vmserror = SYS$FIND(fp);	/* Tells position of NEXT record */
	if (vmserror&1)			/* Randomly resets the pointer	 */
		fp->last_op |= 1;
  }
  else 	vmserror = RMS$_NORMAL;

  switch(vmserror)
  { case RMS$_EOF:	pos = vmsize(TheFile);
  			pos_to_rfa(pos);
			break; 
    case RMS$_NORMAL:	pos = (*(long *)(fp->rab$w_rfa))-1;
			pos = pos * fap->fab$w_bls + fp->rab$w_rfa[2];
			break;
    default:		oserror = ESPIPE;
			break;
  }

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

/*==========================================================================*/
static int vmsput(pbuf, nochar)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Interface to SYS$PUT
.RETURNS Number of characters actually written.
	-1 is returned in case of error (error in oserror).
.REMARKS Use SYS$UPDATE service to write in the middle of a file...
------------------------------------------------------------*/
	char *pbuf;			/* IN : pointer to i/o buffer */
	long nochar;	        	/* IN : number of i/o characters */
{
	register int len;

#if DEBUG
	printf("rfm is %d, rat is %d\n", fap->fab$b_rfm, fap->fab$b_rat);
#endif

  len = nochar;

  if (fp->rab$b_rac == RAB$C_RFA)		/* Put within the file */
  {  	switch(vmserror = SYS$FIND(fp))
	{ case RMS$_EOF:	toeof(vmsize(TheFile));
	  	goto Append;
	  case RMS$_NORMAL:	
		break;
	  default: 		FINISH;
	}
	fp->rab$l_rbf = pbuf, fp->rab$w_rsz = nochar;
	vmserror = SYS$UPDATE(fp);
	if (vmserror&1)		/* This updates the RFA to NEXT record	*/
  	{	fp->rab$b_rac = RAB$C_SEQ;
		if (SYS$FIND(fp) &1)	
			fp->rab$b_rac = RAB$C_RFA, 
			fp->last_op |= 1;	
	}
	FINISH;
  }

  Append: 
  fp->rab$l_rbf = pbuf, fp->rab$w_rsz = nochar;
  vmserror = SYS$PUT(fp);

  FIN:
  if (!(vmserror&1))		oserror = EIO;
  return(oserror ? -1 : len);
}

/*==========================================================================*/
static long vmsseek(address, mode)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE moves read/write pointer to text file. The address defines
         the pointer position in characters relative to:
         start of the file, current position or end of the file as
         defined by the variable mode (FILE_START, FILE_CURRENT, FILE_END).
.RETURNS Upon successful completion, the resulting pointer position
	measured in characters is returned or -1 on error.
.REMARKS use of File Record Address
------------------------------------------------------------*/
	long address;			/* IN : offset to seek to  */
	int  mode;			/* IN : mode of addressing */
{
    register long pos;

  switch (mode) 
  { case FILE_CURRENT :		/* seek to file_current + address */
	pos = vmstell();
	if (address == 0)	FINISH;
	pos += address;		/* Absolute address, continue	*/
	break;
    case FILE_START : 		/* seek to file_start + address */
	pos = address;
	break;
    case FILE_END : 		/* seek to file_end + address 	*/
	pos = vmsize(TheFile) + address;
	break;
    default:
	oserror = EINVAL;
	break;
  }

  if (oserror)		FINISH;

	/* Convert now pos to RFA (Record File Address), and find this
	 * record ...	*/	

  pos_to_rfa(pos);
  fp->rab$b_rac = RAB$C_RFA;	/* First seek randomly... */
  vmserror = SYS$FIND(fp);
  if (vmserror & 1)	fp->last_op |= 1;	/* Last operation = $FIND */

  FIN:
  fp->rab$l_rop &= ~RAB$M_EOF;		/* We're not any more at EOF 	*/
	/* Note: RMS apparently provides an EOF message when
	 * the RFA is bad... Let's assume that we go to EOF.
	 */
  switch(vmserror)
  { case RMS$_EOF:	pos = vmsize(TheFile);
  			toeof(pos);
			break; 
    default:		oserror = ESPIPE;
			break;
    case RMS$_NORMAL:	
    case 0:		break;
  }  
  
  return( ( oserror ? -1 : pos));
}



		/************************
		 *	OSA Routines	*
		 ************************/


/*==========================================================================*/
int osaopen(phname, mode)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Opens disk file for read or write,
	and positions at file beginning (at end for APPEND mode).
         The variable `mode'
         defines the way of opening the file as READ_ONLY,
         WRITE_ONLY, READ_WRITE and APPEND. (see midas/osparms.h).
.RETURNS Upon successful completion a file descriptor is returned. -1 is
         returned on error.
.REMARKS File is opened with buffering. The mode is a combination of
	the following options (defined is osparms):
\begin{TeX}
\begin{itemize}
\item {\em READ  WRITE  READ\_WRITE  APPEND}
\item {\em NOATTR} (record without attributes, i.e. without linefeed)
\end{itemize}
	The routine {\em osfop}, if called before {\em osaopen},
	allows to create non STMLF--format files.
\end{TeX}
------------------------------------------------------------*/
	char *phname;	/* IN : physical filename */
	int  mode;	/* IN : open mode (see remarks) */
{
	register int i;
    
  i = vmsopen(phname, mode);	/* Call system-dependent part */

  if ((i > 0) && ((mode&3) == READ_WRITE))
  {	getfile(i);
	if (SYS$FIND(fp) &1)	
		fp->rab$b_rac = RAB$C_RFA, 
		fp->last_op |= 1;	
  }

  return( i);
}

/*==========================================================================*/
int osaclose(fid)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Closes a disk file.
         The argument fid is the file identification obtained from osdopen.
.RETURNS 0 (success) / -1 (failure)
.REMARKS 
------------------------------------------------------------*/
	int   fid;		/* IN : file identifier */
{

  oserror = 0;
  
  vmsclose(fid);

  return(oserror ? -1 : 0);
}

/*==========================================================================*/
int osawait(fid)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Force the physical writing on disk of modified buffers.
         The argument fid is the file identification obtained from osdopen.
.RETURNS 0 (success) / -1 (failure)
.REMARKS 
------------------------------------------------------------*/
	int   fid;			/* IN : file identifier */
{
  if (getfile(fid) < 0)		FINISH;
  if (chan)			FINISH;

  vmserror = SYS$FLUSH(fp);
  if (!(vmserror&1))		oserror = EIO;
  
  FIN:
  return(oserror ? -1 : 0);
}

/*==========================================================================*/
int osaread(fid, pbuf, nochar)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Synchronous RECORD read from disk file.
         The argument fid is the file identification obtained from osdopen.
         The routine reads 1 record from the current file
         position and stores up to nochar characters into the buffer 
         pointed by pbuf. If the buffer is too small, the remainder of the
	 record is IGNORED, BUT the function returns the actual record size.
	 The end-of-record character is never returned, but replaced
	 with the end-of-string character. The buffer returned
	 is always null terminated.
.RETURNS --- Number of characters actually read, excluding the end-of-string
	 terminator (e.g. 0 for an empty record). This  number may be 
	 larger than (nobuf-1) in case of truncation.
         --- -1 if an error is encountered or if the end of file is
	 reached. In case of error, oserror is set to the relevant error
	 number; oserror is set to 0 in case of EOF.
.REMARKS 
------------------------------------------------------------*/
	int    fid;			/* IN : file identifier */
	char   *pbuf;			/* OUT : pointer to i/o buffer */
	unsigned int    nochar;		/* IN : size of buffer */
{
	register    int len;

  len = 0;
  if (nochar < 2)		FINISH;

  if (getfile(fid) < 0)		FINISH;

  if (chan)	len = vmsrlb(chan, pbuf, nochar-1);
  else		len = vmsget(pbuf, nochar-1);	

  if (len < 0)	FINISH;
  *(pbuf + MIN(len,nochar-1)) = EOS;

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

/*==========================================================================*/
int osawrite(fid, pbuf, nochar)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Synchronous write to a file a string of characters followed
	by a newline (end of record).
         The argument fid is the file identification obtained from osdopen.
.RETURNS Number of characters actually written 
	-1 for error (oserror provides an explanation)
.REMARKS 
------------------------------------------------------------*/
	int  fid;		/* IN : file identifier */
	char *pbuf;		/* IN : EOS-terminated string to write */
	unsigned int  nochar;	/* IN : Number of bytes to write */
{
	register char *p;
	register int  i;

  if (getfile(fid) < 0)		FINISH;

  i  = nochar;	
  if (i == 0)			FINISH;

  p = pbuf + i - 1;			/* Suppress last linefeed */
  if (chan) {				/* Output via QIO	*/
  	i = vmsqio(chan, IO$_WRITELBLK, pbuf, i, 0);
  	if (*p == '\n')		i += vmsqio(chan, IO$_WRITELBLK, "\r", 1, 0);
  }
  else {
	if (*(p) == '\n')	i--;
	i = vmsput(pbuf, i);	
  }

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

/*==========================================================================*/
long osaseek(fid, address, mode)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Moves read/write pointer to a specified byte position.
	Bytes are numbered from 0 at the file beginning.
         The address is a displacement in BYTES from
	start of the file, current position or end of the file as
         defined by the variable mode (FILE_START, FILE_CURRENT, FILE_END).
         The argument fid is the file identification obtained from osdopen.
.RETURNS Upon successful completion, the resulting sector number;
	-1 on error (oserror explains the error).
.REMARKS 
------------------------------------------------------------*/
	int  fid;			/* IN : file identifier */
	long address;			/* IN : Move in bytes	*/
	int  mode;			/* IN : mode of addressing */
{
    register    int i;
    register    long pos;

  if (getfile(fid) < 0)		FINISH;

  if (chan)	oserror = ESPIPE;
  else		pos = vmsseek(address, mode);
  
  FIN:
  return(oserror ? -1 : pos);
}

/*==========================================================================*/
long osasize(fid)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Tells the size of an opened file without moving the file pointer.
.RETURNS Upon successful completion, the file size;
	-1 on error (oserror explains the error).
.REMARKS 
------------------------------------------------------------*/
	int  fid;			/* IN : file identifier */
{
  return(vmsize(fid));
}

/*==========================================================================*/
int osaunix(fid)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Tells if the opened file is Unix-compatible
.RETURNS 0 if non-unix, 1 if Unix, -1 on error (oserror explains the error).
.REMARKS VMS only
------------------------------------------------------------*/
	int  fid;			/* IN : file identifier */
{
	register int isunix;
	
  if (getfile(fid) < 0)		return(-1);

  return(fap->fab$b_rfm == FAB$C_STMLF ? 1 : 0);
}
