/*
 *  Copy files from RSX-11 (ODS-1) filesystem.  Files may be copied to
 *  disk or to standard output.  Transfer modes supported are "text"
 *  (FCS stuff is thrown away, newline is tacked on the end of each
 *  RSX record), and "image" (straight byte-by-byte transfer).  There
 *  were once plans to support a third mode ("binary"), but this has
 *  not yet been implemented.  Defaults for the output destination and
 *  transfer mode are set by #defines, but the destination/mode can be
 *  specified at runtime by using various flags (see "options()").
 *
 *  The input device and UFD, if omitted, will default to that of the
 *  previous filespec.  Note that this means that the first filespec
 *  MUST have a UFD specified, and (if DFLTDEV is not defined) also a
 *  device as well.  The filename syntax is the same as the standard
 *  RSX naming scheme, except that a "." may be used to separate the
 *  filetype from the version number, and some delimiters may be changed
 *  via #defines, if desired.  (This is all to avoid the possibility of
 *  having to escape some of the characters that the shell treats as
 *  special.)  The device name is the name of the UNIX special file in
 *  /dev, rather than what RSX thinks it would be.
 *
 *  If the first character of argv[0] is "l", or if the "-l" option is
 *  used, the program lists the contents of the UFD rather than copying
 *  a file.  At present, only one UFD may be listed per command.
 *
 *  Written by Mark Bartelt.  Structs (plus routine putch()) are left over
 *  from an earlier attempt by Rob Pike.  Otherwise, this is a total rewrite.
 *
 *  Ported to RT-11 with DECUS C compiler, May 1983.  Various changes were
 *  necessary because:  (1) DECUS C does not permit initialization of either
 *  automatic variables or internal static variables.  (2) The old version
 *  of the DECUS C compiler had several bugs (compiler bomb-outs, and bad
 *  code generation), mostly having to do with typecasting to long (both
 *  explicit and implied).  These may have been fixed in later versions of
 *  the compiler (if so, the workarounds can go away), but I haven't had a
 *  chance to find out.  (3) Some obvious changes were necessary because
 *  of fundamental operating system differences.  NOTE: Since this program
 *  uses `#define' with arguments, it's necessary to use the separate `mp'
 *  preprocessor before compiling.
 */


#include <stdio.h>

#ifdef rt11
#define	isalnum(x)	( ( 'a'<=(x) && (x)<='z' ) || ( 'A'<=(x) && (x)<='Z' ) || ( '0'<=(x) && (x)<='9' ) )
#else
#include <ctype.h>
#endif

#define	isoctal(x)	( '0'<=(x) && (x)<='7' )

#define	err0(msg)	{ errmsg(msg); return(0); }
#define	err1(msg,arg)	{ errmsg(msg,arg); return(0); }

#ifdef rt11
typedef	unsigned	ushort;
#else
typedef	unsigned short	ushort;
#endif

struct filnam {
	ushort	f_nam[3];	/* File name (Radix-50) */
	ushort	f_typ;		/* File type (Radix-50) */
	ushort	f_ver;		/* Version number */
};

struct uic {
	char	u_prog;		/* Programmer number */
	char	u_proj;		/* Project number */
};

struct fileid {
	ushort	f_num;		/* File number */
	ushort	f_seq;		/* File sequence number (worthless concept) */
	ushort	f_rvn;		/* Relative volume number (ditto and MBZ) */
};

struct fcs {
	char	f_rtyp;		/* Record type */
	char	f_ratt;		/* Record attributes */
	ushort	f_rsiz;		/* Record size */
	ushort	f_hibk[2];	/* Highest VBN allocated */
	ushort	f_efbk[2];	/* End of file block */
	ushort	f_ffby;		/* First free byte */
};

struct ident {
	struct filnam i_fnam;	/* File name, type, version number */
	ushort	i_rvno;		/* Revision number */
	char	i_rvdat[7];	/* Revision date (ASCII) */
	char	i_rvti[6];	/* Revision time (ASCII) */
	char	i_crdt[7];	/* Creation time (ASCII) */
	char	i_crti[6];	/* Creation time (ASCII) */
	char	i_exdt[7];	/* Expiration date (ASCII) */
	char	i_UU;		/* Unused */
};

