/*===========================================================================
  Copyright (C) 1995-2012 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 Massachusetts Ave, Cambridge, 
  MA 02139, USA.
 
  Correspondence 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		osu
.LANGUAGE	C
.AUTHOR		Francois Ochsenbein [ESO]
.CATEGORY	Interface to i/o on devices like tapes / OD / etc
.ENVIRONMENT	VAX / VMS or Unix
.COMMENTS	
	The functions of this module perform basic i/o to/from magnetic tape
	and optical disks 
	on VAX/VMS environment. The maximum number of available special
	units is assumed to be MAXDEV (defined here)
\begin{TeX}
\\
{\bf Some Troubleshooting}: With TD interface, the error 
	``Invalid Buffer Length"
	arises when the read/write buffer is not aligned at a 4-byte
	address...

{\bf Restrictions}: read/write on (optical) disks is limited to
	multiple of sectors.

A file, called DEVCAPFILE, is used to describe the various parameters of the
device(s). Each device is described in this file, 
as {\tt node\_name{\bf!}device\_name} where {\tt device\_name}
is the {\em physical} name of the device.
Properties are described as {\tt pp}$=${value}, where {\tt pp} is
a 2-letter code specifying the item. 
The most important items in this file are:
\begin{itemize}
\item 	{\tt bs}$=$ blocksize (in bytes)
\item 	{\tt ss}$=$ sector size (in bytes)
\item 	{\tt us}$=$ Unit size in sectors
\item 	{\tt ds}$=$ density (concerns tapes only)
\item 	{\tt dc}$=$ Reference to another device in the DEVCAPFILE
\item 	{\tt cl}$=$ Device Class Name. This Name specifies the iodev class
	which must be used to access the lower-level io functions.
\item 	{\tt bw}$=$ 0 if Backwards Movements are NOT available.

\end{itemize}

\end{TeX}
.VERSION	1.0	10-Mar-1986: Creation
.VERSION	1.1	21-Mar-1986 : Modified io1 and io2 for direct-access
		devices.
.VERSION	1.2	17-Dec-1986: New FCB definition
.VERSION	1.3	21-May-1987: Suppress warning message
.VERSION	2.0	24-Feb-1988: Integrated as osu routines.
			Tested on Gigadisk, Maxtor
.VERSION	3.0	03-Agu-1988: Implementation in mVAX-Ultrix. C Guirao. 
.VERSION	3.1	28-Nov-1988: Implementation in VAX/VMS. C Guirao. 
.VERSION	3.2	28-Nov-1988: Using MAXIO1 record. C Guirao. 
.VERSION	3.3	05-May-1989: Forcing "tmove()" reading to check double 
                        file mark. Translating WRITE in READ_WRITE in open mode,
                        and checking the proper mode in osuread. C Guirao.
.VERSION	3.4	18-Jul-1989: Devices are described in a file named $DEVCAPFILE
			Added osubsize to get blocksize
.VERSION 4.0	891012: Use of external functions.
.VERSION 4.1	891027: Be sure that DEVCAPFILE exists.
				Added tm (number of tm for End of Tape)
.VERSION 5.0	910919: When an error writing, the keep osermsg and oserror
.VERSION 6.0	920217: Separator between hostname & devicename also ':'
.VERSION 7.0	920625: Allowing multi-skip operations tmove() & bmove().CG
.VERSION 7.1	920821: osuopen and block_no == 0, we are after a FM. CG.

 120420		last modif
-----------------------------------------------------------------------------*/

#define NO_TAPE_REMOTE
#ifdef NO_TAPE_REMOTE 		/* IODEV0: First "iodevg" in the list */
#define IODEV0	iodevg
#else
#define IODEV0	iodevr
#endif

#define MAXDEV		4	/* Maximal number of opened devices */

#include <osdefos.h>		/* Keywords definitions 	*/
#include <osudef.h>		/* Internal to osu / iodev	*/
#include <osnull.h>		/* Contains NULL_DEV definition	*/
#include <devstat.h>		/* Contains devstat structure 	*/
#include <filedef.h>		/* Contains READ, WRITE, etc...	*/
#include <stdlib.h>		/* Function definitions 	*/
#include <errno.h>		/* System error definitions 	*/
#include <oserror.h>		/* Midas error definitions 	*/
#include <atype.h>
#include <proto_os.h>		/* ANSI-C prototyping */

typedef int (*FCT)();

#define MAX(x,y)	((x) >= (y) ? (x):(y) )    /* Maximum */
#define MIN(x,y)	((x) <= (y) ? (x):(y) )    /* Minimum */
#define NULL_PTR(x)	(x *)0

#define BUFTEST 30*1024		/* The size of a test buffer 	*/
#define DEFAULT_DENSITY   1600	/* Density by default 		*/
#define DEFAULT_SECTORSIZE 512	/* Size of one disk sector	*/
#define START_DEV	   100	/* First Number			*/

	/* Definition of Functions */

#define ioinfo(f,b,fn,bn)	(*fcb->ops[U_INFO])(f,b,fn,bn)
#define ioopen(f,m,d)		(*fcb->ops[U_OPEN])(f,m,d)
#define ioclose(f,o)		(*fcb->ops[U_CLOSE])(f,o)
#define ioread(f,b,l)		(*fcb->ops[U_READ])(f,b,l) 
#define iowrite(f,b,l)		(*fcb->ops[U_WRITE])(f,b,l) 
#define iorew(f)		(*fcb->ops[U_REWIND])(f) 
#define ioeom(f)		(*fcb->ops[U_EOM])(f) 
#define ioweof(f)		(*fcb->ops[U_WEOF])(f,1)	 
#define iofsf(f,n)		(*fcb->ops[U_FMF])(f,n)	
#define iobsf(f,n)		(*fcb->ops[U_FMB])(f,n)	
#define iofsr(f,n)		(*fcb->ops[U_BMF])(f,n)	
#define iobsr(f,n)		(*fcb->ops[U_BMB])(f,n)	
#define iosread(f,s,ss,b,l)	(*fcb->ops[U_SREAD])(f,s,ss,b,l) 
#define ioswrite(f,s,ss,b,l)	(*fcb->ops[U_SWRITE])(f,s,ss,b,l)	


	/* This structure, created for each device, allows to know
	 	the current position on any device
	 */
