/* @(#)osu.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
.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	12-Oct-1989: Use of external functions.
.VERSION 4.1	27-Oct-1989: Be sure that DEVCAPFILE exists.
				Added tm (number of tm for End of Tape)
.VERSION 4.2	20-Dec-1989: Removed bug in scandev (F.O.)
			Added functions for Disks, and EOM (End-Of-Media).
.VERSION 4.3	11-Jan-1990: Added osustat. Added NULL devices. 
.VERSION 4.4	18-May-1990: 2 parameters for osuclose . 
			Loop check on findclass. FO
.VERSION 4.5	20-Nov-1990: osuclose properly handled for DA devices
.VERSION 4.6	23-Apr-1991: Corrected tmove (suppress error condition)

-----------------------------------------------------------------------------*/

#define MAXDEV		4	/* Maximal number of opened devices */
#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 <errno.h>
#include <computer.h>
#include <atype.h>

typedef int (*FCT)();
#include <osdefos.h>
MID_EXTERN int oserror;
MID_EXTERN char *oserrmsg;

#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 IO_MAX MAXIO1
#define BUFTEST 2048		/* The size of a test buffer 	*/
#define DEFAULT_DENSITY   1600	/* Density by default 		*/
#define DEFAULT_SECTORSIZE 512	/* Size of one disk sector	*/
#define DEFAULT_USIZE	 10240	/* Default number of sectors	*/
#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 ignore;		/* ignore flags			*/
#define FCB_NOCOUNT	32		/* 	Unknown block number	*/
#define FCB_NOFEET	64		/* 	Unknown feet count	*/
	unsigned char last_op;		/* Last operation performed	*/
	unsigned char	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, "mt" };
static struct osustat stbuf;
static int  findclass_loop = 0;
#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 long tfeet(blk)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Compute the length of a block on a magnetic tape
.RETURNS The length in feet/1000
.REMARKS Only for tapes!
----------------------------------------------------------------------*/
unsigned int blk;	/* IN: Size of the Block (bytes)	*/
{
	register long ctrl, dblk;

	dblk = blk*1000; 

	switch(fcb->density) {
	case    0: 	return(0);	/* Unknown density	*/
	case 6250: 			/* Formula for 6250bpi	*/
		ctrl = ((blk-1)/790 + 1) * 20000; 
		return((301 + (dblk+ctrl)/fcb->density)/12);
	default:			/* Formula for other densities	*/
		return(((dblk/fcb->density)+600)/12);
	}
}

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

	if (fcb->file_no < 0) {		/* Beginning of Tape		*/
		if (fcb->access & FCB_TM)
			fcb->ignore &= ~(FCB_NOFEET),
			fcb->feet= 0;	/* No length for this file	*/
		fcb->file_no	 = 0;	/* First File			*/
		}
	else 	{			/* Increment the file counter	*/
		fcb->file_no += fcb->tape_marks; 
		fcb->feet += fcb->tape_marks * tgap;
		}
	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		*/

	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->feet += tfeet(bytes);
		(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;

	return(0);
}

static int rewind ()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.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
----------------------------------------------------------------------*/
{
    if (oserror == 0)
    	ioweof(fcb->pfd);

    if (oserror == 0)
    	(fcb->tape_marks)++, fcb->last_op = U_WEOF;
    
    return(oserror ? -1 : 0);
}