struct map {
	char	m_esqn;		/* Extension segment number */
	char	m_ervn;		/* Extension relative volume number */
	ushort	m_efnu;		/* Extension file number */
	ushort	m_efsq;		/* Extension file sequence number */
	char	m_ctsz;		/* Block count field size */
	char	m_lbsz;		/* LBN Field size */
	char	m_use;		/* Map words in use */
	char	m_max;		/* Map words available */
	char	m_rtrvp[410];	/* Retrieval pointers */
};

struct header {
	char	h_idof;		/* Ident area offset */
	char	h_mpof;		/* Map area offset */
	ushort	h_fnum;		/* File number */
	ushort	h_fseq;		/* File sequence number (why no RVN?) */
	ushort	h_flev;		/* File structure level */
	struct uic h_fown;	/* File owner UIC */
	ushort	h_fpro;		/* File protection code */
	char	h_fcha[2];	/* File characteristics */
	struct fcs h_fcs;	/* FCS area */
	char	h_ufat[18];	/* User attribute area */
	struct ident h_ident;	/* Ident area */
	struct map h_map;	/* Map area */
};

struct homeblock {
	ushort	H_ibsz;		/* Index file bitmap size */
	ushort	H_iblb[2];	/* Index file bitmap LBN */
	ushort	H_fmax;		/* Maximum number of files */
	ushort	H_sbcl;		/* Storage bitmap cluster factor */
	ushort	H_dvty;		/* Disk device type */
	ushort	H_vlev;		/* Volume structure level */
	char	H_vnam[12];	/* Volume name */
	char	H_UU1[4];	/* Unused 1 */
	struct uic H_vown;	/* Volume owner UIC */
	ushort	H_vpro;		/* Volume protection code */
	char	H_vcha[2];	/* Volume characteristics */
	ushort	H_fpro;		/* Default file protection */
	char	H_UU2[6];	/* Unused 2 */
	char	H_wisz;		/* Default window size */
	char	H_fiex;		/* Default file extend */
	char	H_lruc;		/* Directory pre-access limit */
	char	H_UU3[11];	/* Unused 3 */
	ushort	H_chk1;		/* First checksum */
	char	H_vdat[14];	/* Volume creation date */
	char	H_UU4[398];	/* Unused 4 */
	char	H_indn[12];	/* Volume name (don't you believe H_vnam?) */
	char	H_indo[12];	/* Volume owner (in a different format!) */
	char	H_indf[12];	/* Format type == "DECFILE11A " */
	char	H_UU5[2];	/* Unused 5 */
	ushort	H_chk2;		/* Final checksum */
} hblock;

struct directory {
	struct fileid d_fid;	/* File id */
	struct filnam d_fname;	/* File name, type, version number */
};

#define	BLKSIZE	512	/* Number of bytes per disk block */
#define	DEPB	32	/* Number of directory entries per block */

#define	bit(x)	((01)<<(x))

#define	DEV	bit(0)
#define	DIR	bit(1)
#define	FIL	bit(2)
#define	EXT	bit(3)
#define	VER	bit(4)

#define	DIRBEG	'['
#define	DIREND	']'

#define	NULLCHR	'\0'
#define	NULLSTR	""

#define	FSMAX	100

#ifdef rt11
#define	DEVMAX	3
#else
#define	DEVMAX	10
#endif

#define	TEXT	0
#define	IMAGE	1
#define	BINARY	2

#define	DISK	0
#define	STDOUT	1

#define	DFLTMOD	TEXT
#define	DFLTOUT	DISK

#define	wtp(c,l)	( (c) + ( (l) << 8 ) )

char	**av;				/* Global argv */
char	lsflag = 0;			/* Nonzero ==> list directory */
int	xfermode = DFLTMOD;		/* Transfer mode */
int	outdest = DFLTOUT;		/* Output destination */
char	filspec[FSMAX];			/* Full filename string being processed */
int	pflags;				/* Flags returned by crack() */
char	*dev, *dir, *fil, *typ, *ver;	/* Pointers to cracked filename fields */
int	rsx = -1;			/* File descriptor for reading RSX filesystem */
FILE	*of;				/* Stream pointer for output file */