typedef int (*FCT_PTR)();		/* Pointer to function */

typedef struct {			/* File Control Block		*/
	char *name;			/* PHYSICAL Device Name		*/
	char *klass;			/* 	    Device Class Name	*/
	unsigned char access;		/* Access mode 0/1/2/3		*/
#define FCB_AM		 16		/* 	Append Mode available	*/
#define FCB_BW		 32		/* 	Backwards available	*/
#define FCB_DA		 64		/* 	Direct Access (Seek)	*/
#define FCB_TM		128		/* 	Tape Mode (erase's end)	*/
	unsigned char status;		/* status flags			*/
#define FCB_EOM		16		/* 	Tape in EOM		*/
#define FCB_NOCOUNT	32		/* 	Unknown block number	*/
/*#define FCB_NOFEET	64*/		/* 	Unknown feet count	*/
	unsigned char last_op;		/* Last operation performed	*/
	int eotm;			/* Number of TM to write at end */
/*	long feet;*/			/* used tape length in feet/1000*/
	long usize;			/* bytes of file / total blocks	*/
	long block_no;			/* block position within file	*/
	int tape_marks;			/* Number of CONSECUTIVE TM 	*/
	int block_max;			/* Maximum blocksize (tape)	*/
	int block_min;			/* Minimum blocksize (tape)	*/
	int blocksize;			/* Size of one block		*/
	int sectorsize;			/* Size of one sector		*/
	int density;			/* Density (Tapes), sector size	*/
	int file_no;			/* number of file (for MT)	*/
	FCT_PTR ops[U_MAX+1];		/* Available Operations		*/
					/* --- System-dependant Part----*/
	int pfd;			/* Tape file descriptor in UNIX */
     } FCB;

#define tgap		(fcb->density == 6250? (1500/12) : (3000/12))	\
			/* Gap on tape due to the Tape-Mark	*/

#define BADCLASSmsg	"Class of Device unknown:"
static FCB *Units[MAXDEV] = {0,0,0,0};
static FCB *fcb;
static int ncb = START_DEV;	/* Number of opened units 		*/
static char hasbw;		/* 1 if Backward available		*/
static char hasam;		/* 1 if Append Mode available		*/
static struct { 
	char msg[sizeof(BADCLASSmsg)];
	char klass[20];
    } theclass = { BADCLASSmsg, "generic" };
static struct osustat stbuf;
static int  findclass_loop = 0;
static char *test_buffer;

#define MAX_findclass	50	/* Recursivity limit to find a device class */

long osufseek();

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

static int osuerror(op)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Set Error Message
.RETURNS -1
----------------------------------------------------------------------*/
int op;	/* IN: The operation to execute */
{
	static char msg[60] = "Function not available: ";
	register char *pm, *pl;
	static 	char *text[] = {
		/* U_INFO  */ "info",
    		/* U_OPEN  */ "open",
    		/* U_CLOSE */ "close",
    		/* U_READ  */ "read",
    		/* U_WRITE */ "write",
    		/* U_REWIND*/ "rewind",
    		/* U_SREAD */ "sector_read",
    		/* U_SWRITE*/ "sector_write",
    		/* U_WEOF  */ "write_EOF",	
    		/* U_FMF   */ "file_move_forward",
    		/* U_FMB   */ "file_move_backwards",
    		/* U_BMF   */ "block_move_forward",	
    		/* U_BMB   */ "block_move_backwards",
    		/* U_EOM   */ "to_EOMedia"
	};

  oserror = -1;
  oserrmsg = msg;
  pm = &msg[24];

  	/* Append the signification of the function */
  for(pl = text[op]; *pl; pl++) 	*(pm++) = *pl;
  *pm = '\0';

  return(-1);
}
	/* Declare here the various error functions */
static int err0()  { return(osuerror(0));}
static int err1()  { return(osuerror(1));}
static int err2()  { return(osuerror(2));}
static int err3()  { return(osuerror(3));}
static int err4()  { return(osuerror(4));}
static int err5()  { return(osuerror(5));}
static int err6()  { return(osuerror(6));}
static int err7()  { return(osuerror(7));}
static int err8()  { return(osuerror(8));}
static int err9()  { return(osuerror(9));}
static int err10() { return(osuerror(10));}
static int err11() { return(osuerror(11));}
static int err12() { return(osuerror(12));}
static int err13() { return(osuerror(13));}

static FCT err_fct[1+U_MAX] = {
	err0, err1, err2, err3, err4, err5, err6, err7, err8, err9,
	err10, err11, err12, err13};

static int uget(f)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieve the FCB structure
.RETURNS 0 (success) / -1 (failure)
.REMARKS 
----------------------------------------------------------------------*/
int f;		/* IN: Unit number	*/
{

	oserrmsg = NULL_PTR(char);
	oserror  = 0;
	fcb      = NULL_PTR(FCB);
  
	if ((f >= START_DEV) && (f <= ncb))
		fcb = Units[f-START_DEV];

	if (fcb == NULL_PTR(FCB)) {
		oserror = -1;
		oserrmsg = "Bad Unit Number";
		}

	return(oserror);
}