static int tmove()
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Move Forwards 1 TM.
.RETURNS 0 (success) / -1 (failure)
.REMARKS After move, the tape is positionned after a tape-mark.
----------------------------------------------------------------------*/
{
	register int l, lbuf;

	char test_buffer[BUFTEST];

	/* 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);
		if (l == -1) l = lbuf;	/* Generally "too small buffer */
		fcb->last_op = U_FMF;
		if (l == 0) { 
			(fcb->tape_marks)++; 
			return(0); 
			}
		newfile();	/* Tape_marks reset to zero */
		}

	oserror = 0;
	if (iofsf(fcb->pfd, 1) == -1)		return(-1);

	fcb->ignore |= (FCB_NOFEET|FCB_NOCOUNT);

	fcb->last_op = U_FMF;
	fcb->tape_marks = 1;
	return(0);
}

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

	return(oserror ? -1 : 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, *iodev();
	struct iolist *(*def)();
	char	*p, *q;
	
	/* Follow the linked list of iolist's */

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

   for (def = iodev; 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')		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 = "Entry unknown in 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, *iodev();
	struct iolist *(*def)();
	int	i;
	OPITEM	*pop;
	
   			
   for (def = iodev; 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->feet 	   = fcb->feet;
  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 "mt".
	 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;
	register char *p, *q;
	char	*devname, isnulldev;

	/* 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 contrains 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 = "mt";
  	}
	oscopy(theclass.klass, p, 1+strlen(p));

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

	fcb->name 	  = (char *)(fcb+1);
	fcb->access 	  = mode;
	fcb->ignore	  = FCB_NOFEET;
	fcb->eotm	  = 0;
	fcb->feet	  = 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];

		/* Fill fcb with Info from DEVCAPFILE */
	l = getdev(devname);

		/* Copy device name, but strip hostname info.
		   However, if getdev returned with an error,
	           and the ! is present (i.e. DEVCAPFILE doesn't find
		   the entry), return with an error. 
                   Devices without the ! are assumed to be standard */

	for (p = devname; (*p) && (*p != '!'); p++)	;
	if (*p == '!')	{ if (l < 0) goto error_found; p++;}
	else		p = devname;
	for (q = fcb->name; *p; p++, q++)	*q = *p;
	*q = '\0';
	oserror = 0;		/* Clear error from getdev	*/

	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 */
	
	ioinfo(fcb->pfd, &stbuf, &fcb->file_no, &fcb->block_no);
	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 = DEFAULT_USIZE; 
		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.
         * Check position. If 2 TM indicate the End-Of-Tape, be sure that
         * we are not between, or after 2 TM , i.e. rewind.
        */

	if (fcb->file_no < 0)	{
		iorew(fcb->pfd);
		newfile();
	}

        if (fcb->file_no > 0 && fcb->eotm > 1 ) {
                bmove();
                bmove();
        }

		/* 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(fcb);
	return(-1);
}

int osuclose (f, option)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE	Close (dismount) a tape / optical disk
.RETURNS	0 / -1
.REMARKS	Tape is always rewinded...
----------------------------------------------------------------------*/
int f;		/* IN: The unit to close	*/
int option;	/* IN: Option 1 to dismount / spin down	*/
{
	if (uget(f)) 
		return(-1);	/* Bad unit number...	*/
  
	/* Perform necessary checks. Write tape marks is 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) && (fcb->tape_marks < fcb->eotm))
	 		tfclose();
	}

	if(oserror)		return(-1);

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

	osmmfree(fcb);
	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) 	fcb->tape_marks++;
	}

	/* Check special case oserror = -2, means TOO SHORT BUFFER */
	if (oserror == -2) {
		oserror = -1;
		oserrmsg = "Too short buffer";
		fcb->ignore  |= FCB_NOFEET;
		newpos(length);
	}

	if (oserror)
		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;

	/* Check Buffer Length	*/

	if ((length <= 0) || (length > IO_MAX))	{
		oserror = -1; 
		oserrmsg = "Bad buffer size";
		return(-1);
		}

	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 (l == -1)
		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);

	return(tfclose());
}

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;
        int fileno, blockno;

	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 < 2) && (oserror == 0))
			tfclose();
		    }
                ioinfo(fcb->pfd,&stbuf,&fileno,&blockno);
                if ( (n == 0) && (fcb->access & FCB_AM) && (blockno != -1) )
                        {
                        /* Use fast EOM function if we know where we are */
                        /* If ioeom ERROR, drops the FCB_AM flag */
                        if ( ioeom(fcb->pfd) == 0) {
                                ioinfo(fcb->pfd, &stbuf,&fcb->file_no,
                                        &fcb->block_no);
                                fcb->block_no = 0;
                                fcb->tape_marks = 0;
				fcb->ignore &= ~FCB_NOCOUNT;
                                if (fcb->file_no == 0)   rewind();
                                else    fcb->file_no -= 1,
                                        fcb->tape_marks = 1;
				break;
			}
                        else { fcb->access &= ~FCB_AM; oserror = 0; }
		    }
		while ((fcb->tape_marks < 2) && (oserror == 0))
			tmove();		/* Find EOTape */
                if ( (n == 0) && (fcb->access & FCB_AM) ) {
                        /* Use fast EOM function. We know now where EOM is */
                        iobsf(fcb->pfd,1);    /* Move backward */
                        if (ioeom(fcb->pfd) == 0)
                                fcb->tape_marks = 1;
                        else { fcb->access &= ~FCB_AM; oserror = 0; }
                        break;
                        }
		n += fcb->file_no + 1;	
			/* n is now Absolute File Number where to go	*/
	  case FILE_START:
		/* When I'm beyond the allowed number of TM indicating the
			end of the tape, it's better to rewind.
		*/
		if ((n <= 0) || (fcb->tape_marks > 1)) { 
			if (fcb->last_op == U_WEOF) 	/* I just wrote a TM */
				tfclose();
			rewind(); 
			if (n <= 0)	break; 
			}
		n -= (fcb->file_no + fcb->tape_marks); 	/* Relative */
			/* n is now Relative File */
	  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 */
				tfclose();
			rewind(); 
			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 */
			n--, tfclose();
		if (n <= 0) {	/* Move -n-1 backwards, 1 time forwards */
			if (fcb->access & FCB_BW) {
				while ((n++ <= 0) && (oserror == 0))	
					bmove();
				tmove();
				n = 0;	/* We're OK */
				}
			else	{	/* First Rewind, then Move Forw. */
				n = an; 	/* Where to go */
				rewind();
				}
			}
				/* Move n tape-marks forwards */
		while ((--n >=0) && (oserror==0) && (fcb->tape_marks <=1))
			tmove();
		break; 
	  default:
		oserror = -1;
		oserrmsg = "Bad open 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);
}