#ifdef rt11
char	rsxdev[DEVMAX+2];		/* Device name for RSX filesystem */
#else
char	rsxdev[DEVMAX+6];		/* Special file name for RSX filesystem */
#endif

/*  The following two used to be internal static variables in openin() and   */
/*  putch(), respectively.  They were moved here for the benefit of DECUS C  */

int	filecnt = 0;			/* Nubmer of files processed */
int	state = 0;			/* Current state for putch() */

struct header	indexh, mfdh, dirh, fileh;	/* File headers for index file, MFD, UFD, and file */
struct map	*indexm, *mfdm, *dirm, *filem;	/* Pointers to their map areas */


char	r50c[128] = {				/* ASCII to Radix-50 conversion table */
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	000,  -1,  -1,  -1, 033,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1, 034,  -1,
	036, 037, 040, 041, 042, 043, 044, 045,
	046, 047,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1, 001, 002, 003, 004, 005, 006, 007,
	010, 011, 012, 013, 014, 015, 016, 017,
	020, 021, 022, 023, 024, 025, 026, 027,
	030, 031, 032,  -1,  -1,  -1,  -1,  -1,
	 -1, 001, 002, 003, 004, 005, 006, 007,
	010, 011, 012, 013, 014, 015, 016, 017,
	020, 021, 022, 023, 024, 025, 026, 027,
	030, 031, 032,  -1,  -1,  -1,  -1,  -1
};

char	r50a[] = " abcdefghijklmnopqrstuvwxyz$.?0123456789";

#define	BADR50	((ushort)(-1))


main(argc,argv)
int	argc;
char	**argv;
{
	av = argv;

	if ( --argc == 0 )
		usage();

	if ( **argv == 'l' )
		++lsflag;

	while ( argc-- ) {
		if ( **++av == '-' )
			options(*argv);
		else
			getrsx();
	}
}


usage()
{
	fprintf(stderr,"usage: %s [-tibdfsl] rsxfile(s)\n",*av);
	exit(-1);
}


/*
 *  Process option flags
 */

options()
{
	register char	*p;

	for ( p = *av; *++p; ) {

		switch ( *p ) {

		case 'd':
		case 'f':	outdest = DISK; break;
		case 's':	outdest = STDOUT; break;

		case 't':	xfermode = TEXT; break;
		case 'i':	xfermode = IMAGE; break;
		case 'b':	xfermode = BINARY; break;

		case 'l':	++lsflag; break;
		case 'c':	lsflag = 0; break;

		default:	fprintf(stderr,"Invalid option (%c)\n",*p);

		}
	}
}


/*
 *  Get the next reqested file from the RSX filesystem
 */

getrsx()
{
	if ( strlen(*av) > FSMAX )
		err0("Filespec too long");
	strcpy(filspec,*av);

	if ( lsflag ) {
		if ( openin() )
			listdir();
	} else {
		if ( openin() && openout() ) {
			copyfile();
			if ( of != stdout )
				fclose(of);
		}
	}
}


/*
 *  Open RSX file for input
 */

openin()
{
	int		crackok;
	struct filnam	fn;
	ushort		fnum;
	ushort		search();

	++filecnt;
	crackok = crack();
	if ( pflags&DEV && !openrsx(dev) )
		return(0);
#ifdef DFLTDEV
	if ( !(pflags&DEV) && filecnt==1 && !openrsx(DFLTDEV) )
		return(0);
#endif
	if ( rsx < 0 )
		err0("No device specified");
	if ( pflags&(DEV|DIR) ) {
		if ( !dir )
			err0("No directory specified");
		if ( !finddir() )
			return(0);
	}
	if ( lsflag ) {
		if ( pflags & (FIL|EXT|VER) )
			err0("Invalid directory specification");
		return(1);
	}
	if ( !crackok )
		return(0);
	if ( !(pflags&EXT) )
		typ = NULLSTR;
	if ( !(pflags&VER) )
		ver = NULLSTR;
	if ( !convert(fil,typ,ver,&fn) )
		return(0);
	if ( !(fnum=search(dirm,&fn)) )
		err0("File does not exist");
	if ( !gethdr(fnum,&fileh) )
		err0("Can't get file header for file");
	filem = &fileh.h_map;
	return(1);
}


