Google
 

Trailing-Edge - PDP-10 Archives - decuslib10-08 - 43,50512/io.b36
There are no other files named io.b36 in the archive.
MODULE IO=
!TOPS-10 Dependent I/O routines and filespec parsing routines
!Compilation instructions:
!	IO,IO=IO/VARIANT:nnn
!nnn is decoded into bits as follows:
!1:	FTSIGNAL (FTSIG)	use signalling mechanism
!2:	FTNETSPL		special stuff for NETSPL
!4:	FTIPC			special stuff for IPC
!2000:	FTWILD			wildcard stuff
!4000:	FTALLOC			use ALLOC & FREE for core mgt.
BEGIN
!
! Conditional Compilation
!
COMPILETIME FTPSI=0;			!On to rely on software interrupts
COMPILETIME FTSIGNAL=(%VARIANT AND 1);	!Conditional for signal package
	!Conditional for signal package (on if /VAR:1)
COMPILETIME FTNETSPL=((%VARIANT AND 2) NEQ 0);
	!Conditional for NETSPL-only code
COMPILETIME FTWILD=((%VARIANT AND %O'2000') NEQ 0);
	!Conditional for wildcarding (on if /VAR:#2000)
COMPILETIME FTALLOC=((%VARIANT AND %O'4000') NEQ 0);
	!Conditional for core allocation stuff (on if /VAR:#4000)
%IF FTNETSPL %THEN !Force FTSIGNAL,FTWILD, & FTALLOC on
	%ASSIGN(FTSIGNAL,1) %ASSIGN(FTWILD,1) %ASSIGN(FTALLOC,1)
	%FI
!
! REQUIRE & LIBRARY declarations
!

%IF FTSIGNAL
	%THEN REQUIRE 'INTR.REQ';
	%ELSE LIBRARY 'IPCF';
%FI

!
! Forward declarations
!
UNDECLARE ZERO;
FORWARD ROUTINE
	FILOP,	!Open/close file
	FBINI,	!Initialize file block
	FBINIS,	!Set up file block, but not defaults
	INPUT,	!Read in a buffer
	OUTPUT,	!Write out " "
	READ,	!Read in a line
	WRITE,	!Write out a line
	BIN,	!Read in a character & return it
	BINA,	!Read a character & advance pointer
	BOUT,	!Write out a character
	BOUTA,	!write a character & advance pointer
	RDDIR,	!Parse a directory specifcation
	RDNUMA,
	RDSIXA,
	SWAP,
	PATH,
	ZERO,
	FUNPARSE,
	WRSIXA,
	WRSIX,	!Sixbit to ASCII
	FFIOCH,	!Find a free I/O channel
	RELEASE,	!Free up channel & release device
	RESETF,	!Reset a channel (RESDV.)
	OPENFILE;	!Open a file
!	SETSTS;
%IF FTALLOC %THEN
FORWARD ROUTINE
	FREEFB,	!Free a fileblock & all decendants
	BUFFALO,	!Allocate space for buffers
	BUFFREE:NOVALUE;!Free a buffer ring
%FI

THIS_IS [IO]

VERSION [4]	%IF FTSIG %THEN VMINOR [S]%FI	EDIT [34] DATE [18,MAY,79]
!++
!	Facility:	NETSPL,RMCOPY,NODTBL
!
!Abstract:
!	Subroutines to do filespec parsing, file selection,
!	and sequential I/O.  The entire module is TOPS-10 dependant.
!Author: Andrew Nourse

!	These routines can use the signalling mechanism, or just
!	return an error code.  To use signalling, compile as follows:
!	IO,IO=IO/VARIANT:1


%(***	R E V I S I O N   H I S T O R Y

[34]	Don't zero .JBFF on recursive OPENFI (like in error handler)
[33]	Fix so it will compile under BLISS-36 version 1A(114)
	(which does not like BLOCKs in registers)
[32]	ALWAYS restore JBFF after attempting to open something
[31]	Call RELEASE before INTFREE in FREEFB to avoid ill addr in UUO
[27]	Add BUFFALO routine to allocate buffer space, and OPENF to open files
[26]	Make FPARSE not force default to current path if defaulted
[25]	Make FUNPARSE return value of updated byte pointer instead of nothing
	Undeclare ZERO so no multiple definition
[24]	Recognize % as wildcard character, make definitions of chars a macro
[23]	Put in FILE$PF_NOSIG bit for FPARSE
[22]	Change default bit setting.  Sign bit off means reset before scanning
[21]	Put in changes to RDSIX,FPARSE,& RDDIR to allow wildcards sometimes
[20]	Put in FREEFB routine, put in FBTBL to remember where file blks are
[17]	Turn off interrupts while doing I/O (WAIT will turn back on)
[15]	Fix multiple shift of error code in BINA,BOUTA,READ,WRITE
	and 0-length buffer doesn't get bad data, Fix SFD's in FUNPARSE (..S)
	Make WRSIX not dependant on layout of stack in BLS36C
[14]	Put in REF (FILE_BLOCK) in PATH routine, don't assign chn 20
	put in RELEASE routine
[13]	Fix non-blocking INPUT & OUTPUT
[12]	Fix RDSIX some more (a loop got put back)
	Add routines to unparse a filespec
[11]	Fix bug in FPARSE which lost short filenames when no extension given
[10]	Change to use VAX/11 standard format error codes
	and use VARIANT to specify signalling version
[9]	Fix FBINI to put PATH. block addr in LOOKUP block
[8]	Replace recursive RDSIX with ordinary type
[7]	Fix bug in DECR loops in READ & WRITE.
[6]	Change some obsolete comments, put in SIGNAL stuff,
	Change BIN to BINA and BOUT to BOUTA
[0-5]	The distant past.....

	E N D	R E V I S I O N   H I S T O R Y	)%
!
! Global Data Areas
!
GLOBAL FBTBL: VECTOR[16];
!
! External References
!
%IF FTALLOC	!Core management stuff
	%THEN EXTERNAL ROUTINE
			ALLOC,	!Get some core
			FREE;	!Release core
EXTERNAL SAVJBFF;		!Save value of JBFF while making buffers
		!This should always contain either the first free location or 0
	%FI
EXTERNAL ROUTINE
	WRNUMA;		!Convert a number to ASCII
%IF FTWILD	!Wildcard conditional compilation
	%THEN EXTERNAL ROUTINE MSKW6; !Return mask for wildcard (sixbit) chars
	%FI
!
! Builtin, Literals, and Bound declarations
!

LITERAL DEFAULT_IORETI=1;	!Try every 2/3 second
BUILTIN MACHSKIP,MACHOP;
%IF NOT %DECLARED(PATMIN)
	%THEN LITERAL PATMIN=4; %FI	!# of words in path block w/o SFD's
LITERAL EQMSGSIZE=%O'1000';	!These are always sent as pages
BIND JBFF=%O'121';	!in JOB DATA AREA

!
! Macros
!
MACRO	FILENAME_CHARS=%C'A' TO %C'Z',%C'0' TO %C'9'%,
	WILD_1=%C'%',%C'?'%,
	WILD_N=%C'*'%,
	LOWER_CASE=%C'A'+%O'40' TO %C'Z'+%O'40'%;
MACRO XCT(AA)=MACHOP(%O'256',0,AA)%;
MACRO DEVSIZ(R)=CALLI(R,%O'101') %;	!DEVSIZ UUO
MACRO	IOERRCODE(X)=
			BEGIN
			IF (.X XOR ENDFILE) EQL 0
				THEN WARNING(ENDFILE,.FILBLK)
				ELSE ERROR(IOERROR,.FILBLK,.X)
			END%;
MACRO ERR_INTERPRET=
	BEGIN
	%IF FTSIG
		%THEN
		IF .FF EQL 0
		THEN	BEGIN
			LOCAL IB: REF INT_BLOCK;
			%IF NOT FTPSI %THEN
			EXTERNAL ROUTINE
				UDT,
				TSIGNL,
				TSICAN;
			LOCAL RETRYIT: INT_BLOCK;
			LOCAL RTR;	!Handle for timer
			CLEARV(RETRYIT);!Empty block for wakeup only
			RTR=TSIGNL(RETRYIT,UDT()+.IORETINTERVAL);
			%FI
			IF (IB=.FILBLK[FILE$IB]) EQL 0
				THEN CRASH('Interrupt block missing');
			WAIT(.IB);	!Wait for 'done' interrupt
			%IF NOT FTPSI %THEN TSICAN(.RTR); %FI
			END
		ELSE IOERRCODE(FF)
	%ELSE IOERRCODE(FF)
	%FI
	END%;
%IF NOT FTSIG
	%THEN			!Bypass signalling stuff if not using it
	MACRO	NOSIGNAL[]=%REMAINING%;
	MACRO	INTERRUPTS[]=%,
		NOINTS[]=%REMAINING%,
		WAIT[]=%;		!Make No-op of calls to these
	%FI

MACRO	INSTR_ADDRESS=0,18 %,
	INSTR_OPCODE=27,9  %,
	INSTR_AC=23,4      %;

!
! Tell about conditionals
!

%IF FTNETSPL %THEN
%INFORM('IO for NETSPL')
%ELSE
	%IF (FTSIGNAL) %THEN
		%INFORM('IO being built with PSI support')
	%FI
	%IF (FTWILD) %THEN
		%INFORM('IO being built with wildcard support')
	%FI
	%IF (FTALLOC) %THEN
		%INFORM('IO being built with core management support')
	%FI
%FI
!
! Global data
!
GLOBAL	IORETINTERVAL: INITIAL(DEFAULT_IORETI); !How often check I/O done

GLOBAL ROUTINE FILOP(FILBLK)=
!Bottom-level file selection routine
!FILBLK is a file block which should be already set up,
!including the FILOP. function code.
!Returns -1 if successful
BEGIN
REGISTER F;
F=XWD(8,.FILBLK);	!Point to File block 
			!(which curiously enough, is a FILOP. block)
IF CALLI(F,%O'155')	!FILOP. UUO
	THEN WIN	!RETURN WIN IF SUCCESSFUL
	ELSE	BEGIN
		IF .F GTR %O'77' THEN ERROR(FILOPN,.FILBLK); !Open Failure
		ERROR(FILERR+.F,.FILBLK) !Else include lookup error code
		END
END;
GLOBAL ROUTINE FBINI(FILBLK)=
!This sets up a file block for the first time it gets used
!It only sets up pointers, not filespecs.

BEGIN
MAP FILBLK: REF FILE_BLOCK;

!First Zero out everything

ZERO(.FILBLK,.FILBLK+FB_LEN-1);

!Now fill in the required pointers in the FILOP. block

FILBLK[FILE$COUNT]=%FIELDEXPAND(FILE$ALLOC,0)-%FIELDEXPAND(FILE$COUNT,0);
					!# OF WORDS IN LOOKUP/ENTER BLOCK
FBINIS(FILBLK[FILE$START]);		!Set up the pointers
FFIOCH(.FILBLK)				!Find a free channel for I/O
END;	!FBINI


GLOBAL ROUTINE FBINIS(FILBLK)=
BEGIN
!Set up the pointers inside a file block
!This does not initialize any of the other fields, or get a channel #

!
! Formal Parameters
!
MAP FILBLK: REF FILE_BLOCK;	!Address of the file block

!
! Returned value
!

!	none
FILBLK[FILE$LOOKUP]=FILBLK[FILE$COUNT];	!Set up pointer to lookup block
FILBLK[FILE$LPATH]=FILBLK[FILE$PATH_FUN];	!PATH block pointer in LOOKUP block
FILBLK[FILE$PATH_LEN]=SFDMAX+PATMIN;
FILBLK[FILE$PATH_BLOCK]=FILBLK[FILE$PATH_FUN];
FILBLK[FILE$I_BRH]=FILBLK[FILE$I_CBUFF];	!Input buffer ring header pointer
FILBLK[FILE$O_BRH]=FILBLK[FILE$O_CBUFF];	!Output ...
FILBLK[FILE$I_NBUFF]=-1;			!Use default # of buffers
FILBLK[FILE$O_NBUFF]=-1;			! "    "
FILBLK[FILE$DEVICE]=%SIXBIT 'DSK   ';
END;	!FBINIS
GLOBAL ROUTINE INPUT(FILBLK)=
!ROUTINE TO INPUT A BUFFER.
!TAKES FILE BLOCK AS ARGUMENT
!RETURN WIN IF SUCCESSFUL

BEGIN
REGISTER FF; MAP FF: INSTRUCTION;	!WE WILL BUILD THE INSTRUCTION HERE
MAP FILBLK: REF FILE_BLOCK;
BUILTIN LSH;
%IF FTNETSPL %THEN LITERAL IOERROR=INERROR;
%FI	!NETSPL should be able to tell input errors from output errors

DO	BEGIN	!While 1
	LOCAL GG;			!Status goes here
	FF=0;				!Initialize the AC
	IF .FILBLK[FILE$I_NBUFF] EQL 0 THEN	!Must be dump mode
		FF<INSTR_ADDRESS>=.FILBLK[FILE$I_IOLIST]; !Address of I/O list
	FF<INSTR_AC>=.FILBLK[FILE$CHANNEL];	!Channel # for instruction
	FF<INSTR_OPCODE>=%O'056';	!OR IN THE OPCODE
	IF MACHSKIP(%O'256',0,FF)	!ERROR OR NOT FINISHED YET
	THEN	BEGIN
		FF<INSTR_OPCODE>=%O'062';	!OR IN GETSTS OPCODE
		FF<INSTR_ADDRESS>=GG;	!Return status to register
		MACHOP(%O'256',0,FF);		!GETSTS UUO
		FF=(.GG AND %O'760000');	!Only error bits
		ERR_INTERPRET;		!Figure out what went wrong
		END

	ELSE	BEGIN
		FILBLK[FILE$READS]=.FILBLK[FILE$READS]+1;
		!Bump count of blocks read
		RETURN WIN		!RETURN WIN IF SUCCESSFUL
		END
	END WHILE 1
END;


GLOBAL ROUTINE OUTPUT(FILBLK)=
!ROUTINE TO OUTPUT A BUFFER.
!TAKES FILE BLOCK AS ARGUMENT
!RETURN WIN IF SUCCESSFUL

BEGIN
REGISTER FF;	!WE WILL BUILD THE INSTRUCTION HERE
MAP FILBLK: REF FILE_BLOCK;
BUILTIN LSH;
MAP FF:INSTRUCTION;
%IF FTNETSPL %THEN LITERAL IOERROR=OUTERROR;
%FI	!Netspl needs to know the difference between input & output errors

DO	BEGIN	!While 1
	LOCAL GG;			!Status goes here
	FF=0;				!Initialize the AC
	IF .FILBLK[FILE$O_NBUFF] EQL 0 THEN	!Must be dump mode
		FF<INSTR_ADDRESS>=.FILBLK[FILE$O_IOLIST]; !Address of I/O list
	FF<INSTR_AC>=.FILBLK[FILE$CHANNEL];	!Channel # for instruction
	FF<INSTR_OPCODE>=%O'057';	!OR IN THE OPCODE
	IF MACHSKIP(%O'256',0,FF)	!ERROR OR NOT FINISHED YET
	THEN	BEGIN
		FF<INSTR_OPCODE>=%O'062';	!OR IN GETSTS OPCODE
		FF<INSTR_ADDRESS>=GG;	!Return status to register
		MACHOP(%O'256',0,FF);		!GETSTS UUO
		FF=(.GG AND %O'760000');	!Only error bits
		ERR_INTERPRET;		!Figure out what went wrong
		END

	ELSE	BEGIN
		FILBLK[FILE$WRITES]=.FILBLK[FILE$WRITES]+1;
		!Bump count of blocks written
		RETURN WIN		!RETURN WIN IF SUCCESSFUL
		END
	END WHILE 1
END;


GLOBAL ROUTINE READ(FILBLK,DEST,MAXCHARS,TERMINATOR)=
!ROUTINE TO READ A STRING FROM A FILE
!PARAMETERS:
!FILBLK: FILE BLOCK (SOURCE)
!DEST:	ADDR OF DESTINATION BYTE POINTER
!MAXCHARS: MAXIMUM # OF CHARACTERS TO INPUT;
!TERMINATOR: TERMINATE INPUT IF THIS CHARACTER IS FOUND
!RETURNS WIN if count exhausted, TERMINATOR otherwise
BEGIN
REGISTER ODEST,C;
	DECR MAXC FROM .MAXCHARS TO 0 DO BEGIN
	ODEST=..DEST;	!Save pointer to character we are about to read
	C=BINA(.FILBLK,.DEST);
	NOSIGNAL< IF .C NEQ WIN THEN RETURN .C;>	!Check for errors
	IF CH$RCHAR(.ODEST) EQL .TERMINATOR THEN RETURN .TERMINATOR;
	END;
RETURN WIN;
END;
GLOBAL ROUTINE WRITE(FILBLK,ADDR,MAXCHARS,TERMINATOR)=
!ROUTINE TO WRITE A STRING TO A FILE
!Parameters:
!FILBLK: FILE BLOCK (Destination)
!ADDR: ADDR OF SOURCE BYTE POINTER
!MAXCHARS: MAXIMUM # OF CHARACTERS TO OUTPUT;
!TERMINATOR: TERMINATE OUTPUT AFTER THIS CHARACTER IS FOUND
!Returns WIN if count exhausted, TERMINATOR otherwise
BEGIN
LOCAL C;
	DECR MAXC FROM .MAXCHARS TO 0 DO BEGIN
	C=BOUTA(.FILBLK,.ADDR);
	NOSIGNAL<IF .C NEQ WIN THEN RETURN .C;>	!Check for errors
	IF CH$RCHAR(..ADDR) EQL .TERMINATOR THEN
		(BOUTA(.FILBLK,.ADDR);RETURN .TERMINATOR);
	END;

END;
GLOBAL ROUTINE BIN(FILBLK)=
!READ ONE BYTE FROM A FILE
!FILBLK: FILE BLOCK
!Returns: byte read
BEGIN
MAP FILBLK: REF FILE_BLOCK;
UNTIL (FILBLK[FILE$I_COUNT]=.FILBLK[FILE$I_COUNT]-1) GEQ 0 DO
	BEGIN
	LOCAL R;
	R=INPUT(.FILBLK);
	NOSIGNAL <IF .R NEQ WIN THEN RETURN .R> !Check for errors
	END;
CH$RCHAR_A(FILBLK[FILE$I_PTR])	!Return character
END;	!BIN
GLOBAL ROUTINE BINA(FILBLK,DEST)=
!READ ONE BYTE FROM A FILE
!FILBLK: FILE BLOCK
!DEST:  Addr of DESTINATION BYTE POINTER
!RETURNS WIN
BEGIN
LOCAL R;
MAP FILBLK: REF FILE_BLOCK;
UNTIL (FILBLK[FILE$I_COUNT]=.FILBLK[FILE$I_COUNT]-1) GEQ 0 DO
	BEGIN
	R=INPUT(.FILBLK);
	NOSIGNAL <IF .R NEQ WIN THEN RETURN .R>	!Check for errors
	END;
CH$WCHAR_A(CH$RCHAR_A(FILBLK[FILE$I_PTR]),.DEST);
WIN	!SUCCESSFUL RETURN VALUE
END;
GLOBAL ROUTINE BOUT(FILBLK,B)=
!WRITE ONE Byte TO A FILE
!FILBLK: FILE BLOCK
!B: byte to write
!Returns WIN
BEGIN
MAP FILBLK: REF FILE_BLOCK;
UNTIL (FILBLK[FILE$O_COUNT]=.FILBLK[FILE$O_COUNT]-1) GEQ 0 DO
	BEGIN
	LOCAL R;
	R=OUTPUT(.FILBLK);
	NOSIGNAL <IF .R NEQ WIN THEN RETURN .R>	!Check for IO errors
	END;
CH$WCHAR_A(.B,FILBLK[FILE$O_PTR]);
WIN	!SUCCESSFUL RETURN VALUE
END;	!BOUT
GLOBAL ROUTINE BOUTA(FILBLK,SOURCE)=
!WRITE ONE BYTE TO A FILE
!FILBLK: FILE BLOCK
!SOURCE:  Addr of SOURCE BYTE POINTER
!Returns WIN
BEGIN
LOCAL R;
MAP FILBLK: REF FILE_BLOCK;
UNTIL (FILBLK[FILE$O_COUNT]=.FILBLK[FILE$O_COUNT]-1) GEQ 0 DO
	BEGIN
	R=OUTPUT(.FILBLK);
	NOSIGNAL <IF .R NEQ WIN THEN RETURN .R>	!Check for IO errors
	END;
CH$WCHAR_A(CH$RCHAR_A(.SOURCE),FILBLK[FILE$O_PTR]);
WIN	!SUCCESSFUL RETURN VALUE
END;
!Sixbit, number, & filespec parsing routines follow


%IF NOT %DECLARED(FTNETWORK) %THEN LITERAL FTNETWORK=1; %FI


GLOBAL ROUTINE FPARSE(FILBLK,SOURCE)=
!Routine to parse a filespec.
!FILBLK = file block	SOURCE = addr of byte pointer
BEGIN
MACRO FPERROR(CODE)=	!Signal error or return code depending on bit
%IF FTSIG
%THEN
	IF .FILBLK[FILE$PF_NOSIG]
		THEN RETURN CODE
		ELSE ERROR(CODE,FILBLK,SOURCE)
	%ELSE RETURN CODE
%FI%;
LOCAL C;
LOCAL NO_DIR_DEFAULT;	!Remember if caller supplied a default directory
MAP FILBLK: REF NDB;
LOCAL N;

IF .FILBLK[FILE$PF_NORESET] EQL 0 THEN	!Sign bit means freeze default bits
	FILBLK[FILE$PF_ALL_D]=-1;	!Nothing has been specified yet

IF .FILBLK[FILE$PPN] EQL 0
 THEN	BEGIN				!Set a default if none given yet
	FILBLK[FILE$PATH_FUN]=_PTFRD;	!Read default path code
	PATH(.FILBLK);			!Put it in the file block
	NO_DIR_DEFAULT=1;	!Remember we did this
	END
 ELSE	NO_DIR_DEFAULT=0;

SOURCE<35,1>=.FILBLK[FILE$PF_WILD_A];
		!Set the sign bit if wildcarding is allowed
		!Note that this will allow it everywhere in this fileblock
		!Including nodeid & device fields, so we may have to check
		!for it there later
WHILE 1 DO
	BEGIN
	N=RDSIXA(.SOURCE);	!Try to get a sixbit word
	SELECTONE CH$RCHAR(..SOURCE) OF SET
	[%C':']:	BEGIN
		IF CH$A_RCHAR(.SOURCE) EQL %C':'
			THEN %IF FTNETWORK %THEN BEGIN
				CH$RCHAR_A(.SOURCE);
				IF .FILBLK[FILE$PF_NODE_A] EQL 0
					THEN FPERROR(NODNAL);
					!Nodeid specified where none wanted
%IF FTWILD
	%THEN
				IF .FILBLK[FILE$PF_WILDN_A] EQL 0 THEN
				 (IF MSKW6(.N) NEQ -1 THEN FPERROR(WLNNAL));
				!Check for wildcarded nodeid if neccessary
	%FI
				IF .FILBLK[FILE$PF_NODE_D]
					!If none given so far, store it
					THEN BEGIN
						FILBLK[NDB$NODEID]=.N;
						FILBLK[FILE$PF_NODE_D]=0;
						!Remember we did so.
						END
					ELSE FPERROR(DUPNOD)
				END
				%ELSE FPERROR(NODNAL)
				%FI
				ELSE
				BEGIN
%IF FTWILD
	%THEN
				IF MSKW6(.N) NEQ -1 THEN FPERROR(ILLFSP);
	%FI
				IF .FILBLK[FILE$PF_DEV_D]
				!Store it if none given before
					THEN BEGIN
					FILBLK[FILE$DEVICE]=.N;
					FILBLK[FILE$PF_DEV_D]=0
					END

					ELSE FPERROR(DUPDEV)
				END;
			END;
	[%C'_']:	!Old form for NODEID
				%IF FTNETWORK %THEN BEGIN
					IF .FILBLK[FILE$PF_NODE_A] EQL 0
					THEN FPERROR(NODNAL);
					!Nodeid specified where none wanted
%IF FTWILD
	%THEN
					IF .FILBLK[FILE$PF_WILDN_A] EQL 0 THEN
					(IF MSKW6(.N) NEQ -1
						THEN FPERROR(WLNNAL));
					!Check for wildcarded nodeid if neccessary
	%FI
					IF .FILBLK[FILE$PF_NODE_D]
					!If none given so far, store it
					THEN BEGIN
						FILBLK[NDB$NODEID]=.N;
						FILBLK[FILE$PF_NODE_D]=0;
						!Remember we did so.
						CH$RCHAR_A(.SOURCE);
						!Go to next character
						END
					ELSE FPERROR(DUPNOD)
					END
				%ELSE FPERROR(NODNAL)
				%FI;
	[%C'.']:	BEGIN
			IF .FILBLK[FILE$PF_EXT_D] EQL 0 THEN FPERROR(DUPEXT);
			IF .N NEQ 0 THEN BEGIN
				IF .FILBLK[FILE$PF_NAME_D] EQL 0
					THEN FPERROR(DUPNAM);
				FILBLK[FILE$PF_NAME_D]=0;
				FILBLK[FILE$NAME]=.N
				END;
			FILBLK[FILE$PF_EXT_D]=0;
			CH$RCHAR_A(.SOURCE);	!SKIP OVER '.'
			FILBLK[FILE$EXTENSION]=SWAP(RDSIXA(.SOURCE))
			END;
	[%C'[',%C'(']:	BEGIN
			IF .N NEQ 0 THEN BEGIN
				IF .FILBLK[FILE$PF_NAME_D] EQL 0
					THEN FPERROR(DUPNAM);
				FILBLK[FILE$PF_NAME_D]=0;
				FILBLK[FILE$NAME]=.N
				END;
			IF .FILBLK[FILE$PF_DIR_D] EQL 0 THEN FPERROR(DUPDIR);
			N=RDDIR(.FILBLK,.SOURCE); IF .N NEQ WIN
				THEN FPERROR(.N);
			FILBLK[FILE$PF_DIR_D]=0
			END;
	[%C'/',%C'=',%C',',%O'15',%O'33',%O'12',0]:
			BEGIN
			IF (.N NEQ 0) THEN BEGIN	![11] Test whole word
				IF .FILBLK[FILE$PF_NAME_D] EQL 0
					THEN FPERROR(DUPNAM);
				FILBLK[FILE$PF_NAME_D]=0;
				FILBLK[FILE$NAME]=.N
				END;
			IF .FILBLK[FILE$PF_DIR_D] AND .NO_DIR_DEFAULT
			 THEN	FILBLK[FILE$LPPN]=0;
			 !Let the monitor do the defaulting
			RETURN WIN
			END;
	[OTHERWISE]:	FPERROR(ILCFSP);!Illegal filespec charcter
		TES;
	END;
END;	!FPARSE

GLOBAL ROUTINE RDDIR(FILBLK,SOURCE)=
!Routine to fill in a PPN and SFD's
!uses .SOURCE as a byte pointer, returns it updated past directory
!FILBLK is a file block to fill in
BEGIN
	LOCAL C;	!TEMP
	LOCAL SFDPTR;	!KEEP TRACK OF WHAT SFD LEVEL WE GOT TO
	LOCAL N;	!TEMP

	MAP FILBLK: REF NDB;
	C=CH$RCHAR(..SOURCE);
	IF ((.C EQL %C'[') OR (.C EQL %C'(')) THEN CH$RCHAR_A(.SOURCE);
	IF (C=CH$RCHAR(..SOURCE)) EQL %C'-'	!Default directory
		THEN BEGIN
		C=CH$A_RCHAR(.SOURCE);
		IF (.C EQL %O'15') OR (.C EQL %O'12') OR (.C EQL %C'=')
		 OR (.C EQL %C'/') OR (.C EQL 0) THEN RETURN WIN;

		IF (.C EQL %C']') OR (.C EQL %C')')
			THEN BEGIN
			CH$RCHAR_A(.SOURCE);	!Skip past it
			RETURN WIN		!it was good
			END;
		ERROR(ILLDIR)			!"[-" followed by garbage
		END;
	IF (.C EQL %C'*') AND .FILBLK[FILE$PF_WILD_A] 	!Wildcard project
		THEN (IF .FILBLK[FILE$PF_WILD_A] THEN N=%O'777777'
						 ELSE ERROR(WLDNAL))
		ELSE N=RDNUMA(.SOURCE,8);	!PDP-10 PPNS ARE OCTAL
	IF .N NEQ 0 THEN FILBLK[FILE$PROJECT]=.N;
	IF CH$RCHAR(..SOURCE) NEQ %C',' THEN ERROR(ILLDIR);
	CH$RCHAR_A(.SOURCE);
	IF (CH$RCHAR(..SOURCE) EQL %C'*')	!Wildcard programmer number
		THEN (IF .FILBLK[FILE$PF_WILD_A] THEN N=%O'777777'
						 ELSE ERROR(WLDNAL))
		ELSE N=RDNUMA(.SOURCE,8);	!PDP-10 PPNS ARE OCTAL
	IF .N NEQ 0 THEN FILBLK[FILE$PROGRAMMER]=.N;
	INCR SFDPTR FROM FILBLK[FILE$SFD] TO FILBLK[FILE$SFD]+SFDMAX DO BEGIN
		SELECTONE CH$RCHAR(..SOURCE) OF SET
		[%C']',%C')']:	(CH$RCHAR_A(.SOURCE);.SFDPTR=0; RETURN WIN);
		[%O'15',%O'33',%O'12',0,%C'=',%C'/']:(.SFDPTR=0; RETURN WIN);
		[%C',']:(CH$RCHAR_A(.SOURCE);	.SFDPTR=RDSIXA(.SOURCE));
						!GOT AN SFD
		[OTHERWISE]:	ERROR(ILLDIR);
		TES;
		END;
	ERROR(ILLDIR);	!TOO MANY SFD'S IF WE GOT HERE
	END;




GLOBAL ROUTINE RDNUMA(SOURCE,RADIX)=
!Using ..SOURCE as a byte pointer, get an octal number and return it
!and update the byte pointer past it. RADIX is the radix.
BEGIN
LOCAL C;	!LAST CHARACTER READ
LOCAL N;	!THE NUMBER SO FAR
N=0;
WHILE 1 DO BEGIN
  C=CH$RCHAR(..SOURCE);
  SELECTONE .C OF SET
    [%C'0' TO %C'0'+(.RADIX)-1]: (N=(.N*.RADIX)+(.C-%C'0');CH$RCHAR_A(.SOURCE));
    [OTHERWISE]:	RETURN .N;
    TES;
  END;
END;!RDNUMA

GLOBAL ROUTINE RDSIXA(SOURCE)=
!Routine to read a sixbit word from an ascii string.
!SOURCE is:[RH] the ADDRESS of a byte pointer, (returned updated)
!	   [LH] The sign bit is set if wildcarding is allowed
!Value returned is the sixbit word.
BEGIN
LOCAL N;	!VALUE SO FAR
REGISTER C;	!Current character
LOCAL PTR;	!Pointer into N (sixbit)

PTR=CH$PTR(N,0,6);	!Initialize 6bit pointer
N=0;		!Start out with nothing

WHILE 1 DO BEGIN
	C=CH$RCHAR(..SOURCE);	!NEXT CHARACTER
	SELECT .C OF SET
	[LOWER_CASE]:	C=.C-%O'40';	!MAKE UPPER CASE
%IF FTWILD
	%THEN
	[WILD_1,WILD_N]:	IF (.SOURCE GTR 0) THEN RETURN .N;
			!Wildcard characters were not expected
	%FI
	[FILENAME_CHARS,WILD_N,WILD_1,LOWER_CASE]:
				BEGIN
				IF (.N AND %O'77') EQL 0 THEN
					CH$WCHAR_A(.C-%O'40',PTR);
				CH$RCHAR_A(.SOURCE)
				END;
				!Write & advance
	[OTHERWISE]:	RETURN .N;	!NO MORE FILENAME CONSTITUANTS.
	TES
	END;
END;	!RDSIXA

!Miscellaneous routines

GLOBAL ROUTINE SWAP(N)=(MACHOP(%O'207',0,N);.N);

GLOBAL ROUTINE PATH(FILBLK)=
!Routine to do a PATH. uuo on a FILE BLOCK
!FILBLK is a FILE BLOCK
BEGIN
REGISTER F;
MAP FILBLK: REF FILE_BLOCK;

F=XWD(SFDMAX+PATMIN,0)+FILBLK[FILE$PATH_FUN];
IF CALLI(F,%O'110') THEN %(Skip return)% RETURN WIN
	ELSE ERROR(PATDND+(.F AND %O'77'))
	!Error codes from uuo are 0 (DND) & -1 (NSF)

END;

MACRO BLT(F,Y,X,I)=MACHOP(%O'251',F,Y,X,I) %;

GLOBAL ROUTINE ZERO(LO,HI)=
!Routine to zero out some core via BLT
!LO & HI are the limits (inclusive)
BEGIN
REGISTER F;

F<LH>=.LO;
F<RH>=(.LO)+1;
.LO=0;
BLT(F,.HI);
END;

GLOBAL ROUTINE COPY(SOURCE,DEST,LEN)=
!Routine to copy a block of data
!Arguments:
!SOURCE:	source address
!DEST:	destination address
!LEN:	# of WORDS
BEGIN
REGISTER F;
IF .LEN NEQ 0 THEN BEGIN	!BLT will move 1 word always so check first
	F<LH>=.SOURCE;
	F<RH>=.DEST;
	BLT(F,.DEST+.LEN-1)
	END
END;
GLOBAL ROUTINE FUNPARSE(FILBLK,PTR)=
!Routine to generate an ASCIZ filespec string from a file block
!Arguments:
!FILBLK:	the file block
!PTR:		a byte pointer through which we store the result
!Returns:	value of above pointer, updated past filespec
BEGIN
MAP FILBLK: REF FILE_BLOCK;

WRSIXA(.FILBLK[FILE$DEVICE],PTR);
CH$WCHAR_A(%C':',PTR);		!DEV:
WRSIXA(.FILBLK[FILE$NAME],PTR);	!Filename
CH$WCHAR_A(%C'.',PTR);		!.
WRSIXA(SWAP(.FILBLK[FILE$EXTENSION]),PTR);	!Extension
IF .FILBLK[FILE$PPN] NEQ 0 THEN BEGIN
	CH$WCHAR_A(%C'[',PTR);
	WRNUMA(.FILBLK[FILE$PROJECT],8,PTR);
	CH$WCHAR_A(%C',',PTR);
	WRNUMA(.FILBLK[FILE$PROGRAMMER],8,PTR);
	INCR S FROM FILBLK[FILE$SFD] TO (FILBLK[FILE$SFD])+SFDMAX DO
		BEGIN
		IF ..S EQL 0 THEN EXITLOOP;
		CH$WCHAR_A(%C',',PTR);
		WRSIXA(..S,PTR);		!Write out the SFD's
		END;
	CH$WCHAR_A(%C']',PTR);
	END;
CH$WCHAR(0,.PTR);			!Make ASCIZ string
.PTR					!Return value of pointer
END;

GLOBAL ROUTINE WRSIXA(N,PTR)=
!Routine to convert SIXBIT to ASCII
!Arguments:
!N:	Sixbit word
!PTR:	ADDRESS of byte pointer. Pointer is incremented past data written
BEGIN
LOCAL T;	!Most recent character
LOCAL SIXPTR;	!Pointer within SIXBIT word
LOCAL NN: VECTOR[2];

NN[1]=0;	!Force zero terminator
NN[0]=.N;
SIXPTR=CH$PTR(NN,0,6);
UNTIL ((T=CH$RCHAR_A(SIXPTR)) EQL 0) DO
	BEGIN
	CH$WCHAR_A(.T+32,.PTR)
	END
END;
GLOBAL ROUTINE WRSIX(N,PTR)=
!Routine to convert SIXBIT to ASCII
!Arguments:
!N:	Sixbit word
!PTR:	byte pointer.
BEGIN
LOCAL T;	!Most recent character
LOCAL SIXPTR;	!Pointer within SIXBIT word
LOCAL NN: VECTOR[2];

NN[1]=0;	!Force zero terminator
NN[0]=.N;
SIXPTR=CH$PTR(NN,0,6);
UNTIL ((T=CH$RCHAR_A(SIXPTR)) EQL 0) DO
	BEGIN
	CH$WCHAR_A(.T+32,PTR)
	END
END;
GLOBAL ROUTINE FFIOCH(FB)=
!Find a free I/O channel and return it
BEGIN
MAP FB: REF FILE_BLOCK;
EXTERNAL FBTBL: VECTOR[16];
	!map for free channels and table of file blocks in use
	!Channel 0 is never returned  (reserved for TTY & GETSEG)
LABEL LP;

NOINTS((
LP:	BEGIN
	INCR I FROM 1 TO 15 DO
	IF .FBTBL[.I] EQL 0 THEN BEGIN
		FBTBL[.I]=.FB;	!Mark the channel in use
		LEAVE LP WITH (FB[FILE$CHANNEL]=.I)	!Return channel number
		END;
	ERROR(NOIOCH)	!No free I/O channels left
	END	!LP
))!NOINTS
END;	!FFIOCH
GLOBAL ROUTINE RELEASE(FB)=
!Reset & release device & free channel
!Discards any open file & resets device
!FB:	 Address of FILE_BLOCK
BEGIN
MAP FB: REF FILE_BLOCK;
REGISTER FF;
MAP FF:INSTRUCTION;
EXTERNAL FBTBL: VECTOR[16];

FF=.FB[FILE$CHANNEL];
IF (.FF LEQ 0) OR (.FF GTR %O'20') THEN RETURN;
%IF FTSIG %THEN
	IF .FB[FILE$IB] NEQ 0 THEN INTERRUPTS(REMOVEC,.FB[FILE$IB]);
%FI
CALLI(FF,%O'117');	!RESDV. UUO
FF=0;			!Start with nothing
FF<INSTR_OPCODE>=%O'71';	!Opcode for RELEASE UUO
FF<INSTR_AC>=.FB[FILE$CHANNEL];	!Channel # in AC field
XCT(FF);	!Execute instruction in AC
FBTBL[.FB[FILE$CHANNEL]]=0;	!Mark this channel free
FB[FILE$CHANNEL]=-1;	!Wipe out channel # so we don't try to use it
%IF FTALLOC %THEN
BUFFREE(.FB[FILE$I_BRH]);	!Free the input buffer
BUFFREE(.FB[FILE$O_BRH]);	!and the output buffer
%FI
WIN
END;
GLOBAL ROUTINE RESETF(FB)=
!Reset a channel.  Does a RESDV. UUO on the channel specified in a file block

!
! Formal Parameters
!

!FB: Address of file block

!
! Returned Value
!

!none

BEGIN
MAP FB: REF FILE_BLOCK;
REGISTER R;

R=.FB[FILE$CHANNEL];
CALLI(R,%O'117');	!RESDV. UUO
END; !RESETF
GLOBAL ROUTINE OPENFILE(FB)=
!Routine to allocate buffer space and open a file
!This is called by the OPEN_? macros.

!
! Formal Parameters
!

!FB: address of file block

!
! Returned value
!

!SS$_SUCCESS if successful, error code if not.
!if FTSIG is on, an error condition will be signalled if unsuccessful

BEGIN
MAP	FB: REF FILE_BLOCK;

LOCAL	LEN,		!Length of block required for buffers
	ADDR,		!Address of prospective buffer space
	SAVEJBFF,	!Place to save this while we're fiddling
	FILOPVALUE;	!Save value returned by FILOP

%IF FTSIG %THEN
ROUTINE OPENFHANDLE(SIGNAL_ARGS,MECH_ARGS,ENABLE_ARGS)=
	!Condition handler for OPENFILE
	!Standard condition-handler arguments.
	!ENABLE_ARGS[1] is addr of addr of block of memory for buffers
	!	    [2] is addr of length of block of memory for buffers
	!	    [3] is the addr of the FILE BLOCK for the file being opened
	BEGIN
	MAP ENABLE_ARGS: REF VECTOR;
	BIND FB=.ENABLE_ARGS[3]: FILE_BLOCK;

	IF .SAVJBFF NEQ 0	!Don't leave bogus value in .JBFF
	THEN	BEGIN
		JBFF=.SAVJBFF;	!Restore .JBFF
		SAVJBFF=0;	!Clear so we don't use this by mistake
		END;

	IF (.FB[FILE$I_CBUFF] EQL 0) AND (.FB[FILE$O_CBUFF] EQL 0)
	 THEN	BEGIN	!The buffers were never built.
		FREE(..ENABLE_ARGS[1],..ENABLE_ARGS[2]);
		!Give back the space that would have become buffers
		END;
	SS$_RESIGNAL		!Pass the condition down
	END;	!OPENFHANDLE

ESTABLISH(OPENFHANDLE,ADDR,LEN,FB[FILE$START]);
			!Condition handler to restore .JBFF in case of trouble
%FI

%IF FTALLOC %THEN
LEN=BUFFALO(FB[FILE$START]);	!Find out how much we need
ADDR=ALLOC(.LEN);		!Allocate it
IF .SAVJBFF EQL 0 THEN SAVJBFF=.JBFF; !Save .JBFF [34] only if necessary
JBFF=.ADDR;			!Point it towards our block
%FI

FILOPVALUE=FILOP(FB[FILE$START]); !Open the file
%IF FTALLOC %THEN
IF .SAVJBFF NEQ 0		![34] Only restore if valid
THEN	BEGIN
	JBFF=.SAVJBFF;		!Restore .JBFF
	SAVJBFF=0;		!Clear so we don't use this by mistake
	END;
%FI
.FILOPVALUE			!Return whatever FILOP returned
END;	!OPENFILE
%( This routine is no longer needed (I hope)
GLOBAL ROUTINE SETSTS(FILBLK,STATUS)=
!Do SETSTS UUO on channel in FILBLK
BEGIN
MAP FILBLK: REF FILE_BLOCK;
REGISTER FF;
MAP FF: INSTRUCTION;

CASE .STATUS<0,4> FROM _IOASC TO _IOIMG OF SET
[_IOASC]:	FF=7;
[_IOBYT]:	FF=8;
[_IOIMG]:	FF=36;
[INRANGE,OUTRANGE]:	CRASH('?Illegal mode requested');
TES;
(FILBLK[FILE$I_PTR])<24,6>=((FILBLK[FILE$O_PTR])<24,6>=.FF);
	!We have to change the byte size ourselves
FF=0;
FF<INSTR_AC>=.FILBLK[FILE$CHANNEL];
FF<INSTR_OPCODE>=%O'60';	!SETSTS UUO
FF<INSTR_ADDRESS>=.STATUS;
XCT(FF);
WIN
END; !SETSTS
! The above routine is no longer needed
)%
%IF (FTALLOC) %THEN
GLOBAL ROUTINE FREEFB(NB)=
!Routine to RELEASE the file block, free the core & that of any associated blks
BEGIN
MAP NB: REF NDB;

RELEASE(.NB);	![31] Moved before INTFREE stuff since
		! RELEASE references the interrupt block

%IF FTSIG %THEN
	BEGIN
	LOCAL X: REF INT_BLOCK;
	IF (X=.NB[FILE$IB]) NEQ 0 THEN 
		BEGIN
		EXTERNAL ROUTINE INTFREE;
		BIND PB=.X[INT$PROCESS]: PROCESS_BLOCK;	!Find the owner
		IF .PB[P$NDB] EQL .NB THEN PB[P$NDB]=0;	!Get rid of pointer
		INTFREE(.X);FREE(.X,INT_LEN);	!Interrupt block
		END
	END;
%FI
IF .NB[FILE$PF_NODE_A] THEN
	BEGIN
	LOCAL X;
	IF (X=.NB[NDB$FB]) NEQ 0 THEN FREEFB(.X); !Do same to associated filblk
	IF (X=.NB[NDB$RENAME_FB]) NEQ 0 THEN FREEFB(.X); !& rename filblk
	IF (X=.NB[NDB$EQ]) NEQ 0 THEN FREE(.X,EQMSGSIZE);
			!Get rid of message that created us if it's there
	FREE(.NB,NDB_LEN);	!release the core
	END
ELSE	FREE(.NB,FB_LEN);
END;	!FREEFB
GLOBAL ROUTINE BUFFALO(FB)=
!Determine how much space to allocate for I/O buffers

!
! Formal Parameters
!

!FB: address of FILE BLOCK (should be set up, ready for OPEN)

!
! Returned value
!

!Required amount of space for buffers.
!Caller may allocate this much space,
! set .JBFF to this, open the file, then restore .JBFF

BEGIN
MAP FB: REF FILE_BLOCK;

REGISTER
	R;	!For DEVSIZ UUO

R=FB[FILE$MODE];	!Mode & device happen to be in just the right place
DEVSIZ(R);		!this gets default # of buffs,,size of buffs

((IF .FB[FILE$I_NBUFF] EQL -1 THEN .R<LH> ELSE .FB[FILE$I_NBUFF])
	+(IF .FB[FILE$O_NBUFF] EQL -1 THEN .R<LH> ELSE .FB[FILE$O_NBUFF]))
	*.R<RH>
	!Find out how many buffers we need
	!multiply by size, and return that value
END;	!BUFFALO


GLOBAL ROUTINE BUFFREE(HDR):NOVALUE=
!Free storage occupied by I/O buffers
!HDR: address of buffer ring header (a.k.a. buffer control block)
!Returns: none
BEGIN
FIELD BUFF_FIELDS=SET			!Fields within a buffer ring
	BUFF$STS=[-1,0,36,0],		!Status word for buffer
	BUFF$USE=[0,35,1,0],		!USE bit
	BUFF$NOEOR=[0,34,1,0],		!No-end-of-record bit
	BUFF$LEN=[0,18,9,0],		!Length of buffer - 2
	BUFF$NEXT=[0,0,18,0],		!Next buffer
	BUFF$COUNT=[1,0,18,0],		!Data count within buffer
	BUFF$DATA=[2,0,36,0]		!Data area
	TES;
LOCAL	B: REF BLOCK FIELD(BUFF_FIELDS); !Current position therein

IF (B=.(.HDR)<RH>) EQL 0 THEN RETURN;	!First buffer in ring (not set up if 0)
DO	BEGIN
	LOCAL BNEXT;
	BNEXT=.B[BUFF$NEXT];			!Save pointer to next buffer
	FREE(B[BUFF$STS],.B[BUFF$LEN]+2);	!Release the buffer
	B=.BNEXT;				!Walk down the list
	END UNTIL .B EQL .(.HDR)<RH>;		!Until we get back to the top
(.HDR)<RH>=0;			!Get rid of pointer to nonexistant buffers
END;	!BUFFREE
%FI	!End %IF FTALLOC
GLOBAL BIND ROUTINE
	BINIP=BINA,	!Keep the old names alive incase anyone cares
	BOUTIP=BOUTA,
	RDNUM=RDNUMA,
	RDSIX=RDSIXA;
END ELUDOM