long osufeet (f)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Compute position in feet/1000
.RETURNS Current position / -1 if unknown
.REMARKS
----------------------------------------------------------------------*/
int	f;		/* IN: Unit Number	*/
{
	long ft;

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

	ft = (fcb->ignore & FCB_NOFEET ? -1 : fcb->feet);

	return(ft);
}

long osubseek (f, offset, mode)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Block seek: Skip blocks on tape, skip sectors on
	 optical disks.
.RETURNS New block number / -1 if error or unknown block number.
.REMARKS Seek is stopped by a tape-mark, BUT the tape-mark is skipped.
		Disk: Simply set the position.
----------------------------------------------------------------------*/
int	f;		/* IN: Unit Number	*/
long offset;		/* IN : Offset for displacement	*/
int  mode;		/* IN : mode of addressing */
{
	register long n, bn;
	int st;

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

    n = offset; 
    bn = 0; 

    if (fcb->access & FCB_DA)	/* Disk Seek 	*/
    {
  	switch(mode)
  	{ case FILE_CURRENT: 	n += fcb->block_no;
  	  case FILE_START: 	break;
  	  case FILE_END: 	n = fcb->usize + n;
  	  			break;
	}
	fcb->block_no = n;
	if ((n < 0) || (n >= fcb->usize))	oserror = ESPIPE, bn = -1;
	else	bn = fcb->block_no;

	return(bn);  
	
    }

	/* Perform necessary checks. Write tape marks if previous operation
	** was write or fclose.
	*/

    if (fcb->last_op == U_WRITE) {
	oserror = -1, oserrmsg = "Can't move after write";
  	return(-1);
    }
		
    if (fcb->last_op == U_WEOF)	if (tfclose() < 0) return(-1);

    switch(mode) {
	case FILE_END: 
		if (n > 0) { 
			oserror = ESPIPE; 
			return(-1); 
			}
		/*
		** Method: Move forwards, then backwards, then skip
		*/
  		while ((fcb->tape_marks == 0) && (oserror == 0)) tmove();
			bmove();
		break;
	case FILE_START:
		/* 
		** If we don't know where we are, better to go to beginning
		** of current file 
		*/
		if (fcb->ignore & FCB_NOCOUNT)	osufseek(f, 0, FILE_CURRENT);
	  	else 		n -= fcb->block_no;	/* Relative */
	  case FILE_CURRENT:
		break;
    }
    bn = (fcb->tape_marks ? 0 : 		/* We are here */
	(fcb->ignore & FCB_NOCOUNT ? -1 : fcb->block_no));

    if (oserror)
	return(-1);
    if (n == 0)
	return(bn);

	/* 
	** I just passed over a tape mark.
	** If Move forward, do it. 
	** Else move only 1 block!	
	*/
    if (fcb->tape_marks) {	
	if (n > 0) {
		newfile(); 
		fcb->ignore |= FCB_NOFEET;
		}
	else 	{
		n = -1;
		fcb->ignore |= FCB_NOCOUNT;
		}
	}
    else	fcb->ignore |= FCB_NOFEET; 

    if (n<0)
	st = iobsr(fcb->pfd,-n), fcb->last_op = U_BMB;

    else
	st = iofsr(fcb->pfd,n), fcb->last_op = U_BMF;

    if (oserror)	return(-1);

	/* 
	** Tape-mark was encountered
	*/
    if (st < 0) {		
	if (n < 0) {
		if (fcb->tape_marks)	(fcb->tape_marks)--;
		else if (--(fcb->file_no) < 0)	newfile();
		}
	else	fcb->tape_marks += 1;
	}
    else	{
	if (n > 0)	fcb->block_no += bn;
	else		fcb->block_no -= bn;
	}
    bn = (fcb->tape_marks ? 0 : 		/* We are here */
	(fcb->ignore & FCB_NOCOUNT ? -1 : fcb->block_no));

    return(bn);
}

long osubtell (f)
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.PURPOSE Compute current block / sector number
.RETURNS Current block number (in current file)
.REMARKS
----------------------------------------------------------------------*/
int	f;		/* IN: Unit Number	*/
{
	register long bn;

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

	bn = (fcb->tape_marks ? 0 : 		/* We are here */
		(fcb->ignore & FCB_NOCOUNT ? -1 : fcb->block_no));

	return(bn);
}