/*
 *  Crack the filename string -- First step in parsing it; just
 *  locates the fields, doesn't do much real validity checking
 */

crack()
{
	register char	*p;
	register char	*q;

	for ( p=filspec, pflags=0; *p; ) {

		if ( *p == DIRBEG ) {
			if ( pflags & (DIR|FIL|EXT|VER) )
				err0("Bad filename syntax");
			for ( dir = ++p; *p != DIREND; )
				if ( *p++ == NULLCHR )
					err0("Bad filename syntax");
			*p++ = NULLCHR;
			pflags |= DIR;
			continue;
		}

		for ( q=p; isalnum(*q); ++q )
			;

		if ( *q == ':' ) {
			if ( pflags&(DEV|DIR|FIL|EXT|VER) )
				err0("Bad filename syntax");
			dev = p;
			pflags |= DEV;
			*q++ = NULLCHR;
			p = q;
			continue;
		}

		if ( *q == '.' || *q == NULLCHR ) {

			if ( !(pflags&FIL) ) {
				if ( p == q )
					err0("Filename missing");
				fil = p;
				pflags |= FIL;
				if ( *q == ';' ) {
					typ = NULLSTR;
					pflags |= EXT;
				}
			} else if ( !(pflags&EXT) ) {
				typ = p;
				pflags |= EXT;
			} else if ( !(pflags&VER) ) {
				ver = p;
				pflags |= VER;
			} else
				err0("Bad filename syntax");

			if ( *q == NULLCHR ) {
				if ( !(pflags&EXT) )
					typ = NULLSTR;
				if ( !(pflags&VER) )
					ver = NULLSTR;
				break;
			}
			*q++ = NULLCHR;
			p = q;
			continue;
		}

		err0("Bad filename syntax");
	}

	return(1);
}


/*
 *  Open a disk containing an RSX filesystem
 */

openrsx(devname)
char	*devname;
{
	long	ifhbn;

	if ( strlen(devname) > DEVMAX )
		err1("Device name too long (%s)",devname);
#ifdef rt11
	strcpy(rsxdev,devname);
	strcat(rsxdev,":");
#else
	strcpy(rsxdev,"/dev/");
	strcat(rsxdev,devname);
#endif
	if ( (rsx=open(rsxdev,0)) < 0 )
		err1("Can't open %s",rsxdev);

	if ( !getlb(1L,&hblock) )
		err1("Can't read homeblock on %s",rsxdev);

	ifhbn = ((long)hblock.H_iblb[0]<<16) + (long)hblock.H_iblb[1] + hblock.H_ibsz;
	if ( !getlb(ifhbn,&indexh) )
		err1("Can't read index file header on %s\n",rsxdev);
	indexm = &indexh.h_map;

	if ( !getlb(ifhbn+3,&mfdh) )
		err1("Can't read mfd header on %s",rsxdev);
	mfdm = &mfdh.h_map;

	return(1);
}


/*
 *  Locate the directory whose name is pointed to by "dir"
 */