static int newfile()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Update fcb information when a new file starts
.RETURNS 0
.REMARKS For disks, should be called only at open...
----------------------------------------------------------------------*/
{
	fcb->status   &= ~(FCB_NOCOUNT); /* count number is correct	*/

	if (fcb->file_no < 0) {		/* Beginning of Tape		*/
		fcb->file_no	 = 0;	/* First File			*/
		}
					/* Increment the file counter	*/
	else fcb->file_no += fcb->tape_marks; 
		
	fcb->tape_marks= 0;		/* No tape-mark encountered	*/
	fcb->block_no  = 0;		/* block position within file	*/
	fcb->block_max = 0;		/* Maximum blocksize		*/
	fcb->block_min = 0;		/* Minimum blocksize		*/
	fcb->status &= ~FCB_EOM;

	return(0);
}

static int newpos(bytes)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Computes the new position on the device
.RETURNS 0
.REMARKS	
----------------------------------------------------------------------*/
int bytes;		/* IN: number of bytes read/ written	*/
{
	if (fcb->tape_marks)	newfile();

    	if (fcb->access & FCB_DA)	/* Direct Access */
	{	fcb->block_no 	+= (bytes/fcb->sectorsize);
		if (bytes%fcb->sectorsize)	fcb->block_no += 1;
	}
	else	if (fcb->access & FCB_TM) (fcb->block_no)++;
	

	fcb->block_max = MAX(fcb->block_max, bytes);
	fcb->block_min = MIN(fcb->block_min, bytes);
	if (fcb->block_min <= 0)	fcb->block_min = bytes;

	fcb->status &= ~FCB_EOM;
	return(0);
}

static int rewind_dev ()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Rewind the tape
.RETURNS 0 (success) / -1 (failure)
.REMARKS fcb updated.
----------------------------------------------------------------------*/
{
	iorew(fcb->pfd);
	if (oserror)	return(-1);

	fcb->last_op = U_REWIND;
	fcb->file_no = -1;
	newfile();
	return(0);
}

static int tfclose()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Write EOF (Single TM)
.RETURNS 0 (success) / -1 (failure)
.REMARKS
----------------------------------------------------------------------*/
{
    ioweof(fcb->pfd);
    if (oserror == 0) {
    	(fcb->tape_marks)++; 
	fcb->last_op = U_WEOF;
	fcb->status |= FCB_EOM;
	}
    
    return(oserror ? -1 : 0);
}

static int bmove(num)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Move Backwards num TM.
.RETURNS 0 (success) / -1 (failure)
.REMARKS After move, the tape is positionned just before a tape-mark.
----------------------------------------------------------------------*/
int num;		/* IN: Number of TM to move backward */
{
	if (fcb->tape_marks < num ) {	
	   fcb->file_no -= (num - fcb->tape_marks);
	   fcb->tape_marks = 0;
	   fcb->status |= (FCB_NOCOUNT);
	   }
	else	fcb->tape_marks -= num;
  
	if (fcb->file_no < 0)	
		rewind_dev(); 
	else	
		iobsf(fcb->pfd,num), fcb->last_op = U_FMB;

	fcb->status &= ~FCB_EOM;
	return(oserror ? -1 : 0);
}

static int tmove(num)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Move Forwards num TM.
.RETURNS 0 (success) / -1 (failure)
.REMARKS After move, the tape is positionned after a tape-mark.
----------------------------------------------------------------------*/
int num;		/* IN: Number of TM to move forward */
{
        register int l, lbuf;
	int w_error; char *w_msg;

	oserror = 0;
	if (num <= 0 || fcb->status & FCB_EOM) return(0);
	/* If a TM was previously detected, ensure a physical */
	/* reading to force the move */
	if (fcb->tape_marks) {
		lbuf = BUFTEST - (BUFTEST % fcb->blocksize);
		l = ioread(fcb->pfd, test_buffer, lbuf);
		oserror = 0;
		fcb->last_op = U_FMF;
		if (l == -1 || l == 0) { 	/* Generally End Of Media  */
		    if (fcb->eotm > 1) { 
		        (fcb->tape_marks)++; 
			bmove(1);
			}
		    fcb->status |= FCB_EOM;
		    return(0); 
		    }
		newfile();	/* Tape_marks reset to zero */
		}

	if (iofsf(fcb->pfd, num) == -1) {
		w_error= oserror; w_msg = oserrmsg;
		rewind_dev();
		oserror = w_error; oserrmsg = w_msg;
		return(-1);
		}
	fcb->file_no += (num - 1);
	fcb->last_op = U_FMF;
	fcb->status |= (FCB_NOCOUNT);
	fcb->tape_marks = 1;
	return(0);
}

static struct iolist *findclass(aclass)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieve the class name
.RETURNS Pointer to found class / NULL
.REMARKS 
----------------------------------------------------------------------*/
	char	*aclass;	/* IN: Class to look for */
{
	struct iolist *plist, *IODEV0();
	struct iolist *(*def)();
	char	*p, *q;
	
	/* Follow the linked list of iolist's */

   if (++findclass_loop > MAX_findclass)
	return (NULL_PTR(struct iolist));

   plist = (*IODEV0)();
   for (def = IODEV0; def; def = plist->next)
   {
	plist = (*def)();
	for(q=aclass, p=plist->klass; (*p == *q) && (*p); p++, q++) ;
	if ((*q == '\0') && (*p == '\0'))	break;
   }
   if (!def)	/* Class Not Found */
   	plist = NULL_PTR(struct iolist);
   
   return(plist);
}

