.z80 title MML:BACKUP For large files extending over one floppy ;----------------------------------------------------------------------- ; BACKUP ; Program to backup large files extending over one floppy ; ; Author: D. Powys-Lybbe ; Written: July 1984 ; ; Procedure ; Load this program ; Respond as follows: ; > Source filename -------- ? ; > Destination disk ------- ? ; Files copied ; > Change floppy disk ; > Press return when ready ? ; ; Requires: Z80, Assemble with M80.COM link with LINK.COM ; ;--------------------------------------------------------------------- ; REVISIONS ; ;--------------------------------------------------------------------- bdos equ 5 dfcb equ 05ch dbuff equ 080h public d.reset, rdextent, rdblk, tstspace init: ld sp,stack ld c,12 ; BDOS Version Number call bdos ld (vers),a cp 22h ; test for 2.2 jr z,cpm cp 31h ; or 3.1 jr nz,not22 cpm: ld a,b cp 00h ; CP/M jr nz,not22 ld c,25 ; BDOS Return current disk call bdos ld (ddsk),a ; and save ld de,msg1 call string jp start not22: ld de,msg0 call string ld c,0 jp bdos ds 64 stack equ $ string: ld c,9 ;BDOS String output jp bdos finish: ld c,0 jp bdos ucse: cp 'a' ret c cp 'z'+1 ret nc add a,'A'-'a' ret ;-------------------------------------------------------------------------- msg0: db 'MUST USE CP/M 2.2 or 3.1','$' msg1: db 0ah,0dh,'MML:BACKUP V1.0 - Multi Floppy Disc backup of large files' db 0ah,0dh,'$' msg2: db 0ah,0dh,'> Source file description ---- ? ' db '$' msg3: db 0ah,0dh,'> Destination (or drive) ----- ? ' db '$' msg4: db 0ah,0dh,'> Destination diskname ------- ? ' db '$' msg5: db 0dh,0ah,'*** NO FILE ***','$' msg6: db 0dh,0ah,'*** PROGRAM ERROR ***','$' msg7: db 0dh,0ah,'*** Cannot create new file - perhaps disc is full','$' msg8: db 0dh,0ah,'*** READ ERROR','$' msg9: db 0dh,0ah,'*** WRITE ERROR - DISK or DIRECTORY is full','$' msg10: db 0dh,0ah,'*** Incorrect filename syntax','$' msg11: db 0dh,0ah wctxt: db ' . ','$' ; 12345678 123 msg12: db 0dh,0ah,'COPYING:','$' msg13: db 0dh,0ah,'*** Incorrect disk name syntax','$' msg14: db 0dh,0ah,'When ready, press ?','$' msg15: db 0dh,0ah,'*** The diskette is now full, please replaces with another disk','$' msg16: db 0dh,0ah,'*** The diskette can not be used as it is too full.' db 0dh,0ah,' Please replaces with another disk','$' msg17: db 0dh,0ah,'*** As the diskette cannot be erased' db 0dh,0ah,' Please replaces with another disk','$' msg18: db 0dh,0ah,'*** Destination file write protected' db 0dh,0ah,' OK to destroy files (Y/N) ?','$' wild: db 0 ; set to number of wildcards outstanding size: dw 0 ; size of file (last record number + 1) db 0 ; remaining to copy free: dw 0 ; free records on destination db 0 ; remaining for copy extent: dw 0 ; records per destination physical extent db 0 ; (should be zero) blocks: dw 0 ; remaining sectors in destination physical extent vers: db 0 ; 22h or 31h version numer srcfcb: ds 36 ; source FCB dstfcb: ds 36 ; destination FCB erafcb: ds 36 ; erase destination FCB ddsk: db 0 ; Default disk returned by BDOS dstdpb: ds 2 ; SPT dstbsh: ds 2 ; BSH, BLM dstexm: ds 1 ; EXM dstdsm: ds 2 ; DSM dstdrm: ds 2 ; DRM dstal: ds 2 ; AL0,AL1 lendpb equ $-dstdpb cbuff: db 16 cbuff1: db 0 cbuff2: ds 16 ;-------------------------------------------------------------------------- ;----------------------; ; MAIN PROCESSING LOOP ; ;----------------------; start: ld sp,stack ld a,(wild) ; test for wild card or a jp nz,dowild ;----------------------- ; input SOURCE file name ;----------------------- ld hl,srcfcb ld de,srcfcb+1 ld bc,36-1 ld (hl),0 ldir ; fill with zeroes ld hl,srcfcb+1 ld de,srcfcb+2 ld bc,11-1 ld (hl),' ' ldir ; initialise filename to blanks ld de,msg2 call string ld de,cbuff ld c,10 ; BDOS Read console buffer call bdos ld hl,cbuff1 ld a,(hl) or a jp z,finish ; terminates program inc hl ld b,8+1 ; max number of bytes in filename ld de,srcfcb+1 ld c,a cp 2+1 jr c,s.name ; only allow drive if 3 or more chars entered inc hl ld a,(hl) cp ':' ; do we have drive dec hl jr nz,s.name ld a,(hl) call ucse sub 'A' jp c,badname cp 16 jp nc,badname inc a ; A = 1 dec de ld (de),a inc de inc hl inc hl dec c dec c s.name: ld a,c or a jr z,s.eofn ld a,(hl) cp '.' jp z,s.Fstop dec b jp z,badname ; too many characters entered call ucse ld (de),a inc hl inc de dec c jr s.name s.Fstop: ld de,srcfcb+9 inc hl dec c ld b,3+1 ; maximum number of type characters s.type: ld a,c or a jr z,s.eofn ld a,(hl) call ucse dec b jp z,badname ; too many characters entered ld (de),a inc hl inc de dec c jr s.type s.eofn: ; we have a filename ;--------------------; ; test for wild card ; ;--------------------; ld hl,srcfcb+1 ld b,11 next1: ld a,(hl) cp '?' jp z,setwild cp '*' jp z,setwild inc hl djnz next1 jp s.open ;----------------- ; open SOURCE file ;----------------- s.open: ld de,srcfcb ld c,15 ; BDOS open file call bdos cp -1 jp z,nofile ; no source file ld de,srcfcb ld c,35 ; BDOS compute file size call bdos ld hl,srcfcb+33 ; random record bytes ld de,size ld bc,3 ldir ; and save ld hl,0 ld (srcfcb+33),hl ; zero 3 bytes ld (srcfcb+34),hl ; random record pointer ;---------------------------- ; input DESTINATION drive name ;---------------------------- ld hl,dstfcb ld de,dstfcb+1 ld bc,36-1 ld (hl),0 ldir ; fill with zeroes ld hl,dstfcb+1 ld de,dstfcb+2 ld bc,11-1 ld (hl),' ' ldir ; initialise filename to blanks ld de,msg4 call string ld de,cbuff ld c,10 ; BDOS Read console buffer call bdos ld hl,cbuff1 ld a,(hl) or a jp z,finish inc hl ld b,8+1 ; max number of bytes in filename ld de,dstfcb+1 ld c,a cp 1 jr z,d.drive ; drive only entered inc hl ld a,(hl) dec hl cp ':' ; do we have drive jr nz,d.name d.drive: ld a,(hl) call ucse sub 'A' jp c,baddisk cp 16 jp nc,baddisk inc a ; A = 1 dec de ld (de),a inc de inc hl inc hl ld a,c cp 2+1 jr c,d.dronly ; only drive entered dec c dec c d.name: jp badname ; too many characters entered d.dronly: ; only drive name entered ld hl,srcfcb+1 ld de,dstfcb+1 ld bc,11 ldir ; copy source filename call maskt ; clear any flag bits set ;--------------------; ; test for wild card ; ;--------------------; ld hl,dstfcb+1 ld b,11 next2: ld a,(hl) cp '?' jp z,badname cp '*' jp z,badname inc hl djnz next2 ;--------------------; ; test for srce=dest ; ;--------------------; ld hl,dstfcb ld de,srcfcb ld b,12 next3: ld a,(de) and 7fh cp (hl) jr nz,d.init inc de inc hl djnz next3 jp badname ; same filenames d.init: call setera ; construct erase FCB call d.reset ; reset disc system jp d.open ; open file and copy ;-------------------------; ; clear any flag bits set ; ;-------------------------; maskt: ld hl,dstfcb+1 ld b,11 nobits: ld a,(hl) and 7fh ld (hl),a inc hl djnz nobits ret ;---------------------; ; construct erase FCB ; ;---------------------; setera: ld hl,dstfcb ld de,erafcb ld bc,36 ldir ; copy to erafcb ret page ; -------------- ; WILD CARD COPY ; -------------- ;------------------- ; initiate wild card ;------------------- setwild: xor a ld (wild),a ; set wildcard count to zero ;---------------------; ; WILD CARD filenames ; ;---------------------; ld hl,srcfcb+1 ld b,8 sname: ld a,(hl) cp '*' jr z,astxn inc hl djnz sname ld b,3 jr stype astxn: ld (hl),'?' inc hl djnz astxn ld b,3 stype: ld a,(hl) cp '*' jr z,astxt inc hl djnz stype jr sfirst astxt: ld (hl),'?' inc hl djnz astxt ;--------------------------------- ; do a search FIRST of default fcb ;--------------------------------- sfirst: ld (hl),0 ; set extend = 0 ; (this assumes extent 0 is exists) xor a ld (wild),a ld c,17 ; BDOS search for first ld de,srcfcb call bdos cp -1 jp z,nofile ; no file found call addfcb ; copy fcb into our FCB area ;---------------------- ; search for more FCB'S ;---------------------- snext: ld c,18 ; BDOS search for next ld de,srcfcb call bdos cp -1 jr z,builtwc ; no more files found call addfcb ; copy fcb into our FCB area jr snext ;------------------------------------ ; scanned all possible FCB's ;------------------------------------ ;---------------------------- ; input DESTINATION drive ;---------------------------- builtwc: ld hl,dstfcb ld de,dstfcb+1 ld bc,36-1 ld (hl),0 ldir ; fill with zeroes ld de,msg4 call string ld de,cbuff ld c,10 ; BDOS Read console buffer call bdos ld hl,cbuff1 ld a,(hl) or a jp z,finish ; unexpected termination inc hl ld de,dstfcb ld c,a cp 1 jr z,d.dr cp 2 jp nz,baddisk ; only 1 0r 2 characters allowed inc hl ld a,(hl) cp ':' ; do we have drive dec hl jp nz,baddisk ; 2nd character can only be a colon d.dr: ld a,(hl) call ucse sub 'A' jp c,baddisk cp 16 jp nc,baddisk inc a ; A = 1 ld (de),a ;--------------------------- ; scanned all possible FCB's ;--------------------------- ld hl,srcfcb ; construct erase FCB ld de,erafcb ld bc,36 ldir ; copy to erafcb ld a,(dstfcb) ld (erafcb),a ; set destination drive call d.reset ; reset disk system ld de,msg12 call string jp start ;----------------------------------------------------------- ; copy fcb from directory entry number into our FCB area ;----------------------------------------------------------- addfcb: ld l,a ld h,0 add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl ld de,dbuff add hl,de inc hl ; point to start of filename ex de,hl ; -> directory entry ld hl,wild inc (hl) ld a,(hl) ; FCB number dec a ; now offset call getblk ; return in address of next FCB block ld a,(srcfcb) ld (hl),a ; set drive inc hl ex de,hl ld bc,11 ldir ; copy directory entry to our entry ret ;----------------------------------------- ; return in address of next FCB block ;----------------------------------------- getblk: ld hl,fcbblk ld bc,12 ; size of fcb blk or a ret z nxtbk1: add hl,bc dec a jr nz,nxtbk1 ret ;---------------------- ; display next filename ;---------------------- dspwild: ld hl,srcfcb+1 ld de,wctxt ld bc,8 ldir inc de ld bc,3 ldir ld de,msg11 jp string ;--------------------- ; end of wildcard copy ;--------------------- nomore: xor a ld (wild),a jp start page ;----------------------- ; copy next wildcard FCB ;----------------------- dowild: ld a,(wild) or a jp z,wceof ; no more to send ;-------------------------------- ; fcbbk now contains (WILD) fcb's ;-------------------------------- ld hl,fcbblk ld de,srcfcb ld bc,12 ldir ; set up our default fcb ld h,d ld l,e inc de ld (hl),0 ld bc,36-12-1 ldir ; and fill rest with zeroes ;------------------- ; set up destination ;------------------- ld hl,srcfcb+1 ld de,dstfcb+1 ld bc,36-1 ldir call maskt ; clear any flag bits set ;------------------------------------------- ; now ripple all wild cards through to start ;------------------------------------------- ld hl,wild dec (hl) ld a,(hl) ; Number of FCB's remaining in table or a jr z,openwc ld de,12 ; size of fcb blk ld hl,0 ld b,a upblks: add hl,de djnz upblks ld b,h ld c,l ; size of transfer ld hl,fcbblk+12 ld de,fcbblk ldir ; and copy over openwc: call dspwild ; and display ;---------------------- ; open WILD SOURCE file ;---------------------- ld de,srcfcb ld c,15 ; BDOS open file call bdos cp -1 jp z,nofile ; no source file (or no extent 0) ld de,srcfcb ld c,35 ; BDOS compute file size call bdos ld hl,srcfcb+33 ; random record bytes ld de,size ld bc,3 ldir ; and save ld hl,0 ld (srcfcb+33),hl ; zero 3 bytes ld (srcfcb+34),hl ; random record pointer ;---------------------- ; open destination file ;---------------------- d.open: call setfree ; calculate free space call tstspace ; check enough room in destination jp c,fulldisc ld de,dstfcb ld c,22 ; BDOS Make file call bdos cp -1 jp z,noopen ld hl,0 ld (dstfcb+33),hl ld (dstfcb+34),hl ; set random record count to 0 jp rdextent ; and do transfer fulldisc: ld de,msg16 call string call waitcr call d.reset jp d.open ; and try again page ;------------------------ ; Prepare destination disk ;------------------------ d.reset: ld c,13 ; BDOS reset disk system call bdos ld a,(ddsk) ; default disk ld e,a ld a,(dstfcb) ; destination disk or a jr z,seldsk dec a ld e,a seldsk: ld c,14 ; BDOS select disk call bdos ld c,31 ; BDOS return DPB call bdos ld de,dstdpb ld bc,lendpb ldir ; copy DPB ; calculate records per extent ld hl,0 ld de,128 ; records per logical extent ld a,(dstexm) ; extent mask ld b,a inc b setext: add hl,de djnz setext ld (extent),hl ; set records per physical extent xor a ld (extent+2),a ; 3rd byte always zero ld a,(ddsk) ; default disk ld e,a ld c,14 ; BDOS select disk call bdos call erase ; erase target disk first ret z ; -ok- ;cant so start again ld de,msg17 call string call waitcr jp d.reset ;----------- ; erase disc ;----------- ;------------------------------- ; do a search FIRST of erase fcb ;------------------------------- erase: ld a,-1 ld (eraflag),a ; set era query flag true ld hl,erafcb+12 ; -> EX ld (hl),'?' inc hl inc hl ld (hl),'?' ; -> S2 (just in case) ld c,17 ; BDOS search for first ld de,erafcb call bdos cp -1 ret z ; no file found ;----------------------- ; test each erafcb entry ;----------------------- tstera: call tstwp ; test for write protect jr z,seran ; -no- call okera ret nz ; cant erase file call setrw ; make file read/write ;---------------------- ; search for more FCB'S ;---------------------- seran: ld c,18 ; BDOS search for next call bdos cp -1 jr nz,tstera ; more files found ld c,19 ; BDOS Delete file ld de,erafcb call bdos xor a ret ;------------------------------------------- ; test target file for Read/Only file ; (for CP/M Plus should also check password) ;------------------------------------------- tstwp: ld l,a ld h,0 add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl ld de,dbuff add hl,de ; -> fcb found ld a,(erafcb) ld (hl),a ; set drive ex de,hl ld hl,9 add hl,de ld a,(hl) ; t1 and 80h ; test Read/Only ret z ; -no- ex de,hl ld de,dfcb ld bc,32 ldir ; copy to default FCB or -1 ; [ro] to [rw] required ret ;----------------------------------- ; see if ok to erase read-only files ; returns Z - Yes, NZ - NO ;----------------------------------- eraflag: db -1 okera: ld hl,eraflag ld a,(hl) or a ret z ; already said yes xor a ld (hl),a waitok: ld de,msg18 call string ld de,cbuff ld c,10 ; BDOS Read console buffer call bdos ld hl,cbuff1 ld a,(hl) or a ret z ; continues program cp 1 jr nz,waitok inc hl ld a,(hl) cp 3 jp z,finish ; terminates cp 'Y' ret z cp 'y' ret z cp 'N' jr z,noera cp 'n' jp nz,waitok noera: or -1 ret ; NON-ZERO flag ;----------------------------------- ; set target file to Read/Write file ;----------------------------------- setrw: ld a,(dfcb+9) ; t1 and 7fh ; mask Read/Only ld (dfcb+9),a ld a,(dfcb+6) ; f6 and 7fh ; Do not set byte count ld (dfcb+6),a ld c,30 ; BDOS Set file attributes ld de,dfcb call bdos ld c,17 ; BDOS search for first ld de,dfcb call bdos ld hl,erafcb ld de,dfcb ld bc,32 ldir ; copy to default FCB ret ;---------------------------- ; Calculate space on new disc ;---------------------------- newdisk: call setfree ; calculate free space ld de,dstfcb ld c,22 ; BDOS Make file call bdos ret ;--------------------------------------------- setfree: ; calculate free space ;--------------------------------------------- ld a,(vers) cp 31h jp z,tst31sp ld a,(dstfcb) ; destination disk ld e,a dec e ld c,14 ; BDOS select disk or a call nz,bdos ; if not default ld c,27 ; BDOS22 Get alloc vector call bdos ex de,hl ; -> address of destination alloc xor a ld (free),a ld hl,(dstal) ld a,h ld h,l ld l,a ld a,(dstdsm) ; assume high dstdsm = 0 ld c,a inc c loop01: ld b,8 ld a,(de) or h loop02: add hl,hl add a,a jr c,next02 push hl ld hl,free inc (hl) pop hl next02: dec c jr z,done01 ; completed scan of alloc djnz loop02 inc de jp loop01 done01: ld a,(free) ; free data blocks ld l,a ld h,0 ld a,(dstbsh) ld b,a loop03: add hl,hl djnz loop03 ld (free),hl ; = number of 128 byte records xor a ld (free+2),a ld a,(dstfcb) ; destination disk or a ret z ; if default ld a,(ddsk) ; default disc ld e,a ld c,14 ; BDOS select default disk call bdos ret tst31sp: ld de,dbuff ld c,26 ; BDOS Set DMA transfer address call bdos ld a,(ddsk) ; default disk ld e,a ld a,(dstfcb) ; destination disk or a jr z,getsp dec a ld e,a getsp: ld c,46 ; BDOS Get Disk Free space call bdos ld hl,dbuff ld de,free ld bc,3 ldir ; save number of free records ret waitcr: ld de,msg14 call string ld de,cbuff ld c,10 ; BDOS Read console buffer call bdos ld hl,cbuff1 ld a,(hl) or a ret z ; continues program cp 1 jr nz,waitcr inc hl ld a,(hl) cp 3 jp nz,waitcr jp finish ; terminates page ;-----------------------------------------; ; COPY RECORDS FROM SOURCE TO DESTINATION ; ; both files are OPEN and ready to go ; ;-----------------------------------------; newblk: call nospace ; open new disc rdextent: call tstspace ; check enough room in destination jr c,newblk ; -no- ld hl,(extent) ; records per extent ld (blocks),hl ; maximum records to copy ;------------------------------------------------- ; read records for one destination physical extent ;------------------------------------------------- rdblk: ld de,srcfcb ld c,33 ; BDOS read randomly call bdos or a jp nz,rdnodata ; empty record ld de,dstfcb ld c,34 ;BDOS write random call bdos or a jp nz,wrerr ld hl,free ; free records call hl1dwn jp nxtrec ;------------------------------------------ rdnodata: ; empty record test for EOF ;------------------------------------------ ld b,a ; save result of random read ld hl,size ; last record number + 1 ld a,(hl) inc hl or (hl) inc hl or (hl) jp z,ateof ; at eof ld a,b ; recover read result cp 04 ; test for no FCB jr nz,onehole ;-------------------------------------------------------- ; missing logical extent so skip 128 records (16k extent) ;-------------------------------------------------------- ld c,128-1 nxtext: ld hl,srcfcb+33 call hl1up ; increment random record counter ld hl,dstfcb+33 ; (should be the same as source) call hl1up ; increment random record pointer ld hl,size ; records remaining to copy call hl1dwn ; reduce by one ld hl,(blocks) ; remaining records in extent dec hl ld (blocks),hl ; maximum records to copy dec c jr nz,nxtext ; continue to end of extent less one onehole: nxtrec: ld hl,srcfcb+33 call hl1up ; increment random record counter ld hl,dstfcb+33 call hl1up ; increment random record pointer ld hl,size ; records remaining to copy call hl1dwn ; reduce by one ld hl,(blocks) ; remaining records in extent dec hl ld (blocks),hl ; maximum records to copy ld a,h or l jp nz,rdblk ; and await next block jp rdextent ; read next extent ;------------------------------------------------- tstspace: ; check enough room in destination ;------------------------------------------------- ;1. check space remaining on dest disk ;2. compare with size of physical extent ; Return if greater or equal ;3. compare with remaining size of source disk ; Return if greater or equal ;4. if no room ; 1. close destination file ; 2. prompt user to change disk ; 3. create new file with current extent ; 4. repeat this module ; RETURNS: Carry set if not enough space ;------------------------------------------------- ; compare space remaining on disc with physical extent size ld de,free ; free records ld hl,extent ; records per extent ld a,(de) sub (hl) inc hl inc de ld a,(de) sbc a,(hl) inc hl inc de ld a,(de) sbc a,(hl) ret nc ; space on disk for one physical extent ; carry true when no space ; compare space remaining on disc with remaining sectors to transfer ld de,free ; free records ld hl,size ; last record number + 1 ld a,(de) sub (hl) inc hl inc de ld a,(de) sbc a,(hl) inc hl inc de ld a,(de) sbc a,(hl) ret ; NC - space on disk for remainder of file ; C - carry true when no space ;--------------------------- ; insufficient space on disk ;--------------------------- nospace: ld a,(dstfcb+15) ; destination record count byte or a jr z,done05 ; empty extent ld de,dstfcb ld c,16 ; close file call bdos cp -1 jp z,wrerr ld a,(dstexm) ; extent mask inc a ld b,a ld hl,dstfcb+12 ; offset to 1st EX byte loop05: inc (hl) djnz loop05 ld a,(hl) and 00100000b ; check for overflow jp z,done05 ; -no- ld a,(hl) and 11011111b ld (hl),a inc hl inc hl ; to S1 inc (hl) ; and increment overflow EX ;---------------------------------- ; open destination file on new disk ;---------------------------------- done05: ld de,msg15 call string call waitcr call d.reset call newdisk cp -1 jp z,noopen ld hl,(srcfcb+33) ld de,(dstfcb+33) ; reset random record count ld bc,3 ldir ret ;------------------------------- ; 3 byte increment and decrement ;------------------------------- hl1up: ld b,3 ; increment 3 bytes at by one hl1up1: inc (hl) ld a,(hl) or a ret nz inc hl djnz hl1up1 ret hl1dwn: ld b,3 ; decrement 3 bytes at by one hl1nxt: ld a,(hl) dec (hl) or a ret nz inc hl djnz hl1nxt ret ;------------ ; reached EOF ;------------ ateof: ld de,dstfcb ld c,16 ; close file call bdos cp -1 jp z,wrerr ld de,srcfcb ld c,16 ; close file call bdos ; done file transfer jp start ; (or finish) wceof: jp start ; (or finish) ;--------; ; ERRORS ; ;--------; nofile: ld de,msg5 call string ld a,0 ld (wild),a jp start errfile: ld de,msg6 call string ld a,0 ld (wild),a jp start noopen: ld de,msg7 call string ld a,0 ld (wild),a jp start rderr: ld de,msg8 call string ld a,0 ld (wild),a jp start wrerr: ld de,msg9 call string ld a,0 ld (wild),a jp start badname: ; too many characters entered ld de,msg10 call string jp start baddisk: ; too many characters entered ld de,msg13 call string jp start ;---------------------------------------------------------------------- ;--------------------------------------------------------------------- fcbblk: ds 12*128 ; space for up to 128 FCB entries ;--------------------------------------------------------------------- end