finddir()
{
	char		dirname[7];
	register char	*p;
	register char	*q;
	register char	*s;
	char		*strchr();	/* NOTE: Called index() on some versions of UNIX */
	int		nch;
	struct filnam	dirfn;
	ushort		dirfnum;
	ushort		search();

	p = dir;
	q = dirname;
	dirm = (struct map *) 0;
	strcpy(dirname,"000000");
	if ( (s=strchr(p,',')) == (char *)0 || (nch=s-p) == 0 || nch > 3 )
		err1("Invalid UFD ([%s])",dir);
	for ( q += 3-nch; p < s; ) {
		if ( !isoctal(*p) )
			err1("Invalid UFD ([%s])",dir);
		*q++ = *p++;
	}
	if ( (nch=strlen(++p)) == 0 || nch > 3 )
		err1("Invalid UFD ([%s])",dir);
	for ( q += 3-nch; *p; ) {
		if ( !isoctal(*p) )
			err1("Invalid UFD ([%s])",dir);
		*q++ = *p++;
	}

	if ( !convert(dirname,"dir","1",&dirfn) )
		err1("Invalid UFD ([%s])",dir);

	if ( !(dirfnum=search(mfdm,&dirfn)) )
		err1("UFD [%s] does not exist",dir);
	if ( !gethdr(dirfnum,&dirh) )
		err1("Can't get file header for UFD [%s]",dir);
	dirm = &dirh.h_map;
	return(1);
}


/*
 *  Convert file name, type, and version number to "struct filnam" format
 */

convert(fl,tp,vr,f)
char		*fl;
char		*tp;
char		*vr;
struct filnam	*f;
{
	register int	i;
	ushort		ator50();

	if ( strlen(fl) > 9 )
		err0("Filename longer than 9 characters");
	if ( strlen(tp) > 3 )
		err0("File type longer than 3 characters");
	for ( i=0; i<3; ++i )
		if ( (f->f_nam[i]=ator50(&fl)) == BADR50 )
			return(0);
	if ( (f->f_typ=ator50(&tp)) == BADR50 )
		return(0);
	for ( f->f_ver=0; *vr; ) {
		if ( !isoctal(*vr) )
			err0("Non-octal character in version number");
		f->f_ver <<= 3;
		f->f_ver += *vr++ - '0';
	}
	return(1);
}


/*
 *  Convert up to three ascii characters to a 16-bit Radix-50 value; note
 *  type of s (char **, not char *);  *s is left pointing to the null byte,
 *  if encountered
 */

ushort
ator50(s)
register char	**s;
{
	register ushort	scale;
	register ushort	r;
	register int	i;

	for ( scale=050*050, r=0, i=3; **s && i--; ) {
		if ( r50c[**s] == -1 ) {
			errmsg("Bad radix-50 character (%c)",**s);
			return(BADR50);
		}
		r += r50c[*(*s)++] * scale;
		scale /= 050;
	}
	return(r);
}


/*
 *  Search a directory (identified by dmap) for a filename
 */

ushort
search(dmap,fn)
register struct map	*dmap;
register struct filnam	*fn;
{
	register ushort			hldver;
	register ushort			hldfnum;
	int				bod;
	register struct directory	*de;
	struct directory		*getde();

	for ( hldver=0, hldfnum=0, bod=1; de=getde(dmap,bod); bod=0 ) {
		if ( de->d_fid.f_num == 0 )
			continue;
		if ( fn->f_ver && fn->f_ver==de->d_fname.f_ver && match(fn,&de->d_fname) )
			return(de->d_fid.f_num);
		if ( !fn->f_ver && de->d_fname.f_ver>hldver && match(fn,&de->d_fname) ) {
			hldfnum = de->d_fid.f_num;
			hldver = de->d_fname.f_ver;
		}
	}
	return(hldfnum);
}


/*
 *  Check whether two filenames match
 */

match(a,b)
register struct filnam	*a;
register struct filnam	*b;
{
	register int	i;

	for ( i=0; i<3; ++i )
		if ( a->f_nam[i] != b->f_nam[i] )
			return(0);
	return ( a->f_typ == b->f_typ );
}


/*
 *  Open output file
 */

openout()
{
	char	outfile[14];

	if ( outdest == STDOUT ) {
		of = stdout;
		return(1);
	}

	strcpy(outfile,fil);
	strcat(outfile,".");
	strcat(outfile,typ);
	if ( (of=fopen(outfile,"w")) == NULL )
		err0("Can't open output file");
	return(1);
}


/*
 *  Copy input file to output destination
 */