static int getclass()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieve the class name, to retrieve operators.
.RETURNS 0 / -1 (class doesn't exist)
.REMARKS Functions added in FCB
----------------------------------------------------------------------*/
{
	struct 	iolist *plist;
	int	i;
	OPITEM	*pop;
	
   findclass_loop = 0;

   plist = findclass(theclass.klass);
   if (!plist)	{
   	oserror = -1, oserrmsg = (char *)&theclass;
	theclass.msg[sizeof(theclass.msg)-1] = ' ';
   	return(-1);
   }
   
   fcb->klass = plist->klass;

   /*
   ** We have just to insert the functions in the list
   */

   for (pop = plist->oplist, i = plist->nop; --i >= 0; pop++)
   	fcb->ops[pop->opid] = pop->opf;
   
   return(0);
}

static int scandev(fd, dev)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Get Capabilities in opened DEVCAPFILE
.RETURNS 0  / -1
.REMARKS Recursive call if `dc=' is found.
----------------------------------------------------------------------*/
	int	fd;	/* IN: DEVCAPFILE file descriptor */
	char	*dev;	/* IN: Device name */
{
	int	i;
	char	buf[133];	/* One line of DEVCAPFILE */
	char	*p, *q;

	/* Be sure dev is terminated with Null */

  for (p = dev; isgraph(*p); p++)	;
  *p = '\0';

  osaseek(fd, 0L, FILE_START);
  while (osaread(fd, buf, sizeof(buf)) >= 0)
  {
	if (buf[0] == '#')	continue;
	for (p = buf, q = dev; *q && (*p == *q); p++, q++)	;
	if (*q == '\0' && (*p == ' ' || *p == '\t'))	goto found_dev;
  }
  return(-1); 


  found_dev:
	/* Here when device is found. Get interesting items, i.e.
	 * bs= blocksize
	 * us= unit size 
	 * ss= sector size 
	 * ds= density
	 * cl= class_name
	 * dc= Synonym
	 */
  while(*p)
  {	
  	while (isspace(*p))	p++;	/* Skip blanks 	*/
	if(!*p)	continue;		/* Empty line	*/

	if (*p == '\\')		/* There is a continuation. Read Next record */
	{
		next_record:
  		if (osaread(fd, buf, sizeof(buf)) < 0)	buf[0] = '\0';
		if (buf[0] == '#')	/* It's a comment */
			goto next_record;	
		p = buf;
  		if (isspace(buf[0]))	continue;
  		goto terminated;
	}
	if (oscomp(p, "dc=", 3) == 0)		/* Fetch another name */
		return(scandev(fd, p+3));
	if (oscomp(p, "bs=", 3) == 0) 		/* Blocksize	*/
		{ if (!fcb->blocksize)	fcb->blocksize = atoi(p+3); }
	else if (oscomp(p, "us=", 3) == 0) 	/* Unit Size	*/
		{ if (!fcb->usize)	fcb->usize = atoi(p+3); }
	else if (oscomp(p, "ss=", 3) == 0) 	/* Sector Size	*/
		{ if (!fcb->sectorsize)	fcb->sectorsize = atoi(p+3); }
	else if (oscomp(p, "ds=", 3) == 0) 	/* Density	*/
		{ if (!fcb->density)	fcb->density = atoi(p+3); }
	else if (oscomp(p, "tm=", 3) == 0) 	/* Number of tm	*/
		{ if (!fcb->eotm)	fcb->eotm = atoi(p+3); }
	else if (oscomp(p, "am=", 3) == 0) 	/* BackWard	*/
		{ if (hasam == 2)	hasam = atoi(p+3); }
	else if (oscomp(p, "bw=", 3) == 0) 	/* BackWard	*/
		{ if (hasbw == 2)	hasbw = atoi(p+3); }
	else if (oscomp(p, "cl=", 3) == 0) 	/* Class	*/
		{ for (i=0, p+=3; (i<sizeof(theclass.klass)-1)
		  	&&(isgraph(*p)); i++, p++) theclass.klass[i] =  *p; 
		  theclass.klass[i] = '\0';
		}
	while (isgraph(*p))	p++;		/* Look for space */
  }

  terminated:
  return(0);
}


static int getdev(dev)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Get Capabilities from DEVCAPFILE.
.RETURNS 0 / -1
.REMARKS Relevant Cap's are used to update the current fcb
----------------------------------------------------------------------*/
	char	*dev;	/* IN: Device name */
{
	char	*devcapfile;	/* DEVCAPFILE name */
	int	fd;
	char	*oshenv();

	/* Check if a DEVCAPFILE does exist	*/

  devcapfile = oshenv("DEVCAPFILE", NULL_PTR(char));
  if(!devcapfile){ 
	oserror = -1; oserrmsg = "DEVCAPFILE is unknown...";
	return(-1);
  }
  
	/* It does. Open in read_only mode	*/

  fd = osaopen(devcapfile, READ);
  if (fd < 0){
	oserror = -1; oserrmsg = "Cannot open file DEVCAPFILE";
	return(-1);
  }
  
	/* Scan the file for the device name, expand dc= references */
	 
  if (scandev(fd, dev) < 0) {
	oserror = -1; oserrmsg = "Entry unknown in DEVCAPFILE";
	return(-1);
  }

	/* Finally Close DEVCAPFILE	*/

  osaclose(fd);
  return(0);
}

/*=====================================================================
		Callable Routines
 *=====================================================================*/

int osugrep (class_name, item)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Get the item function in a class of devices
.RETURNS 0 / -1
----------------------------------------------------------------------*/
	char	*class_name;	/* IN: Class of Devices (NULL for any) */
	OPITEM	*item;		/* OUT: op_code + Function Pointer */
{ 
	struct iolist *plist, *IODEV0();
	struct iolist *(*def)();
	int	i;
	OPITEM	*pop;
	
   			
   plist = (*IODEV0)();
   for (def = IODEV0; def; def = plist->next)
   {
   	if (!class_name)	/* Class not specified: look to next one */
   		plist = (*def)();
   	else	plist = findclass(class_name);
   	if (!plist)	   	return(-1);

		/* Retrieve the item in list */
	
   	for (pop = plist->oplist, i = plist->nop; 
		(--i >= 0) && (pop->opid != item->opid);  pop++)  ;

	if (i >= 0)		/* I found the relevant item... */
	{ 	item->opf = pop->opf;
		return(0);
	}
   	if (class_name)		break;
	
   }
   return(-1);
}

char *osuname(f)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieve name of an opened unit (current when f = -1)
.RETURNS Name / NULL pointer if failed
----------------------------------------------------------------------*/
	int f;		/* IN: The unit number	*/
{ 
    if (f != -1)
	if (uget(f))		
		return((char *)0);	/* Bad unit number...	*/

    return(fcb ? fcb->name : NULL_PTR(char));
}

int osumode(f)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Retrieve opening mode
.RETURNS Mode / -1 when error
----------------------------------------------------------------------*/
	int f;		/* IN: The unit number	*/
{ 
    if (f != -1)
	if (uget(f))		
		return(-1);	/* Bad unit number...	*/

    return(fcb ? fcb->access & 3 : -1);
}

int osubsize(f)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Get the block size of the unit.
.RETURNS The size of blocks for block devices (1 for tapes)
----------------------------------------------------------------------*/
int f;			/* IN: The unit number	*/
{ 
    if (f != -1)
	if (uget(f))		
		return(-1);	/* Bad unit number...	*/

    return(fcb ? fcb->blocksize : -1);
}

int osustatus(f, stat)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Get the status of the device
.RETURNS 0 / -1
----------------------------------------------------------------------*/
int f;			/* IN: The unit number	*/
struct devstat *stat;	/* OUT: Filled with known info */
{ 
  if (uget(f))		
	return(-1);	/* Bad unit number...	*/

  stat->name 	   = fcb->name;
  stat->klass	   = fcb->klass;
  stat->density    = fcb->density;
  stat->sectorsize = fcb->sectorsize;
  stat->usize 	   = fcb->usize;
  stat->blocksize  = fcb->blocksize;
  stat->file_no	   = fcb->file_no;
  stat->block_no   = fcb->block_no;
  stat->block_max  = fcb->block_max;
  stat->block_min  = fcb->block_min;
  stat->tape_marks = fcb->tape_marks;

  return(0);
}

int osuopen (device, mode, den)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Assign (mount) a tape with a specified density
.RETURNS The device number / -1 on error
.REMARKS The default device class is "generic".
	 The mount is performed here.
	 If density is not specified or is zero, use DEFAULT_DENSITY 
	 for write-only; get the density otherwise.
.REMARKS Open the device in APPEND mode does not work properly (it is
	 like READ mode instead). It has been implemented changing the
	 open mode to READ_WRITE and positioning the tape between the last
	 two file marks.
----------------------------------------------------------------------*/
char *device;	/* IN: The unit name, blank or NULL for null device	*/
int  mode;	/* IN: open mode: READ, WRITE, READ_WRITE, APPEND */
int  den;	/* IN: The density or sector size; 0 if default */
{
	register int l, i, no_host;
	register char *p, *q;
	char	*devname, isnulldev;
	static char devhost[64], localhost[64];
	static char host_dev[128];

	/* Convert NULL device name if necessary */
	devname = device;
	if (!devname)	devname = "";
	isnulldev = !*devname;		/* Is zero for NULL device */
	if (isnulldev)	devname = NULL_DEV;

	oserror = 0;
	oserrmsg = NULL_PTR(char);
	fcb = NULL_PTR(FCB);
	hasam  = 2;		/* Unknown Parameter */
	hasbw  = 2;		/* Unknown Parameter */


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

	for (i = 0; i < MAXDEV; i++)  
		if (!Units[i])	break;

	if (i >= MAXDEV) {	
		oserror = -1;
		oserrmsg = "Too many opened osu Units" ;
		return(-1);
		}

	/* Allocate FCB, and Try to guess the device class from name:
		if device name contains a D, assume "disk" class
	 */
  
	l = strlen(devname);
	fcb = (FCB *)osmmget(sizeof(FCB) + l + 2); 
						/* Note: 2 for EOS and ':' */
	if (!fcb)	
		return(-1);

	if (isnulldev)	p = "dumb";
	else {		/* Look for a `d' for Disk, eitherwise Tape */
		q = devname + oscbloc(devname, l, '/') + 1;
		l -= (q - devname);	/* Keep filename */
		if ((oscbloc(q, l, 'd') >= 0 ) || (oscbloc(q, l, 'D') >= 0))
			p = "disk";
		else	p = "generic";
  	}
	oscopy(theclass.klass, p, 1+strlen(p));

	/* Initialize the structure, and copy device name */

	fcb->name 	  = (char *)(fcb+1);
	fcb->access 	  = mode;
	fcb->status	  = 0;
	fcb->eotm	  = 0;
	fcb->usize        = 0;
	fcb->tape_marks   = 0;
	fcb->block_max    = 0;		/* Maximum blocksize		*/
	fcb->block_min    = 0;		/* Minimum blocksize		*/
	fcb->blocksize	  = 0;
	fcb->sectorsize	  = 0;
	fcb->density 	  = den;
	fcb->file_no 	  = -1;
	for (l = 0; l < sizeof(fcb->ops)/sizeof(fcb->ops[0]); l++)
		fcb->ops[l] = err_fct[l];

	no_host=0;
	for (q = devhost, p = devname; (*p) && (*p != '!') &&  (*p != ':'); p++, q++)	*q= *p;
	if (*p == '!' || *p == ':' )  p++;
	else { p = devname; no_host=1; }
	for (q = fcb->name; *p; p++, q++)	*q = *p;
	*q = '\0';

	gethostname(localhost,64);

	/* 
      	 * Fill fcb with Info from DEVCAPFILE 
	 * Copy device name to fcp->name , but strip hostname info.
	 * If hostname not indicated and not found, then add localhost and
  	 * try again.
 	 */
	if (no_host) { 
	  if ( getdev(devname) < 0 ) {
	    for (q = host_dev, p = localhost; (*p); p++, q++) *q= *p;
	    *q = ':';
	    for (q++, p = devname; (*p); p++, q++) *q= *p;
	    *q = '\0';
/* In OSF/1 and Solaris the DAT device behaves like a 2 tm's for End of Tape */
#if !defined(__osf__) && !defined(__SVR4) && !defined(__svr4__)
            if ( getdev(host_dev) < 0 ) fcb->eotm = 1;
#else
            getdev(host_dev);
#endif
            }
          }
	else
