;--------------------------------------------------------------------- ;SPOOL loader v1.3 (development version). ;Loads & initialises SPOOLER RSX ;--------------------------------------------------------------------- ; sectrs equ 64 ; Number of sectors to buffer in disk transfers. ; Sectrs=64 is an 8k buffer. ; dirno equ 256 ; Max no of directory entries on disk ; splusr equ 15 ; User no of spool directory ; pagmax equ 255 ; Maximum page width pagsiz equ 58 ; Default page length pagwid equ 96 ; Default page width (12 cpi) ; ;--------------------------------------------------------------------- ; RSX function nos reserved for communicating with spooler RSX. ; intspl equ 40 ; Install spooler RSX and setup spool directory getnxt equ 41 ; Return fcb for next file to add to queue qcopy equ 42 ; Copy filename entry to queue ; Data format is (nos in brackets are no of bytes): ; Drive(1),filename(8),extension(3),pos(1),size(3). getdir equ 43 ; Return spool directory drive and user no qlist equ 44 ; List spool queue abortp equ 45 ; Cancel one spooled file bugoff equ 46 ; Abort spool and uninitialise ; ;--------------------------------------------------------------------- ; External library routines ; extrn cmphd ; Compare HL & DE extrn dscan ; Scan directory for matches to an afn extrn fcbadr ; Get address of A'th fcb in array [HL] extrn conchr ; Print character in A extrn cstrng ; Print '$' terminated string [DE] extrn blkmov ; Block move B bytes from [DE] to [HL] extrn clrmem ; Clear B bytes from [HL] extrn rsxchk ; Return entry addr of next RSX with name [DE], starting ; At [BC], in [DE]. Also returns HL=BDOS entrypoint addr. extrn comchk ; Check if address in HL is within common memory extrn openf ; Open file extrn getbyt ; Return next byte from file, using buffered I/O extrn makef ; Create file extrn bufbyt ; Add byte to file extrn bufstr ; Add string to file extrn closef ; Write remainder of buffer & close file extrn pnamef ; Print filename [HL] extrn setarg ; Return command line parameter string extrn getarg ; Return next parameter extrn ascbin ; ASCII-Binary conversion routine extrn delchr ; Delete char C from string [HL], returning it in A extrn upchr ; Upshift character in A extrn fcbstr ; Convert fcb to string extrn bindec ; Convert binary to decimal string extrn filstr ; Return string of char B, length C ; public fxfer public fxfer2 public copyf public nfiles public fcbptr public rsxpb public drive public userno public spldrv public inblk public outblk public fcbs public biochk public error public pagef public line public tabexp public inf public outf public outstf public pageno public addhdr public ctfilt public paghdr public nomkr public stophi ; ;--------------------------------------------------------------------- ; Program Start ; jp gospol ; db 'CP/M+ Print Spooler Version 1.3.' db '(c) Andy Metcalfe, March 1989. ' ; gospol: ld sp,stack ld de,signon call cstrng ; Display signon message ld c,getver call bdos ; Version check cp 31h ld de,noplus jp c,error ; No go if V2.2 or earlier ; ld a,(cpmbuf) or a ; Was command line tail specified? ld de,nospec jp z,error ; Error & reboot if not ; ld e,0ffh ld c,user call bdos ; Get current user no ld (ourusr),a ; Saved ld (userno),a ; call getprm ; Decode command line parameters & set vars accordingly ld a,(userno) ld e,a ld c,user call bdos ; Go to user area of source file ; call getfls ; Search for files & store fcbs call setrsx ; Install or interrogate RSX for spool drive call fxfer ; Spool the files found by getfls ; ld a,(ourusr) ld e,a ld c,user call bdos ; Back to original user area ; ; Chain queue lister program (SPQ.COM) ; ld de,comand ld hl,cpmbuf ld b,4 call blkmov ; Transfer 4 byte command to default DMA buffer at 0080h ld c,chprog ; BDOS function 47 (Chain Program) ld e,0ffh ; Chain flag: leave default drive/user alone jp bdos ; Chain SPQ.COM (assumes default drive/user ; Or search chain etc set, otherwise warm boots) ; comand: db 'SPQ',0 ; CCP command line to chain queue lister ; ;--------------------------------------------------------------------- ; Jump here on error, with DE addressing an error message ; error: call cstrng ld a,(ourusr) ld e,a ld c,user call bdos ld c,wboot jp bdos ; ;--------------------------------------------------------------------- ; Extract & identify specified command line parameters ; Note: if none specified, uses defaults hardwired into var area ; These correspond to [p58,h,f,z,t,w96], ie page at 58 lines with header, ; filtering out control chars, reseting high bits & expanding tabs, ; to a maximum line width of 96 chars. ; To change the defaults, modify the relevant variables. ; Note that if ANY options are specified, the defaults are ; overridden. ; getprm: ld hl,cpmbuf ld de,parms call setarg ; [DE]=parameter string ld a,(de) ; A=parm string length or a ; Null? ret z ; Yes: use default options ; ; Parameters present: turn off defaults (except page width) before processing ; xor a ld (pageln),a ; Paging OFF ld (tabfl),a ; Tab expansion OFF ld (paghdr),a ; Page header OFF ld (ctfilt),a ; NO filtering of control chars ld (stophi),a ; Pass high bits ld (nofeed),a ; "No formfeed" OFF ld (nomkr),a ; "Object transfer" OFF ld a,(ourusr) ld (userno),a ; Source user area=current ; getpr2: ld hl,parms ld de,aparm call getarg ; String [DE]=one parameter ex de,hl ld c,(hl) ld a,(de) or c ; Test whether both strings null ret z ; Yes: all parms parsed getpr3: ld c,1 call delchr ; Delete & return 1st char call upchr ; Upshift it cp ' ' jp z,getpr3 ; Strip spaces cp 'F' jp z,setflt ; Filter out control chars cp 'G' jp z,getusr ; Get file from another user area cp 'H' jp z,sethdr ; Enable page header cp 'N' jp z,nfeed ; No formfeed at eof cp 'O' jp z,objxfr ; Object file transfer (ignore eof marker) cp 'P' jp z,pageit ; Page file cp 'T' jp z,exptab ; Expand tabs cp 'W' jp z,setwid ; Set page width (for page no display) cp 'Z' jp z,nohigh ; Strip high bits ; ld de,noparm jp error ; Unrecognised parameter: flag & quit ; setflt: ld a,0ffh ld (ctfilt),a jp getpr2 ; getusr: call ascbin ; Convert user no to binary in DE ld a,e ld (userno),a ; Store user no jp getpr2 ; Jump for next parameter ; sethdr: ld a,0ffh ld (paghdr),a jp getpr2 ; nfeed: ld a,0ffh ld (nofeed),a ; No trailing formfeed jp getpr2 ; objxfr: ld a,0ffh ld (nomkr),a ; Object file transfer = TRUE jp getpr2 ; pageit: call ascbin ; Extract page length ld a,e ld (pageln),a ; Stored jp getpr2 ; exptab: ld a,0ffh ld (tabfl),a ; Tab expansion ON jp getpr2 ; setwid: call ascbin ; Extract page width ld a,e ld (pagwd2),a ; Stored jp getpr2 ; nohigh: ld a,0ffh ld (stophi),a ; Strip high bits = TRUE jp getpr2 ; ;--------------------------------------------------------------------- ; Search for matches to specified filespec, and set up nfiles ; containing the number actually found. ; getfls: ld de,cpmfcb push de ld a,(de) ; A=spooled file(s) drive or a ; Test if drive specified jp nz,stadrv ; Yes: jump to store it ld c,curdsk call bdos ; Else store default drive inc a ; Need to add one for use in fcb stadrv: ld (drive),a ; Store it for later pop de ; DE addresses fcb containing afn ld hl,fcbs ; HL addresses fcb array for found files call dscan ; Check files there - with wildcards or a ; Check how many found ld de,nofile jp z,error ; Error if none there ld (nfiles),a ; Store no of files ret ; ;--------------------------------------------------------------------- ; Search for spooler RSX & check whether active, ; and if not, whether it can safely be installed. ; setrsx: ld hl,(bdos+1) ; HL=BDOS Vector address ld c,l ld b,h ; To BC as addr of 1st RSX (if any) ld de,rname call rsxchk ; Is attached SPOOLER RSX present? ; Returns [DE]=next RSX/LOADER, [HL]=BDOS entrypoint ex de,hl ; HL=addr of next RSX/BDOS or a ; Test result ld de,norsx jp z,error ; If no RSX, can't proceed (GENCOM required) ; push bc ; Save BC as required as start addr if no other RSX ld c,l ld b,h ; BC=entry addr of next RSX (or BDOS) ld de,rname call rsxchk ; Check for another RSX (installed/dormant/uninstalled) or a ; A=0 if no other SPOOLER RSX about pop de ; DE=start of lower (maybe only) RSX jp z,instal ; Not there: need to install attached RSX ; HL=BDOS entrypoint address, DE=attached RSX start addr ; ; May not need to install spooler RSX: ask it which drive the spool ; directory is located on. If the RSX is installed A is returned ; containing the drive no, and if not HL=00ffh, in which case the ; attached RSX must be installed. Note that if a resident RSX is ; waiting to uninstall, but has not yet done so, it will cancel ; this operation on receiving the getdir call. ; push hl push de ; Save BDOS & RSX start addresses ld a,getdir ld (rsxpb),a ld de,rsxpb ld c,calrsx call bdos ; Ask RSX for spool directory (H=user, L=drive) ld a,l ld (spldrv),a ld de,00ffh call cmphd ; HL=00ffh, ie call not intercepted by WORKING RSX? pop de pop hl ; Reload RSX & BDOS addresses ret nz ; No, so call intercepted & no installation needed ; ; Install RSX, given DE=RSX and HL=BDOS entrypoint addresses ; instal: ld (rsxpb+2),hl ; Store addr of BDOS entry point in parm block ex de,hl ; HL=RSX entry point addr call comchk ; Is the RSX in common memory? or a ld de,notcom jp z,error ; Can't proceed if not ; call biochk ; Have any of our BIOS jumps been diverted? or a ld de,bdivrt jp nz,error ; Can't proceed if BIOS jumps diverted by GET, PUT etc ; ; Find out temporary file drive (0 implies default) ; ld c,scblk ld de,scb50 call bdos ; A=temporary file drive (setup by SETDEF) or a jp nz,storit ; Jump to store it if set ld c,curdsk call bdos ; If no temporary drive, use default storit: ld (spldrv),a ; Store for later use ; ; Install spooler RSX ; ld a,intspl ld (rsxpb),a ; Store install code in RSX parm block ld a,(spldrv) ld (rsxpb+4),a ; Store temporary file drive for RSX ld a,splusr ld (rsxpb+5),a ; Store user no for RSX ld c,calrsx ld de,rsxpb ; BDOS entry point is at [rsxpb+2] jp bdos ; Tell the RSX to install itself & ret ; ;--------------------------------------------------------------------- ; Routine to check whether the BIOS jump table entries which ; are redirected by the SPOOLER RSX have been redirected to ; below the BDOS entry point. If this is the case, there is ; a competing process (GET, PUT or SUBMIT most likely) and ; the spooler cannot function. ; On entry, HL ,must contain the address of the BDOS entry point. ; A=0ffh is returned if a competing process is present, else A=00h. ; biochk: ld a,(bios+2) ; BIOS jump table addr high byte ld (b1+2),a ; Correct references to BIOS ld (b2+2),a ld (b3+2),a ld (b4+2),a ld (b5+2),a ; All references to BIOS correct ; ld a,(rsxpb+3) ; A=BDOS entry point high byte ; b1: ld hl,(bios+4) ; WBoot jump address cp h ; BDOS entry point should be below it jp nc,jmpmod ; BDOS>=jump addr, so it's been changed ; b2: ld hl,(bios+7) ; Const jump address cp h jp nc,jmpmod ; b3: ld hl,(bios+10) ; Conin jump address cp h jp nc,jmpmod ; b4: ld hl,(bios+16) ; List jump address cp h jp nc,jmpmod ; b5: ld hl,(bios+46) ; Listst jump address cp h jp nc,jmpmod ; ld a,0 ret c ; A=0 indicates jump table OK for spooler ; jmpmod: ld a,0ffh ; Else GET/PUT etc active - can't run spooler ret ; ;--------------------------------------------------------------------- ; Main loop to spool files: passes to spooler RSX one at a time ; fxfer: ld de,splmes call cstrng ; Print "spooling files" message ld a,1 ld (fcbptr),a ; Initialise file pointer ; fxfer2: ld a,(fcbptr) ; Number of files to spool ld hl,fcbs ; Address of start of fcb array call fcbadr ; DE=address of the fcb we want ex de,hl ld (splfcb),hl ; Saved ld a,getnxt ld (rsxpb),a ; RSX code to get next fcb ld hl,cpmfcb ld (rsxpb+2),hl ; Use CP/M fcb area as workspace ld c,calrsx ld de,rsxpb call bdos ; [cpmfcb] now contains fcb for next spool file or a ; A=0 signifies OK ld de,qfull jp nz,error ; Drop out if queue full - can't accept more files ; ld hl,(splfcb) ; [HL]=fcb of spooled file within array push hl ld a,(drive) ; Retrieve spooled file drive ld (hl),a ; Store drive in transfer fcb for open call copyf ; Copy file to spool directory ; ; Now produce queue entry from original fcb, filling in size entry ; pop hl ; HL=address of spooled fcb within array push hl ex de,hl ld c,size call bdos ; Compute file size (used in queue list) pop hl ld a,(userno) and a ; CY=0 rlca rlca rlca rlca ; High nybble of A is user no or (hl) ld (hl),a ; Low nybble is drive, high is user no ld de,13 add hl,de ; HL=start of size area in entry push hl ld de,(33-13) add hl,de ex de,hl ; DE=source (3 bytes giving next record in file) pop hl ; HL=destination (bytes 13-15) in queue entry ld b,3 ; 3 bytes to move call blkmov ; Do it ; ; [splfcb] now contains queue entry of type required by spooler RSX. ; Now tell the RSX to copy it to its list. ; ld a,qcopy ld (rsxpb),a ; Store "copy to queue" code in RSX parm block ld hl,(splfcb) ld (rsxpb+2),hl ; Pass setup entry to spooler RSX ld de,rsxpb ld c,calrsx call bdos ; Tell RSX to copy to queue ; File will be printed when it reaches front of queue ; ld hl,(splfcb) ld a,0ffh ; A=0ffh tells pnamef to print user no as well call pnamef ; Print name of spooled file ld a,cr call conchr ld a,lf call conchr ; Print ; ld hl,fcbptr inc (hl) ; Increment pointer ld hl,nfiles dec (hl) ; Decrement counter jp nz,fxfer2 ; Loop until all files spooled ret ; ;--------------------------------------------------------------------- ; Copy spooled file to temporary file drive (as set by SETDEF.COM), ; and spooler user area. ; copyf: ld hl,(splfcb) ex de,hl ; Source fcb for copy ld hl,inblk ld bc,inbuf ld a,sectrs call openf ; Open spooled file or a ld de,nopen jp z,error ; Abort if error ; MAY JUST FLAG: NOT ABORT WHOLE OPERATION ; ld e,splusr ld c,user call bdos ; Set user no to spool directory ; ld de,cpmfcb push de ld c,delete call bdos ; Delete file if present in spool directory (shouldn't be) pop de ld hl,outblk ; HL=file parameter block address ld bc,outbuf ; BC=buffer address ld a,sectrs call makef ; Create file in spool directory ; push af ld a,(userno) ; A=original user no ld e,a ld c,user call bdos ; Restore user no pop af ; or a ld de,nodir jp z,error ; Error & abort if no space in spool directory ; ; Copy spooled file to spool directory & user area ; ld a,1 ld (pageno),a ; Page number initially 1 ; ld hl,(splfcb) ; HL=address of source file fcb ex de,hl ; To DE for fcbstr ld hl,hdrstr ; Target string xor a ; No user no prefix call fcbstr ; String hdrstr = filename ; call pagef ; Copy the file, paging it if required ld a,(nofeed) or a ld a,fmfeed call z,outf ; Add formfeed to end of file (unless 'n' option active) ld e,splusr ld c,user call bdos ; To spool directory again for close ld hl,outblk call closef ; ...and close file ; push af ld a,(userno) ld e,a ld c,user call bdos ; Restore user no to original state pop af ; or a ; Error in close? ret nz ; No - return ld de,noclos jp error ; Abort if close failed ; DON'T ABORT ; ;--------------------------------------------------------------------- ; Routine to copy file, paging it if enabled ; pagef: ld a,(pageln) ; Page length ld c,a ; C=page length (used to check whether paging) or a jp z,pagef2 ; No header if no paging... ld a,(paghdr) or a jp z,pagef2 ; No header, so don't need to allow space for it dec c dec c ; If header enabled, 2 lines less push bc call addhdr ; Add header to file pop bc pagef2: ld b,c ; B=line count pagef3: push bc xor a ld (col),a ; (Input) column position initially 0 call line ; Copy a line between files pop bc ret z ; Drop out if eof ld a,c or a jp z,pagef3 ; Loop until eof if page length = 0 dec b jp nz,pagef3 ; Loop until end of page ; ; New page: add formfeed ; ld a,fmfeed call outf ; Add formfeed or a ld de,dskful jp z,error ; Disk full: quit jp pagef ; Next page ; ;--------------------------------------------------------------------- ; Add page header (filename & page no) to spool file. ; addhdr: ld hl,hdrstr call outstf ; Write filename to file ld c,(hl) ; C=string length ld a,(pagwd2) ; Page width (default or set by user) sub 8 ; Less 8 for 'Page xx' bit sub c ; A=no of spaces to right justify page no ld b,a ld c,' ' ld hl,blanks call filstr ; Return string of C spaces call outstf ; Buffer it ld hl,pagstr call outstf ; Buffer the "Page " string ld a,(pageno) ld e,a inc a ld (pageno),a ld d,0 ld hl,pagst2 call bindec ; Convert binary to decimal in string [HL] call outstf ; Buffer it ld a,cr call outf ld a,lf call outf ld a,cr call outf ld a,lf call outf ret ; ;--------------------------------------------------------------------- ; Routine to read a line from input file & write to output file ; line: call tabexp ; Get a byte, expanding tabs if enabled ld b,a ; Hold the byte a second ld a,e or a ; Test PHYSICAL eof flag ret z ; At eof, so return with Z=1 ld a,(nomkr) ; Object file transfer flag or a ; Test it ld a,b ; Get the read byte back again jp nz,fltchr ; If true, don't treat ^Z as eof marker cp cpmeof ret z ; Finished if eof marker found, and not 'o' option ; ; Strip high bit ('z' option) & control chars ('f' option) if enabled ; fltchr: ld b,a ld a,(stophi) or a ld a,b jp z,leavhi ; Don't strip high bit and 07fh ; Strip high bit if 'z' active leavhi: ld b,a ld a,(ctfilt) or a ld a,b jp z,neofch ; Don't strip control chars unless ctfilt is true cp ' ' ; Less than a space? jp nc,neofch ; No: leave alone cp cr jp z,neofch ; Let thru cp lf jp z,neofch ; ...... cp tab jp z,neofch ; ....and tab cp fmfeed jp nz,line ; Reject other control chars (except maybe formfeed) ld a,(pageln) or a ; Page length: 0 if paging disabled ld a,fmfeed jp nz,line ; Paging enabled: filter out formfeeds ; Else let it through ; neofch: cp cr jp z,noincr ; Don't increment column if ... cp lf jp z,noincr ; ...or ; ld hl,col inc (hl) ; Increment column (for tab expansion) ; noincr: push af call outf ; Buffer the byte (sets user area first) or a ; Successful? ld de,dskful jp z,error ; Disk full: quit pop af cp lf jp z,linend ; Finish if just found ld a,(pagwd2) ; Page width ld b,a ld a,(col) cp b jp nz,line ; Continue unless line is full xor a ld (col),a linend: dec a ; Unset Z flag ret ; ;--------------------------------------------------------------------- ; Routine to read bytes from the file & expand tabs, if the ; tabfl flag is true. ; tabexp: ld a,(tabfl) or a jp z,inf ; Just get the byte if not expanding a tab ld a,(col) ; Screen column and 07h jp z,tabex2 ; Jump to read byte if col=8,16,24,... ld a,(tabing) or a jp nz,tabex3 ; If currently expanding a tab, return space ; tabex2: xor a ld (tabing),a ; Not expanding a tab call inf ; Read a byte cp tab ; Is it a tab? ret nz ; No, so return it ld (tabing),a ; Set flag to indicate we're expanding a tab ; tabex3: ld a,' ' ; Return a space ret ; ;--------------------------------------------------------------------- ; Get a byte from the input file, setting required user area ; inf: push hl ld a,(userno) ld e,a ld c,user call bdos ; Go to source user area ; ld hl,inblk call getbyt ; A=read byte pop hl ret ; ;--------------------------------------------------------------------- ; Buffer byte A to output file, setting user area first ; Note that the user area is not reset by this routine ; outf: push hl push af ; Hold byte a second ld e,splusr ld c,user call bdos ; Go to spool directory user area pop af ; ld hl,outblk call bufbyt ; Buffer the byte pop hl ret ; ;--------------------------------------------------------------------- ; Buffer string [HL] to output file, setting user area first ; Note that the user area is not reset by this routine ; outstf: push hl ; Hold string address push hl ld e,splusr ld c,user call bdos ; Go to spool directory user area pop de ; DE=string addr (was HL originally) ; ld hl,outblk xor a ; A=0 stops bufstr adding trailing call bufstr ; Buffer the string pop hl ret ; ;--------------------------------------------------------------------- ; Program messages ; dseg signon: db cr,lf,'CP/M+ Print Spooler v1.3',cr,lf db '(c) March 1989 by Andy Metcalfe',cr,lf,lf,'$' ; splmes: db 'Spooling files:',cr,lf,lf,'$' ; noplus: db 'Sorry, this program requires CP/M+',cr,lf,'$' ; nospec: db 'ERROR: File not specified',cr,lf db 'The format for using spool is:',cr,lf,lf db 'SPOOL filename[option1,option2,option3...]',cr,lf,'$' ; nofile: db 'ERROR: File not found',cr,lf,'$' ; norsx: db 'ERROR: SPOOLER RSX not present',cr,lf db 'Add RSX to .COM file using GENCOM SPOOL SPOOLER ' db 'and try again','$' ; notcom: db 'ERROR: SPOOLER RSX is not in common memory',cr,lf db 'Remove any other RSX present and try again',cr,lf,'$' ; bdivrt: db 'ERROR: SPOOLER cannot function with redirected BIOS jumps' db cr,lf,'Try again later when SUBMIT/GET/PUT finished' db cr,lf,'$' ; noparm: db 'ERROR: unrecognised parameter in command line',cr,lf,'$' ; dskful: db 'ERROR: Insufficient disk space in spool directory',cr,lf,'$' ; nopen: db 'ERROR: Unable to open spooled file',cr,lf,'$' ; nodir: db 'ERROR: No directory space in spool directory',cr,lf,'$' ; noclos: db 'ERROR: Unable to close file in spool directory',cr,lf,'$' ; qfull: db 'ERROR: Spool queue full: unable to complete transfer',cr,lf,'$' ; ;--------------------------------------------------------------------- ; Scratch Pad Area ; rname: db 'SPOOLER ' ; Name of spooler RSX (used to determine its ; Address, if it's currently present) scb50: db 050h,0 ; SCB parameter block: get temporary file drive nfiles: ds 1 ; No of files found by directory scan fcbptr: ds 1 ; Pointer to next fcb within array rsxpb: ds 6 ; RSX parm block ourusr: ds 1 ; User area on entry drive: ds 1 ; Space to store spool file(s) drive spldrv: ds 1 ; Space to store temporary file drive inblk: ds 9 ; Input file parameter block outblk: ds 9 ; Output file parameter block splfcb: ds 2 ; Address of spooled file fcb within fcb array ; ; Parameter/formatting variables & flags ; parms: ds 256 ; String containing parms between '[' & ']' aparm: ds 256 ; Temporary string for each parameter ctfilt: db 0ffh ; Control character filtering flag paghdr: db 0ffh ; Page header flag stophi: db 0ffh ; Strip high bits flag nofeed: db 0 ; "No trailing formfeed" flag nomkr: db 0 ; "Object transfer" flag userno: ds 1 ; User no of source file(s) pageln: db pagsiz ; Page length (default hard coded, but can be set by user) pagwd2: db pagwid ; Page width (default hard coded, but can be set by user) tabfl: db 0ffh ; Tab expansion enable flag (set by user) tabing: db 0 ; "Tab expansion in progress" flag col: db 0 ; Column position for paging pageno: ds 1 ; Page no for current file hdrstr: ds 16 ; Space for header string (filename) blanks: ds pagmax+1 ; Space for string of spaces pagstr: db 5,'Page ' ; Page no header string (contiguous format) pagst2: ds 10 ; String to hold ASCII version of page no ; ds 100 ; Stack area stack equ $ ; ; FCB array for spooled files ; fcbs equ $ ; Must allow sufficient space for max no of directory ; Entries (256 for a 706k DSDD disk) ; ; File I/O Buffers ; inbuf equ $+dirno*36 outbuf equ inbuf+sectrs*128 ; end