copyfile()
{
	long		eofblk;
	register long	block;
	register long	b;
	char		buf[BLKSIZE];
	char		*limit;
	register char	*p;

	block = 0L;
	b = 0L;
	limit = &buf[BLKSIZE];
	if ( xfermode == BINARY )
		err0("Binary mode not yet supported");
	if ( xfermode != IMAGE )
		eofblk = ( (long)fileh.h_fcs.f_efbk[0] << 16 ) + fileh.h_fcs.f_efbk[1];
	while ( getvb(++block,buf,filem) ) {
		if ( xfermode == IMAGE ) {
#ifdef rt11
			for ( p=buf; p<buf+BLKSIZE; )
				if ( putc(*p++,of) == EOF )
					err0("write error");
#else
			if ( fwrite(buf,1,BLKSIZE,of) != BLKSIZE )
				err0("write error");
#endif
			continue;
		}
		if ( ++b > eofblk )
			return(1);
		if ( b == eofblk )
			limit = &buf[fileh.h_fcs.f_ffby];
		for ( p=buf; p<limit; )
			putch(*p++);
	}
	return(1);
}


/*
 *  Process next character from input file
 */

putch(c)
register char	c;
{
	static unsigned	count;
	static int	nextstate;

	if ( state == 0 ) {
		count = (c&0377);
		state = 1;
	} else if ( state == 1 ) {
		if ( (count+=((c&0377)<<8)) == 0 ) {
			putc('\n',of);
			state = 0;
		} else {
			state = 2;
			nextstate = 0;
			if ( count&1 )
				nextstate = 3;
		}
	} else if ( state == 2 ) {
		putc(c,of);
		if ( --count == 0 ) {
			state = nextstate;
			putc('\n',of);
		}
	} else			/* state==3; ignore null pad */
		state = 0;
}


/*
 *  List contents of a UFD
 */

listdir()
{
	register int			bod;
	register struct directory	*de;
	struct directory		*getde();

#ifndef rt11
	redirout();
#endif
	for ( bod=1; de=getde(dirm,bod); bod=0 )
		if ( de->d_fid.f_num != 0 )
			prtfn(&de->d_fname);
	fclose(stdout);
#ifndef rt11
	wait(0);
#endif
	exit(0);
}


/*
 *  Redirect standard output, to pipe it through "sort"
 */

#ifndef rt11

redirout()
{
	int	pfd[2];
	int	fk;

	if ( pipe(pfd) )
		err0("Can't create pipe");
	while ( (fk=fork()) == -1 )
		sleep(1);
	if ( fk == 0 )
		dup2(pfd[0],0);
	else
		dup2(pfd[1],1);
	close(pfd[0]);
	close(pfd[1]);
	if ( fk == 0 ) {
		execl("/usr/bin/sort","sort",0);
		execl("/bin/sort","sort",0);
		err0("Can't exec sort");
	}
}

#endif


/*
 *  Write filename to standard output
 */

prtfn(fn)
struct filnam	*fn;
{
	register int	i;

	for ( i=0; i<3; ++i )
		if ( !r50out(fn->f_nam[i]) )
			break;
	putchar('.');
	r50out(fn->f_typ);
	fprintf(stdout,";%o\n",fn->f_ver);
}


/*
 *  Convert up to three nonblank Radix-50 characters to ASCII, and write to
 *  standard output.  Returns 1 if three characters converted, 0 otherwise.
 *  The argument needs to be shoved into a long, since both UNIX C compilers
 *  (Ritchie and portable) on the PDP-11 have the following bug:  If `x' is
 *  an unsigned short in the range 0100000 <= x <= 0177777, and if `d' has
 *  the value 1, then x/d is alleged to be 0!  Until this bug gets fixed, we
 *  just kludge around it.
 */

r50out(x)
ushort	x;
{
	register long	lx;
	register ushort	div;
	register char	c;

	for ( lx=(long)x, div=050*050; (c=r50a[(lx/div)%050])!=' '; ) {
		putchar(c);
		if ( !(div/=050) )
			return(1);
	}
	return(0);
}


/*
 *  Return pointer to next directory entry
 */