#if !defined(__osf__) && !defined(__SVR4)
            if ( getdev(devname) < 0 ) fcb->eotm = 1;
#else
            getdev(devname);
#endif

	/* Check if it is a remote host, so class="remote" */
	if ( !no_host && osccomp(localhost,devhost,strlen(localhost)) ) {
		p = "remote";
		oscopy(theclass.klass, p, 1+strlen(p));
		oscopy(fcb->name, devname, 1+strlen(devname));
		}	

	if (fcb->eotm == 0)	/* Default = 2 TM		*/
		fcb->eotm = 2;
	if (hasam == 2)
		hasam = (fcb->eotm < 2);
	if (fcb->density <= 0)	
		fcb->density = DEFAULT_DENSITY;
	if (fcb->blocksize <= 0)	
		fcb->blocksize = 1;	/* Tapes have this blocksize... */

		/* Find to which Class the Device belongs to.  */

	if (getclass() < 0)	goto error_found;

		/* Open the device. DON'T REWIND ! */

	fcb->pfd = ioopen(fcb->name, 
		((mode == WRITE) ? READ_WRITE : mode), 
		fcb->density);
	if (oserror) goto error_found;

	Units[i] = fcb;
  	i += START_DEV;		/* Actual returned unit number	*/
	if (i >= ncb)	ncb = i;
	fcb->last_op = U_OPEN;		/* Last Operation */

		/* Get Other Info about Device */
		/* If ioinfo does not work drop eom jumps */
	ioinfo(fcb->pfd, &stbuf, &fcb->file_no, &fcb->block_no);
	if (fcb->file_no == -1) hasam=0;
		/* If 2 FM drop the FCB_AM. It does not work properly */
	/*if (fcb->eotm > 1) hasam=0;*/  
	if (isnulldev)	oserror = 0;	/* This can't be a problem	*/

		/* Fill the missing items with final info */

	if (fcb->ops[U_EOM] == osuerror)  hasam = 0;
	if (stbuf.density)	fcb->density = stbuf.density;
	if (hasbw)		fcb->access |= FCB_BW;
	if (hasam)		fcb->access |= FCB_AM;
	
		/* Take defaults for DA devices */
	if (stbuf.isda)
	{	fcb->access |= FCB_DA;
		if (stbuf.blocksize == 0)
			stbuf.blocksize = DEFAULT_SECTORSIZE;
		if (fcb->sectorsize == 0) 
			fcb->sectorsize = den;
		if (fcb->sectorsize < stbuf.blocksize)
			fcb->sectorsize = stbuf.blocksize;
		if (fcb->usize == 0)	fcb->usize = 
			stbuf.usize/(fcb->sectorsize/stbuf.blocksize);
		if (fcb->usize == 0)	fcb->usize = 0x7fffffff/fcb->sectorsize;
		if (fcb->blocksize <= 1)	
			fcb->blocksize = fcb->sectorsize;
	}
	if (fcb->usize == 0)	fcb->usize = stbuf.usize;
	if (stbuf.istm)		fcb->access |= FCB_TM;

	/* 
	 * If the system doesn't know the position, rewind anyway.
         */
	if (fcb->file_no < 0)	{
		iorew(fcb->pfd);
		newfile();
	}

	/*
	* Allocate test buffer 
	*/
	if (!(test_buffer=(char *)osmmget(BUFTEST))) return(-1);

	/* block_no == 0, we are at the beggining of a file */
	if (fcb->block_no == 0) {
		fcb->file_no -= 1;
		fcb->tape_marks = 1;
	}
	/*
	 * Check position. To be sure we are not between 2 TM 
	 * or End_Of_Media, jump backwards 2 TM if possible.
	if (fcb->file_no > 0 && fcb->access & FCB_BW) bmove(2);
 	*/
	oserror=0;

		/* Position to EOF if Append	*/

	fcb->last_op = U_OPEN;		/* Last Operation */
        if ((mode&3) == APPEND) osufseek(i, 0, FILE_END);

	if (oserror) {
		Units[i-START_DEV] = NULL_PTR(FCB);
		goto error_found;
		}
	return(i);

  error_found:
	osmmfree((char *)fcb);
	osmmfree(test_buffer);
	return(-1);
}

