****************************************************************** * * * CLOSE Command Handler * * * *----------------------------------------------------------------* * * * CMDCLOSE ($A2EA) is an extremely versatile routine. Not * * only is it entered when the CLOSE command is issued, but it is * * also called by many other DOS commands. In addition, it is * * frequently summoned when DOS encounters an error. * * The appeal of "CMDCLOSE" is two fold: * * 1) It uses the GETBUFF routine to locate a DOS buffer * * for some commands that don't use filenames (ex. * * catalog). * * 2) It acts as DOS's clean up committee by freeing up * * DOS buffers and any extra sectors that were * * previously allocated to a file but not needed. If * * necessary, it also updates the file size, fixes up * * links in the directory sector, and updates data, * * VTOC, T/S list, and directory sectors. * * Because CMDCLOSE is called by so many different commands, * * the conditions on entry to this routine can vary considerably. * * Program flow depends, in part, upon the status of the primary * * file name buffer (PRIMFNBF, $AA75) which is conditioned by the * * command processor. (See formatted disassembly titled * * "DOSCMDPARSING&PROCESSING" for more details.) * * If the calling command does not expect a filename, * * CMDCLOSE is entered with a $00 stored in the first byte of the * * primary file name buffer. Under these condtions, CMDCLOSE is * * simply used to access the GETBUFF ($A764) routine. GETBUFF * * then locates a free DOS buffer for use by the original calling * * command. * * If the CLOSE command is issued without an accompaning * * filename, the first byte of the primary file name buffer * * contains a space. This represents a signal to close all files * * via the CLOSEALL ($A316) routine. (CMDCLOSE is then exited * * via the "BEQ CLOSERTS" instruction at $A31E). * * The presence of a valid name in the primary file name * * buffer causes execution to flow into the GETBUFF subroutine. * * If the named file is already open, execution flows through * * CMDCLOSE twice. On the 1rst pass, the matching file name * * buffer is located via GETBUFF & then CLOSEONE ($A2FC) is used * * to close the file. CLOSEONE calls the file manager (FILEMGR, * * $AB06) to do the close function (FNCLOSE, $AC06). * * In order to understand the execution pattern of FNCLOSE, * * you must first be familiar with the writing process. When a * * file is being written, the data are transferred from their * * memory source locations to the DOS data sector buffer. Each * * time that the data sector buffer is filled up, it is written * * to the disk. If the last segment of data does not completely * * fill the buffer, the write command is exited with bit six of * * the update flag (UPDATFLG, $B5D5) set. When the file is * * subsequently closed, FNCLOSE tests UPDATFLG and writes the * * data sector buffer if necessary. A similar situation occurs * * with the T/S list buffer. FNCLOSE also tests bit seven of * * UPDATFLG to see if the T/S list requires updating. If the T/S * * list buffer has changed since the last read or write, it is * * written to the disk. * * After writing the data and T/S list buffers to the disk * * (if necessary), FNCLOSE calls the FIXMAP routine ($B2C3) to * * free up any sectors that were assigned in the VTOC but not * * used. (This step is necessary because the write process * * allocates an entire track to a file even if only a few sectors * * are required.) After the VTOC is updated on the disk, the new * * file size bytes are stored in the appropriate name field of * * the directory buffer. Finally the directory buffer is written * * to the disk. * * Once back in the command handler, execution jumps back to * * the start of CMDCLOSE. On this 2nd pass, a matching file name * * buffer is not found because the DOS name buffer was released * * on the 1rst pass. Therefore, A5L/H ($44,$45) is left pointing * * at the highest numbered (lowest in memory) FREE DOS buffer. * * CMDCLOSE ($A2EA) is then exited via EVENTXIT ($A2F1) and * * CLOSERTS ($A330).) * * If the named file was not already open on entry to * * CMDCLOSE, only one pass is made. This single pass resembles * * the second pass mentioned above. (P.S. Note that trying to * * close a named file that is not on the disk does not result in * * a file-not-found error. Instead, execution just follows the * * same route as that for an unopened file.) * * * ****************************************************************** (A2EA) CMDCLOSE LDA PRIMFNBF ;Get 1rst char from primary name buffer. CMP #" " ;Don't allow leading in name. (A2EF) BEQ CLOSEALL ;Leading = signal to close all files. ;(A close cmd was issued with no ;accompanying file name.) (A2F1) JSR GETBUFF ;Locate a DOS file buffer. * Locate buffer with same name. * If that fails, locate a free buffer. (A764) GETBUFF LDA #0 ;Default hi-byte of pointer to 0 STA A5L+1 ;(ie. assume no free buff available). (A768) JSR GETFNBF1 ;Get pointer to 1rst filename buffer in chain. (A792) GETFNBF1 LDA ADOSFNB1 ;First link to chain of DOS buffers LDX ADOSFNB1+1 ;(ie. pt at 1rst filename buffer in chain). (A798) BNE SETNXPTR ;ALWAYS. (A7A4) SETNXPTR STX A3L+1 ;Put addr of 1rst filename buffer in ptr STA A3L ;(ie. highest name buffer in chain). TXA ;Get hi-byte of addr in back in (a). GETNXRTN RTS (A7A9) (A76B) JMP FNCHAR1 ;Get first byte in DOS name buf. ------------ (A76E) GETFNLNK JSR GETNXBUF * Get addr of next filename buffer in chain * from chain pointers buffer offset 37 & 36 * bytes from 1rst char of present filename * buffer. (A79A) GETNXBUF LDY #37 ;Point the pointer at the chain buffer LDA (A3L),Y ;& pick up the addr of the next filename buffer. BEQ GETNXRTN ;If hi-byte is 0, then lnk zeroed out. TAX ;Save hi-byte in (x). DEY ;Pick up low-byte. LDA (A3L),Y SETNXPTR STX A3L+1 ;Stick addr of filename buffer in ptr. STA A3L TXA ;Get hi-byte back in (a). GETNXRTN RTS (A7A9) (A771) BEQ NOFNMTCH ;Link zeroed out, end of chain. FNCHAR1 JSR GETFNBY1 ;Get the 1rst char of filename from buf in chain. (A773) * Get first byte in DOS name buffer. (A7AA) GETFNBY1 LDY #0 ;Buffer is free if 1rst byte = $00. LDA (A3L),Y ;If buffer occuppied, the 1rst byte = 1rst (A7AE) RTS ;char of filename which owns buffer. (A776) BNE NXFNBUF ;Take branch if buffer wasn't free. LDA A3L ;Buffer was free, there4, point the A5L/H pointer STA A5L ;at the free buffer. LDA A3L+1 STA A5L+1 (A780) BNE GETFNLNK ;ALWAYS. (A782) NXFNBUF LDY #29 ;Buffer not free there4 compare name CMPFNCHR LDA (A3L),Y ;of owner with name of file in primary CMP PRIMFNBF,Y ;name buffer. (Start with last char first.) (A789) BNE GETFNLNK ;Char doesn't match, there4 look for another ;buffer that might have same name. (A78B) DEY ;That char matched, how bout rest of name? BPL CMPFNCHR ;30 chars in name (ie. 0 to 29). CLC ;Clr carry to signal match. (A78F) RTS ============ (A790) NOFNMTCH SEC ;Link zeroed out. (A791) RTS ============ (A2F4) EVENTXIT BCS CLOSERTS ;EVENTUALLY exit via this route. (A2F6) JSR CLOSEONE ;Matching file name was found, so close that file. * Close a specific file. (A2FC) CLOSEONE JSR CKEXCBUF * Check if current DOS name buffer * belongs to an EXEC file. After all, * don't want to close buffer if we are * using it to exec (ie. would be like * burying ourselves alive). (A7AF) CKEXCBUF LDA EXECFLAG ;Check to see if EXECing. BEQ NOTEXCBF ;No sweat - not running exec file. LDA EXECBUFF ;We are EXECing, there4 chk if addr of CMP A3L ;current filename buffer same as that BNE CKEXCRTN ;for buffer belonging to EXEC. LDA EXECBUFF+1 ;Maybe - low-bytes matched, now chk hi bytes. CMP A3L+1 BEQ CKEXCRTN ;Exec buffer = current buffer. NOTEXCBF DEX ;Not EXECing, there4 reduce (x) to make sure z-flag off. (A7C2) ;(P.S. (x) was orig conditioned to a large non-zero ;value on entry to GETFNBF1. There4, if now DEX then ;can be sure z-flag will be off.) CKEXCRTN RTS ;If execing, rtn with z-flag on. (A7C3) (A2FF) BNE FREEBUFF ;Not EXECing from this particular file. LDA #0 ;Closing an exec file so shut off the exec flag. STA EXECFLAG FREEBUFF LDY #0 ;Free up the DOS buffer by poking a $00 in TYA ;1rst byte of filename. STA (A3L),Y (A30B) JSR BUFS2PRM * GET addresses of the DOS buffers from chain * buffer & put them in FM parameter list. (A74E) BUFS2PRM LDY #30 ;Get addresses of FM Work buffer, ADRINPRM LDA (A3L),Y ;T/S list buffer, Data sec buffer and STA WRKBUFFM-30,Y ;NEXT filename buffer from the chain pointer INY ;buffer & put them in the FM parameter list. CPY #38 ;(P.S. addr of NEXT filename buffer is not BNE ADRINPRM ;used by DOS.) (A75A) RTS (A30E) LDA #2 ;Stick opcode for CLOSE function STA OPCODEFM ;in the FM parameter list. (A313) JMP FMDRIVER ;Do the function via file manager driver. ------------ (A6A8) FMDRIVER JSR FILEMGR ;Call the file manager to do the function. (AB06) FILEMGR TSX ;Save stk pointer so we can rtn to caller STX STKSAV ;(ie. so rtn to AFTRFUNC, ($A6AB).) (AB0A) JSR RSTRFMWA ;Copy contents of FM work buffer (in DOS chain) ;to FM work area (not in buffer chain). (AE6A) RSTRFMWA JSR SELWKBUF ;Find the FM work buffer. * Get the address of the FM work * buffer from the FM parameter * list & stick it in the A4L/H * pointer. (AF08) SELWKBUF LDA #0 (AF0A) BEQ PT2FMBUF ;ALWAYS. (AF12) PT2FMBUF LDA WRKBUFFM,X STA A4L LDA WRKBUFFM+1,X STA A4L+1 (AF1C) RTS (AE6D) LDY #0 ;Zero-out return code in FM parm STY RTNCODFM ;list to signal no errors. STORFMWK LDA (A4L),Y ;Copy the contents of the FM work buffer STA FMWKAREA,Y ;(in chain) to FM work area (non-chain). INY CPY #45 BNE STORFMWK CLC ;Why????? (AE7D) RTS (AB0D) LDA OPCODEFM ;Chk if opcode is legal. CMP #13 ;(Must be less than 13.) BCS TOERROP ;Opcode too large, go to range error. ASL ;Double val of opcode & get addr of TAX ;appropriate function handler from the table. LDA FMFUNCTB+1,X ;Stick the address on the stack (hi byte first) PHA ;& then do a "stack jump" to the appropriate LDA FMFUNCTB,X ;function handler. PHA (AB1E) RTS . . (AC06) . FNCLOSE . . (See dis'mbly of CLOSE function handler.) . . - if necessary, free up any sectors that were previously allocated to the file but not needed. - also updates the file size, fixes up links & updates data, VTOC, T/S list & directory sectors if necessary. . . (RTS) ============ TOERROP JMP RNGERROP ;Go handle range error. (AB1F) ------------ ;(See dis'mbly of errors.) * Return here after doing the close function * (Cause after @ function is done, use stack * to get back to original caller.) (A6AB) AFTRFUNC BCC FMDRVRTN ;(c) = 0 = no errors. LDA RTNCODFM ;Get error code from FM parameter list. CMP #5 ;End-of-data error? (A6B2) BEQ TOAPPTCH ;Yes - got a zeroed-out T/S link or a ;zeroed-out data pair in T/S list. ;(Not applicable to the close function.) (A6B4) JMP OTHRERR ;No - see dis'mbly of errors. ------------ (A6C3) FMDRVRTN RTS ;Return to caller of FMDRIVER. (A2F9) JMP CMDCLOSE ;Go back to point A5L/H at a free DOS buffer ------------ ;and exit via EVENTXIT and CLOSERTS. * Close all files (except an active exec file). (A316) CLOSEALL JSR GETFNBF1 ;Enter here when direct call or if 1rst char ;in primary filename field was a space. * Put address of 1rst DOS name buffer * (chain) in the A3L/H pointer. (A792) GETFNBF1 LDA ADOSFNB1 ;First link to chain of DOS buffers LDX ADOSFNB1+1 ;(ie. pt at 1rst filename buffer in chain). (A798) BNE SETNXPTR ;ALWAYS. (A7A4) SETNXPTR STX A3L+1 ;Put addr of 1rst filename buffer in ptr STA A3L ;(ie. highest name buffer in chain). TXA ;Get hi-byte of addr back in (a). GETNXRTN RTS (A7A9) (A319) BNE CKIFEXEC ;ALWAYS. CHKNXBUF JSR GETNXBUF (A31B) * Get addr of next filename buffer in chain * from chain pointers buffer offset 37 & 36 * bytes from 1rst char of present filename * buffer. (A79A) GETNXBUF LDY #37 ;Point ptr at chain buffer. LDA (A3L),Y ;Pick up hi byte of addr of nxt filename buffer. BEQ GETNXRTN ;If hi-byte is 0 then link zeroed out. TAX ;Save hi-byte in (x). DEY ;Pick up low-byte. LDA (A3L),Y SETNXPTR STX A3L+1 ;Stick addr of filename buffer in ptr. STA A3L TXA ;Condition z-flg in relation to hi byte . GETNXRTN RTS (A7A9) (A31E) BEQ CLOSERTS ;Link zeroed out, so now all files closed. (A320) ;NOTE: CLOSEALL exits via this branch. CKIFEXEC JSR CKEXCBUF * Check if current filename buffer * belongs to an EXEC file. (A7AF) CKEXCBUF LDA EXECFLAG ;Check to see if EXECing. BEQ NOTEXCBF ;No sweat - not running exec file. LDA EXECBUFF ;We are EXECing, there4 chk if addr of CMP A3L ;current filename buffer same as that BNE CKEXCRTN ;for buffer belonging to EXEC. LDA EXECBUFF+1 ;Maybe - low-bytes matched, now chk hi bytes. CMP A3L+1 BEQ CKEXCRTN ;Exec buffer = current buffer. NOTEXCBF DEX ;Not EXECing, there4 reduce (X) to make sure z-flag off. (A7C2) ;(P.S. (x) was orig conditioned to a large non-zero ;value on entry to GETFNBF1. There4, if now DEX then (A7C3) ;can be sure z-flag will be off.) CKEXCRTN RTS ;On exit: z-flag = 1 if execing. ; = 0 if not execing. (A323) BEQ CHKNXBUF ;EXEC WAS ACTIVE SO DON'T CLOSE ITS BUFFER ;OUT OR WILL END UP IN NEVER-NEVER-LAND. ;After all, don't want to close buffer ;if we are using it to exec (ie. would ;be like burying ourselves alive)!!! (A325) JSR GETFNBY1 ;Get 1rst char of filename from buf in chain. * Get first byte in DOS name buffer. (A7AA) GETFNBY1 LDY #0 ;Buffer is free if 1rst byte = $00. LDA (A3L),Y ;If buffer occuppied, the 1rst byte = 1rst (A7AE) RTS ;char of filename which owns buffer. (A328) BEQ CHKNXBUF ;This file is already closed, so go back to ;close the remaining files. (A32A) JSR CLOSEONE ;Close the open file. * Close a specific file. (A2FC) CLOSEONE JSR CKEXCBUF * Check if current filename buffer * belongs to an EXEC file. (A7AF) CKEXCBUF LDA EXECFLAG ;Check to see if EXECing. BEQ NOTEXCBF ;No sweat - not running exec file. LDA EXECBUFF ;We are EXECing, there4 chk if addr of CMP A3L ;current filename buffer same as that BNE CKEXCRTN ;for buffer belonging to EXEC. LDA EXECBUFF+1 ;Maybe - low-bytes matched, now chk hi bytes. CMP A3L+1 BEQ CKEXCRTN ;Exec buffer = current buffer. NOTEXCBF DEX ;Not EXECing, there4 reduce (X) to make sure z-flag off. (A7C2) ;(P.S. (x) was orig conditioned to a large non-zero value ;on entry to GETFNBF1 there4, if now DEX then ;can be sure z-flag will be off.) CKEXCRTN RTS ;Exit with z-flag = 1 if execing. (A7C3) ; = 0 if NOT execing. (A2FF) BNE FREEBUFF ;ALWAYS BRANCH IF CLOSEONE IS ACCESSED VIA CLOSEALL. LDA #0 ;Shut exec flag off. (A303) STA EXECFLAG ;NOTE: THIS INSTRUCTION IS NEVER CARRIED ;OUT IF ACCESSED VIA CLOSEALL. (An active exec file ;was already detected and skipped by the "BEQ CHKNXBUF" (A306) ;instruction at $A323.) FREEBUFF LDY #0 TYA ;Free up the DOS buffer by poking a $00 in the STA (A3L),Y ;1rst byte of the filename field. (A30B) JSR BUFS2PRM * GET addresses of the various DOS buffers from the * chain buffer & put them in FM parameter list. (A74E) BUFS2PRM LDY #30 ;Get addresses of FM Work buffer, ADRINPRM LDA (A3L),Y ;T/S list buffer, Data sec buffer and STA WRKBUFFM-30,Y ;NEXT filename buffer from the chain pointer INY ;buffer & put them in the FM parameter list. CPY #38 ;(P.S. addr of NEXT filename buffer is not BNE ADRINPRM ;used by DOS.) (A75A) RTS (A30E) LDA #2 ;Stick opcode for CLOSE function STA OPCODEFM ;in the FM parameter list. (A313) JMP FMDRIVER ;Do function via file manager driver. ------------ (A6A8) FMDRIVER JSR FILEMGR ;Call the file manager ;to execute the close function. (AB06) FILEMGR TSX ;Save stk pointer so we can rtn to caller STX STKSAV ;(ie. so can rtn to AFTRFUNC, $A6AB). (AB0A) JSR RSTRFMWA ;Copy contents of FM work buffer (in DOS chain) ;to FM work area (not in buffer chain). (AE6A) RSTRFMWA JSR SELWKBUF ;Find the FM work buffer. * Get the addr of the FM work buffer * from the FM parameter list & stick * it in the A4L/H pointer. (AF08) SELWKBUF LDA #0 (AF0A) BEQ PT2FMBUF ;ALWAYS. (AF12) PT2FMBUF LDA WRKBUFFM,X STA A4L LDA WRKBUFFM+1,X STA A4L+1 (AF1C) RTS (AE6D) LDY #0 ;Zero-out return code in FM parm list to STY RTNCODFM ;signal no errors as default condition. STORFMWK LDA (A4L),Y ;Copy the contents of the FM work buffer STA FMWKAREA,Y ;(in chain) to FM work area (non-chain). INY CPY #45 BNE STORFMWK CLC ;Why????? (AE7D) RTS (AB0D) LDA OPCODEFM ;Chk if opcode is legal. CMP #13 ;(Must be less than 13.) BCS TOERROP ;Opcode too large, got range error. ASL ;Double val of opcode & get addr of TAX ;appropriate function handler from the table. LDA FMFUNCTB+1,X ;Stick the address on the stack (hi byte first) PHA ;& then do a "stack jump" to the appropriate LDA FMFUNCTB,X ;function handler. PHA (AB1E) RTS . . (AC06) . FNCLOSE . . . (See dis'mbly of CLOSE function.) . . - if necessary, free up any sectors that were previously allocated to the file but not needed. - also updates the file size, fixes up links & updates data, VTOC, T/S list & directory sectors if necessary. . . (RTS) ============ (AB1F) TOERROP JMP RNGERROP ;Go handle range error. ------------ ;(See dis'mbly of errors.) * Return here after doing the close function * (Cause after @ function is done, use stack * to get back to original caller.) (A6AB) AFTRFUNC BCC FMDRVRTN ;(c) = 0 = no errors. LDA RTNCODFM ;Get error code from FM parameter list. CMP #5 ;End-of-data error? (A6B2) BEQ TOAPPTCH ;Yes - got a zeroed-out T/S link or a ;zeroed-out data pair in T/S list. ;(Not applicable to the close function.) (A6B4) JMP OTHRERR ;No - see dis'mbly of errors. ------------ (A6C3) FMDRVRTN RTS ;Return to caller of FMDRIVER. (A32D) JMP CLOSEALL ;Go to CLOSERTS via CLOSEALL. ------------ CLOSERTS RTS ;Exit to caller of close cmd. (Normally (A330) ============ ;returns to AFTRCMD ($A17D) located in the ;command parsing and processing routines.