struct directory *
getde(dm,bod)
register struct map	*dm;
int			bod;
{
	static long		vb;
	static struct directory	dirbuf[DEPB];
	static struct directory	*de;

	if ( bod )
		vb = 0L;
	if ( bod || de>=dirbuf+DEPB ) {
		de = dirbuf;
		if ( !getvb(++vb,(char *)dirbuf,dm) )
			return((struct directory *)0);
	}
	return(de++);
}


/*
 *  Get a file header, given the file number
 */

gethdr(fnum,buf)
ushort	fnum;
char	*buf;
{
	long	bn;

#ifdef rt11	/* To get around bug in DECUS C compiler */
	bn = (long)fnum;
	bn += hblock.H_ibsz;
	bn += 2;
#else
	bn = (long)fnum + (long)hblock.H_ibsz + 2;
#endif
	return(getvb(bn,buf,indexm));
}


/*
 *  Routine to get specified virtual block from a file.  Returns 0
 *  on EOF, 1 otherwise.  Note that vbn is 1-based, not 0-based.
 */

getvb(vbn,buf,map)
register long		vbn;
char			*buf;
register struct map	*map;
{
	register ushort		wtype;
	register char		*rp_1;
	register ushort		*rp_2;
	register long		block;
	register ushort		*limit;
	register long		lbn;
	register long		size;
	long			lbnbase();
#ifdef rt11	/* To get around bug in DECUS C compiler */
	long			getsize();
#else
	ushort			getsize();
#endif

	wtype = wtp(map->m_ctsz,map->m_lbsz);
	rp_1 = map->m_rtrvp;
	rp_2 = (ushort *)rp_1;
	block = 1L;
	limit = rp_2 + map->m_use;
	while ( rp_2 < limit && vbn >= ( block + (size=getsize(wtype,rp_1)) ) ) {
		rp_1 += map->m_ctsz + map->m_lbsz;
		rp_2 = (ushort *)rp_1;
		block += size;
	}
	if ( rp_2 >= limit )
		return(0);
	lbn = lbnbase(wtype,rp_1) + vbn - block;
	return(getlb(lbn,buf));
}


/*
 *  Return number of blocks mapped by the current window
 */

#ifdef rt11	/* To get around bug in DECUS C compiler */
long
#else
ushort
#endif
getsize(wtype,rp_1)
register ushort	wtype;
register char	*rp_1;
{
	register ushort	*rp_2;
#ifdef rt11	/* To get around bug in DECUS C compiler */
	long	dummy;
#endif

	rp_2 = (ushort *)rp_1;
	switch (wtype) {
#ifdef rt11	/* To get around bug in DECUS C compiler */
		case wtp(1,3):	dummy = (long)((rp_1[1]&(ushort)0377)+1); return(dummy);
		case wtp(2,2):
		case wtp(2,4):	dummy = (long)(*rp_2+1); return(dummy);
#else
		case wtp(1,3):	return((rp_1[1]&(ushort)0377)+1);
		case wtp(2,2):
		case wtp(2,4):	return(*rp_2+1);
#endif
	}
}


/*
 *  Return base lbn mapped by the current window
 */

long
lbnbase(wtype,rp_1)
register ushort	wtype;
register char	*rp_1;
{
	register ushort	*rp_2;

	rp_2 = (ushort *)rp_1;
	switch ( wtype ) {
		case wtp(1,3):	return((((long)(*rp_1)&0377)<<16)+rp_2[1]);
		case wtp(2,2):	return((long)rp_2[1]);
		case wtp(2,4):	return((((long)rp_2[1])<<16)+rp_2[2]);
	}
}


/*
 *  Get block from the filesystem, given the logical block number
 */

getlb(lbn,buf)
long	lbn;
char	*buf;
{
	if ( lbn == 0L )
		err0("Bad block in file");
	if ( lseek(rsx,BLKSIZE*lbn,0) == -1L ||  read(rsx,buf,BLKSIZE) != BLKSIZE )
		err0("Read error");
	return(1);
}


/*
 *  Issue an error message
 */

/* VARARGS1 */

errmsg(msg,arg)
char	*msg;
int	arg;
{
	fprintf(stderr,"%s -- ",*av);
	fprintf(stderr,msg,arg);
	fprintf(stderr,"\n");
}