int osuclose (f, option)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE	Close (dismount) a tape / optical disk
.RETURNS	0 / -1
.REMARKS	Tape is not rewinded...
----------------------------------------------------------------------*/
int f;		/* IN: The unit to dismount	*/
int option;	/* IN: option 1 to dismount the device (e.g. spin-down) */
{
	if (uget(f)) 
		return(-1);	/* Bad unit number...	*/
  
	/* Perform necessary checks. Write tape marks if previous operation
	** was write or fclose.
	*/

	if (fcb->access & FCB_DA) ;
	else {
	   if (fcb->last_op == U_WRITE) tfclose();
	   if (fcb->last_op == U_WEOF)
		while (fcb->tape_marks < fcb->eotm) tfclose();
	   }

	if(oserror)		return(-1);

	if (ioclose(fcb->pfd, (option == 1 ? OPU_DISMOUNT : 0)))
		return(-1);

	osmmfree((char *)fcb);
	osmmfree(test_buffer);
	Units[f-START_DEV] = NULL_PTR(FCB);

	return(0);
}

int osuread (f, buffer, length)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Read a block from magnetic tape / optical disk.
.RETURNS Bytes read (>length if buffer too small) / 0 for EOF / -1 for
	 error; omsg provides an explanation.
.REMARKS The length MUST be a multiple of a sector size for
	 optical disks.
	 The returned length may be zero for virgin sectors.
.REMARKS Reading consecutive file marks, it is posible to go further 
	 than two file marks.
----------------------------------------------------------------------*/
int 	f;		/* IN: The file control block	*/
char	*buffer;	/* OUT: The buffer			*/
int	length;		/* IN: The length of the buffer	*/
{ 
	register int l;

	/* Check Buffer Length	*/
	if (length < sizeof(long)) {
		oserror = -1;
		oserrmsg = "Too small buffer";
		}

	if (uget(f))		/* Bad unit number...	*/
		return(-1);
 
	/*
	** If device was opened in only_write mode, return error
	** if trying to read something
	*/

	if ((fcb->access & 3) == WRITE) {
		oserror=EACCES;
		return(-1);
		}
	/*
	** Check if some required blocksize MUST be used.
	*/

	if (length%fcb->blocksize)	
	{	oserror = -1, oserrmsg = "Length not a multiple of Blocksize";
	  	return(-1);
	}

	/*
	** 	For Tapes, check for illegal read
	*/
	
	if (fcb->access & FCB_TM)
	{	if ((fcb->last_op == U_WRITE) || (fcb->last_op == U_WEOF))
		{	oserror = -1;
			oserrmsg = "Can't read after write";
	  		return(-1);
		}
	}
	
	/*
	** For Direct-Access devices, use iosread (0 means "virgin sector")
	** For Tapes, use ioread                  (0 means "tape-mark"    )
	*/
	if (fcb->access & FCB_DA)
		l = iosread(fcb->pfd, fcb->block_no, fcb->sectorsize,
			buffer, length);
	else	{
		l = ioread(fcb->pfd,buffer,length);
		if (l == 0) {
		    if (fcb->tape_marks && fcb->eotm > 1) {
                            (fcb->tape_marks)++;
                            bmove(1);
                            fcb->status |= FCB_EOM;
			    oserror = -1;
			    oserrmsg = "End Of Data.";
			    return(-1);
                            }
		    else (fcb->tape_marks)++;
		    }
	}

	/* Check special case oserror = -2, means TOO SHORT BUFFER */
	if (oserror == -2) {
	  oserror = -1;
	  oserrmsg = "Too short buffer";
	  newpos(length);
	  return(-1);
	}
	/* 
	** If error, but last read was a FileMark then 
	** set EOM flag (End Of Media) and return -1.
	*/
	if (oserror) {
	  if (fcb->tape_marks) {
            fcb->status |= FCB_EOM;
	    oserror = -1;
            oserrmsg = "End Of Data.";
            }
	  return(-1);
	}

	fcb->last_op = U_READ;

	if (l > 0) 		/* Something was read, update fcb contents */
		newpos(l);

	return(l);
}

int osuwrite (f, buffer, length)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Write a block on magnetic tape / optical disk.
.RETURNS Length written / -1 (error)
.REMARKS 
----------------------------------------------------------------------*/
int 	f;		/* IN: The file control block	*/
char	*buffer;	/* IN: The buffer		*/
int	length;		/* IN: The length of the buffer	*/
{ 
	register int l;
	char *w_msg;
	int w_error;

	/* Check Buffer Length	*/
	if (length < sizeof(long)) {
		oserror = -1;
		oserrmsg = "Too small buffer";
		}

	if (uget(f))		/* Bad unit number...	*/
		return(-1);
  
	/* Check if Write is Allowed */

	if ((fcb->access & 3) == READ) { 
		oserror = EACCES; 
		return(-1); 
		}

	/*
	** Check if some required blocksize MUST be used.
	*/

	if (length%fcb->blocksize)	
	{	oserror = -1, oserrmsg = "Length not a multiple of Blocksize";
	  	return(-1);
	}

	/*
	** 	For Tapes, check if 2 TM were previously written; 
	**	     if yes, the data will not be accessible... 
	*/
	if (fcb->access & FCB_TM)
	{	if (fcb->tape_marks >= 2) {
			oserror = -1, oserrmsg = "Attempt to write beyond EOF";
	  		return(-1); 
	  	}
	}
	
	/*
	** For Direct-Access devices, use ioswrite .
	*/
	if (fcb->access & FCB_DA)
		l = ioswrite(fcb->pfd, fcb->block_no, fcb->sectorsize,
			buffer, length);
	else	l = iowrite(fcb->pfd,buffer,length);

	/*
	** If Error when writing, the tape is positioned at the
	** beginning of the file, where it was opened.
	*/
	if (l == -1) {
		w_error= oserror; w_msg = oserrmsg;
		if (fcb->access & FCB_BW) {
		    bmove(1);
		    tmove(1);
		}
		oserror = w_error; oserrmsg = w_msg;
		return(-1);
                }

	if (l > 0)
		newpos(l);
	fcb->last_op = U_WRITE;
	return(l);
}

int osufclose (f)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Close a File (Write a Tape-Mark on the tape)
.RETURNS 0 / -1
.REMARKS Tape mouvements are optimized.
	 Does nothing if file opened for read-only (returns
	 wihout error)
----------------------------------------------------------------------*/
int f;		/* IN: The file control block	*/
{

	if (uget(f))		/* Bad unit number...	*/
		return(-1);

	/* Check if Write is Allowed */

	if ((fcb->access & 3) == READ)
		return(0);

        if (fcb->last_op == U_WRITE) return(tfclose());
        return(0);
}

long osufseek (f, offset, mode)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE File seek (Move the tape, i.e. find tape-marks).
	 Identical to osubseek for disks.
.RETURNS New file number
.REMARKS On return, the tape is always positioned at a
	 file beginning. The tape move is always stopped
	 when two consecutive tape-marks are encountered.
----------------------------------------------------------------------*/
int	f;		/* IN: Unit Number	*/
long offset;		/* IN : Offset for displacement	*/
int  mode;		/* IN : mode of addressing */
{
	register long fn;
	register int n, an, s1, s2;

	if (uget(f))		/* Bad unit number...	*/
		return(-1);

	/* 
	** Perform necessary checks. Can't move tape after write...
	*/

	if (fcb->access & FCB_TM)
	{	if (fcb->last_op == U_WRITE) {
			oserror = -1, oserrmsg = "Can't move after write";
	  		return(-1);
	  	}
	}
		
	n = offset;
	fn = 0;

	switch(mode) { 
	    case FILE_END: 
		if (n > 0) { oserror = ESPIPE; 	return(-1);  }

		if (fcb->last_op == U_WEOF) {	/* I just wrote a TM */
		    if ( (n == 0) && (fcb->tape_marks == 1))	break;
		    while (fcb->tape_marks < fcb->eotm) tfclose();
		    }

		/*
		* Use fast EOM function if we can know where we are.
		* If ioeom ERROR, rewind and drops the FCB_AM flag 
 		*/
		if ( !(fcb->status & FCB_EOM) && 
		    (((n == 0 ) && (fcb->access & FCB_AM)) || 
		    ((fcb->access & FCB_AM) && (fcb->access & FCB_BW))) ) {
		    s1=ioeom(fcb->pfd);
		    s2=ioinfo(fcb->pfd,&stbuf,&fcb->file_no,&fcb->block_no);
		    if (s1 == -1 || s2 == -1) {
	                oserror = 0; 
		        rewind_dev();
			fcb->access &= ~FCB_AM;	
			}
		    else {
                        if (fcb->file_no == 0)  rewind_dev();
			else {
			    fcb->file_no -= 1;
			    fcb->tape_marks = 1;
			    fcb->block_no = 0;
			    fcb->status |= FCB_EOM;
			    fcb->status &= ~FCB_NOCOUNT;
			    if (n == 0) break;
			    }
			}
		    }

		while ( !(fcb->status & FCB_EOM) && (oserror == 0))
		    tmove(1);		/* Find EOTape */

		if ( n == 0 ) break;

		n += fcb->file_no + 1;	
			/* n is now Absolute File Number where to go	*/
	  case FILE_START:
		if (n <= 0) {
		    if (fcb->last_op == U_WEOF)	/* I just wrote a TM */
			while (fcb->tape_marks < fcb->eotm) tfclose();
		    rewind_dev();
		    break;
		    }
		n -= (fcb->file_no + fcb->tape_marks); 	/* n is now Relative */
	  case FILE_CURRENT:
		an = n + fcb->file_no + fcb->tape_marks; /* Absolute File */
		if (an <= 0) {
			if (fcb->last_op == U_WEOF) 	/* I just wrote a TM */
			    while (fcb->tape_marks < fcb->eotm) tfclose();
			rewind_dev(); 
			break; 
			}
		if ( (n == 0) && (fcb->tape_marks)) /* I'm on a file beginning*/
			break;

		if (fcb->last_op == U_WEOF) 	/* I just wrote a TM */
		    while (fcb->tape_marks < fcb->eotm) { tfclose(); n--;}

		if (n <= 0) {	/* Move -n+1 backwards, 1 time forwards */
			if (fcb->access & FCB_BW) {
				bmove(-n+1);
				tmove(1);
				n = 0;	/* We're OK */
				}
			else	{	/* First Rewind, then Move Forw. */
				n = an; 	/* Where to go */
				rewind_dev();
				}
			}
		/* Move n tape-marks forwards */
		/* 
		 * CG. The idea to position one by one in devices with 2 FM was
		 * to avoid to jump after the last FileMark, something very 
		 * annoying particularly with 1/2" tapes. However this limit a 
		 * lot the fast positioning of DAT devices.
		if (fcb->eotm > 1) {
		    while ((--n >= 0) && !(fcb->status & FCB_EOM)) tmove(1);
		    if (n > 0)  oserrmsg="File positioning error", oserror = -1;
		    }
		else (tmove(n));		
		 */
		tmove(n);		
		break; 
	  default:
		oserror = -1;
		oserrmsg = "Bad skip mode";
		return(-1);
	}

	fn = fcb->file_no + fcb->tape_marks;

	return(oserror ? -1 : fn);
}

long osuftell (f)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Compute current file number
.RETURNS Current file number
----------------------------------------------------------------------*/
int	f;		/* IN: Unit Number	*/
{
	register long fn;

	if (uget(f))		/* Bad unit number...	*/
		return(-1);

	fn = fcb->file_no + fcb->tape_marks;

	return(fn);
}
