/* ---------------------------------------------------------------------------- R D M S . C =========== This program reads MS-DOS formatted disks using the CPM BIOS routines. The code was originally written so that it could be compiled with Small-C, but has been slightly modified for Hi-Tech C (and other "K&R" C compilers). Author: Bob Alverson 8717 Empire Ct. Cincinnati, OH 45231 513-729-1676 V1.2 and later for CP/M Plus by: John Hastwell-Batten, Tesseract RCPM+, P.O. Box 242, Dural, NSW 2158, AUSTRALIA. ------------------------------------------------------------------------------- Version history --------------- 2.33 7 May 86 John Hastwell-Batten If running CP/M+ or CCP/M then the program now preserves the exact file length in the directory. Unfortunately we cannot do this for CP/M 2.2 or MP/M (??). **See implementation notes below** 2.32 5 May 86 John Hastwell-Batten Bug fix - RDMS v 2.31 opened output files in mode "w" instead of "wb". Played havoc with binary files which had every LF translated to CR LF. (Amazing! In the nine months since this program was released no-one had even noticed there was a problem!). 2.31 11 Aug 85 John Hastwell-Batten Some minor fixes: a. Program no longer reports erased files in a directory display. b. File length display was incorrect. c. Force a null entry at the end of each directory so that file name searches don't go beserk. 2.3 8 Aug 85 John Hastwell-Batten Added code to properly re-log the CP/M (destination) disk on termination of the program by ^Z at the "Enter filename: " prompt. N.B. Ending the program with ^C is not guaranteed to work! 2.2 21 Jul 85 John Hastwell-Batten Added wildcard recognition to find() routine to allow copying of more than one file at a time. 2.1 18 Jul 85 John Hastwell-Batten CP/M version dependencies are now determined at run-time. Configuration options now accessible via command line. N.B. CP/M 2.2 operation still untested. 2.0 11 Jul 85 John Hastwell-Batten Program now handles subdirectories. 1.6 9 Jul 85 John Hastwell-Batten All changes in this version are for generality and portability:- Added conditionals for three methods of handling double-sided disks. N.B. **** I have only tested HISECBIT and CYLINDER **** Added conditional for back-porting to CP/M 2.2 N.B. **** UNTESTED! **** 1.5 7 Jul 85 John Hastwell-Batten Corrected tables for handling 8-sector disks. 1.4 4 Jul 85 John Hastwell-Batten Added directory display. Allowed command-line selection of the MS-DOS disk drive. 1.3 2 Jul 85 John Hastwell-Batten Added "DEBUG" code and fixed handling of 2-sided disks. 1.2 19 Jun 85 John Hastwell-Batten Converted to Hi-Tech C & CP/M Plus. 1.1 9 Dec 83 Bob Alverson Add ability to read all four IBM formats 1.0 27 Nov 83 Bob Alverson First working version. ------------------------------------------------------------------------------- Notes on version 2.0 (JHB) ==================== The directory display now identifies another directory by listing it with a filename extension of ".dir" (lower case). Log into a subdirectory by typing its name (without the ".dir"). Go back to previous level by typing ".." as a file name. Note that all files copied from any directory end up in the same spot under CP/M. Notes on versions 1.2, 1.3 and 1.4 (JHB) ================================== I couldn't run this program in its original form. The BIOS calls were specific to CP/M 2.2 and I no longer have a 2.2 system. For me the first task was to convert the program to use BDOS function 50 instead. The program still did not run because it was expecting the BIOS to deblock the physical sectors and to return 128-byte sectors. The CP/M Plus BIOS deals with physical sectors and in normal operation the sector deblocking is done by the BDOS. Rather than destroy any chance of running this program on a 2.2 system, I added some simple deblocking logic to the routine which does actual disk reads. The next trick was to get the BIOS to read the correct physical sectors. There is no defined method in CP/M for dealing with multi-surface disks but I have tried to cater for some of the more common methods used by system implementors. Anyone attempting to adapt this program to another machine must know the way the BIOS selects sectors to be read from each disk surface. This program is known to work for systems where the second side of a disk is selected by setting the high-order bit of the sector number and for the more common case of "cylinder" recording. It has not been tested on a system which normally fills all of side 0 before writing side 1. Notes on back-porting to CP/M 2.2 (JHB) ================================= In earlier versions of this program I made no attempt to preserve CP/M 2.2 compatibility, but back-porting this program was really quite easy. Starting with version 2.0 I re-introduced code for CP/M 2.2 which I had modified beyond all recognition for the CP/M+ conversion, and allowed compilation of the program for either operating system. In version 2.1 both sets of code are compiled and the operating system dependencies are determined at run time. There is just one caveat: NONE OF THE CP/M 2.2 CODE IS TESTED! Notes on porting to other C compilers (JHB) ===================================== Hi-Tech C is a full "K&R" standard C compiler so porting this code to other K&R compilers should present very few problems. If you are planning to convert this program to non-standard dialects of C then please do not destroy K&R compatibility. In particular, if you convert to BDS-C, please use the set of #include files from CUG 39 to "fake" the compatibility. Please read the notes at the start of the bios3() function. Notes on preservation of exact file lengths (JHB) =========================================== CP/M Plus and Concurrent CP/M make token provision for the recording of exact file lengths in the directory by allowing the S1 byte in the directory entry to contain the number of bytes in the last (128-byte) record of a file. Since the number of records is known, the exact file length can be calculated. The information is recorded in the directory via BDOS function 30, "set file attributes". The desired contents of the S2 byte are put into the CR (current record) byte of a properly formatted FCB and interface attribute F6 is set. BDOS function 30 copies the CR field into the S1 byte of the directory entry for the file from which it can be retrieved on an open (30), search first (17) and/or search next (18) BDOS call. To date, no program has ever used this feature and I now propose the following standard in order to be consistent and compatible with existing programs and the files they generate: To record the exact length of a file use BDOS function 30 to set the S1 byte to contain the number of UNUSED bytes in the last record of the file. No other program sets the S1 byte - it is always zero. A zero S1 byte should mean that the whole record contains valid data. The exact file length can be calculated as: (number of records * 128) - unused bytes in last record ------------------------------------------------------------------------------- Further development =================== v3.x Write to MS-DOS disk. ---------------------------------------------------------------------------- */ #define VERSION "2.32" #define DATE "7th May 1986" #define COPYRIGHT \ "Copyright\t(C)1984 Bob Alverson,\n\t\t(C)1985 John Hastwell-Batten" #include #define word unsigned short int #define ulong unsigned long int #define byte unsigned char #define begin { #define end } /* ---------------------------------------------------------------------------- The following #define, if enabled, causes the program to display all manner of diagnostic information. JHB ------------------------------------------------------------------------ */ /************* #define DEBUG *************/ /* ---------------------------------------------------------------------------- The following #defines determine the sector/side selection method used and expected by the CP/M BIOS. ---------------------------------------------------------------------------- */ #define NCYLS 40 /* This is not one of the three mentioned above */ /* Low-order bit of track number used to select side */ #define CYLINDER 'C' /* High-order bit of sector number used to select side */ #define HISECBIT 'H' /* Tracks 0-39 on side 0, tracks 40-79 on side 1 */ #define XSURFACE 'X' /* NO OTHER METHOD YET SUPPORTED BY THIS PROGRAM! */ /* ------------------------------------------------------------------------- */ /* Define the disk bios calls */ #define SELDSK 9 /* Select disk drive */ #define SETTRK 10 /* Select track */ #define SETSEC 11 /* Select sector */ #define SETDMA 12 /* Set DMA address */ #define RDSEC 13 /* Read sector */ #define WRSEC 14 /* Write sector */ #define SECTRAN 16 /* Translate sector # */ /* BDOS calls */ #define CURDSK 25 /* bdos selected disk */ #define GETVSN 12 /* bdos fn to get CP/M version */ #define LOGDSK 14 #define SETATT 30 /* disk parameters */ #define ALLMAX 1024 /* maximum alloc table size */ #define DIRMAX 7*512 /* maximum directory size */ #define MAXFILES 355 /* files which will fit on a disk */ /* misc data */ #define DATSIZ 0x4000 /* default data buffer size (16Kb) */ word datsiz = DATSIZ; /* global data */ char spttbl[4] = {64, 32, 72, 36}; /* sectors per track (logical) */ int spt; char sidestbl[4] = {2, 1, 2, 1}; /* tracks per cylinder */ char sides; char alltbl[4] = {4, 4, 8, 8}; /* size of alloc table / 128 */ int allsiz; char dirtbl[4] = {28, 16, 28, 16}; /* size of directory / 128 */ int dirsiz; int blktbl[4] = {8, 4, 8, 4}; /* size of block / 128 */ int blksiz; /* block size in 128-byte sectors */ int blklen; /* block size in bytes */ int secofs; /*------------------- File allocation Table information ----------------------- The File Allocation Table uses a 12-bit entry for each allocation unit on the disk. These entries are packed, two for every three bytes. The contents of entry number N is found by 1) multiplying N by 1.5; 2) adding the result to the base address of the Allocation Table; 3) fetching the 16-bit word at this address; 4) If N was odd (so that N*1.5 was not an integer), shift the word right four bits; 5) mask to 12 bits (AND with 0FFF hex). Entry number zero is used as an end-of-file trap in MS-DOS and is passed to the BIOS to help determine disk format. Entry 1 is reserved for future use. The first available allocation unit is assigned entry number two, and even though it is the first, is called cluster 2. Entries greater than 0FF8H are end of file marks; entries of zero are unallocated. Otherwise, the contents of a FAT entry is the number of the next cluster in the file. Clusters with bad sectors are tagged with FF7H. Any non-zero number would do because these clusters show as allocated, but are not part of any allocation chain and thus will never be allocated to a file. A particular number is selected so that disk checking programs know what to do (ie. a cluster with entry FF7H which is not in a chain is not an error). -----------------------------------------------------------------------------*/ byte fat[ALLMAX]; /* allocation map */ char dirBuf[DIRMAX+1]; /* directory data */ char *dir; char *datbuf, /* data transfer buffer */ *datptr; char *subDir; /* subdirectory buffer */ int cpmdsk; /* cpm default disk */ char dfile[20]; /* filename pulled from directory */ char xfile[20]; /* filename to transfer */ int drive; /* drive of MSDOS disk */ word blk; /* MSDOS block number */ int xtra, blks; /* file length */ long byteCount; FILE *cfile; /* output file stream */ byte CPMversion; char SIDkey[7] = {'M','e','t','h','o','d','='}; byte dsMethod = CYLINDER; byte foundF; /* Flags "file name found" */ /*MS-DOS directory entry structure */ #define DIRBIT 0x10 struct DirEnt { byte fn[8]; /* First byte has special meaning */ byte ft[3]; byte attrib; byte rsv[10]; /* Reserved for MS-DOS */ ulong stamp; /* Time/date stamp */ word start; /* Starting cluster number */ ulong filen; /* Length of file in bytes */ /* Total 32 bytes */ }; struct Fcb { byte drv; /* Drive code, 0=default, 1=A, 2=B etc. */ byte fn[8]; /* File name */ byte ft[3]; /* File name extension */ byte ex; /* Extent number */ byte s1; /* Will receive last record byte cound */ byte s2; /* Internal BDOS usage */ byte rc; /* Record number (record=128 bytes) */ byte rsv[16]; /* Group numbers (8*16-bit or 16*8-bit) */ byte cr; /* Current record within extent */ byte rr[3]; /* Random record number */ } cpmFcb; short int find(); short int wcMatch(); void compressFn(); void detType(); char *getName(); void fmtFileName(); void fmtbit(); void readblk(); void readdsk(); void readSec(); void readSec2(); void readSec3(); word nextBlock(); char *index(); /* Returns pointer to character in string */ void doDir(); word bios3(); void check2(); void setLength(); extern char *malloc(); /* Dynamic memory allocate function */ /*----------------------------------------------------------------------------- m a i n ======= -----------------------------------------------------------------------------*/ main(argc,argv) int argc; char *argv[]; { char getspace = 1; int ax; CPMversion = bdoshl(GETVSN,0); printf("\nRead MS-DOS diskettes under CP/M & CP/M+\n%s\nVersion %s - %s\n\n", COPYRIGHT, VERSION, DATE); if (CPMversion > 0x30) puts("Exact file lengths will be preserved in CP/M directory\n"); if ((subDir=malloc(MAXFILES*32+1)) == (char *)NULL) getspace = 0; else do if ((datbuf=malloc(datsiz)) == (char *)NULL) datsiz -= 0x0400; else getspace = 0; while (datsiz & getspace); if (getspace) { fputs("Insufficient memory to run this program!",stdout); exit(0); } #ifdef DEBUG else printf("Data buffer size is %dKb\n",datsiz>>10); #endif *(subDir+MAXFILES*32+1) = 0; /* Force null entry at end */ drive = -1; do { fputs("Drive of MS-DOS diskette: ",stdout); for (ax=1;ax < argc;++ax) if (argv[ax][0] == '-') switch(toupper(argv[ax][1])) begin case 'C': dsMethod = CYLINDER; break; case 'H': dsMethod = HISECBIT; break; case 'X': dsMethod = XSURFACE; break; end else drive = toupper(*argv[ax]); if (drive != -1) { putchar(drive = toupper(*argv[1])); putchar('\n'); drive -= 'A'; argc = 1; } else { fgets(xfile, 20, stdin); drive = toupper(*xfile) - 'A'; } if (drive < 0 || drive >= 16) { fputs("\n\7Drive spec error!\n", stderr); drive = -1; /* flag bad drive */ } } /* do */ while (drive < 0); cpmdsk = bdos(CURDSK, 0); bios3(SELDSK,0,drive,0,0); /* select disk drive */ detType(); #ifdef DEBUG printf("Reading allocation map (%d records)\n",allsiz); #endif readdsk(4, allsiz, fat); /* read allocation map in */ #ifdef DEBUG printf("Reading directory (%d records from logical sector %d)\n",dirsiz, allsiz*2+5); #endif readdsk(allsiz*2+4, dirsiz, dirBuf); /* read dir into memory */ dirBuf[dirsiz*128] = 0; /* force null entry at end */ doDir(dir=dirBuf); bios3(SELDSK,0,cpmdsk,1,0); while (getName() != (char *)NULL) if (!strlen(xfile)) doDir(dir); else begin foundF = 0; do switch(find(dir)) begin case 1: /* File found in directory */ printf("Copying %s (%ld bytes)\n",dfile,byteCount); cfile = fopen(dfile, "wb"); bios3(SELDSK,0,drive,1,0); datptr = datbuf; do begin readblk(blk, datptr); if (blks) { /* complete sector */ --blks; datptr += blklen; } else { /* partial sector */ datptr += xtra; xtra = 0; } if (datptr > datbuf+datsiz-blklen) { bios3(SELDSK,0,cpmdsk,1,0); fwrite(datbuf,1,datptr-datbuf,cfile); bios3(SELDSK,0,drive,1,0); datptr = datbuf; } /* if */ blk = nextBlock(blk); if ((blk&0xff0) == 0xff0) blks = xtra = 0; end /* do */ while (blks || xtra); bios3(SELDSK,0,cpmdsk,1,0); fwrite(datbuf, 1, datptr-datbuf, cfile); fclose(cfile); #ifdef DEBUG printf("Setting length to %ld\n",byteCount); #endif setLength(((byte)byteCount) & 0x7F); break; case 0: /* No such file */ if (!foundF) fputs("\nFile not found\n",stdout); foundF = 0; break; case -1: /* Sub-directory */ bios3(SELDSK,0,drive,1,0); datptr = subDir; do begin readblk(blk, datptr); if (blks) begin /* complete sector */ --blks; datptr += blklen; end blk = nextBlock(blk); if ((blk&0xff0) == 0xff0) blks = 0; end /* do */ while (blks); doDir(dir=subDir); break; case -2: /* Root directory */ doDir(dir = dirBuf); end /* switch */ while (foundF); end /* if */ /* while */ bios3(SELDSK,0,cpmdsk,1,0); bdos(LOGDSK,cpmdsk); } /* main */ /*----------------------------------------------------------------------------- d e t T y p e ============= Determines which of the four types of MS-DOS diskette is loaded by examining the first byte of the FAT. -----------------------------------------------------------------------------*/ void detType() { int dsktyp; char secbuf[128]; readSec(0,5,secbuf); dsktyp = (~ *secbuf) & 3; /* This converts the disk type byte to a number in the range 0 to 3:- FF -> 0 Two-sided, 8 sectors per track (32 @ 128 bytes) FE -> 1 One-sided, 8 sectors per track (32 @ 128 bytes) FD -> 2 Two-sided, 9 sectors per track (36 @ 128 bytes) FC -> 3 One-sided, 9 sectors per track (36 @ 128 bytes) */ spt = spttbl[dsktyp]; allsiz = alltbl[dsktyp]; dirsiz = dirtbl[dsktyp]; blksiz = blktbl[dsktyp]; sides = sidestbl[dsktyp]; blklen = blksiz*128; secofs = ((allsiz - blksiz) * 2) + dirsiz + 4; printf("Disk type = %02x (%sle-sided, %d Sectors/track)\n\n", (byte)*secbuf, (sides-1 ? "Doub" : "Sing"), (spt/sides)/4); #ifdef DEBUG printf("Allocation table size = %d\n",allsiz); printf("Directory size = %d\nBlock size = %d records (%d bytes)\n", dirsiz,blksiz,blklen); printf("Sector offset to beginning of data area = %d\n",secofs); #endif } /* detType */ /*----------------------------------------------------------------------------- g e t N a m e ============= Prompts for a file name. Dumps the answer into the character array, "xfile". -----------------------------------------------------------------------------*/ char *getName() { char *str, *ptr; fputs("\nEnter filename: ", stdout); str = fgets(xfile, 20, stdin); if (ptr=index(xfile, '\n')) *ptr = '\0'; return str; } /*----------------------------------------------------------------------------- r e a d b l k ============= Reads an MS-DOS block (cluster). -----------------------------------------------------------------------------*/ void readblk(blkno, bufptr) int blkno; char *bufptr; { #ifdef DEBUG printf("Reading block %d (%d 128-byte sectors)\n",blkno,blksiz); #endif readdsk((blkno * blksiz) + secofs, blksiz, bufptr); } /*----------------------------------------------------------------------------- r e a d d s k ============= Reads a series of (128-byte logical) sectors from an MS-DOS disk. -----------------------------------------------------------------------------*/ void readdsk(dsksec, numsec, bufptr) int dsksec, numsec; char *bufptr; { do { readSec(dsksec/spt, (dsksec%spt)+1, bufptr); bufptr += 128; ++dsksec; } /* do */ while (--numsec); /* do all sectors requested */ } /* readdsk */ /*----------------------------------------------------------------------------- r e a d S e c ============= "Reads" a single (128-byte) sector from an MS-DOS diskette. -----------------------------------------------------------------------------*/ void readSec(trk, sec, buf) int trk, sec; char *buf; { if (CPMversion < 0x30) readSec2(trk,sec,buf); else readSec3(trk,sec,buf); } /*----------------------------------------------------------------------------- r e a d S e c 3 & r e a d S e c 2 =============== =============== Depending on the host (CP/M) operating system version, one of routines is called by readSec() to do the physical disk read. There are two versions because CP/M versions 2.2 and 3.1 handle physical sector deblocking differently. -----------------------------------------------------------------------------*/ void readSec3(trk, sec, buf) int trk, sec; char *buf; { /* This procedure has been extensively modified to work under CP/M Plus. The rest of the program works in 128-byte sectors and this routine now does the disk sector deblocking from MS-DOS's 512-byte physical sectors. Also added code to handle both types of 2-sided disks properly. For this program to work your BIOS must at least handle 9 physical sectors per track! Each user of this program will have the task of modifying this procedure according to the way that his/her own BIOS handles double-sided disks. There is no standard method for dealing with multi-head disk drives. The BIOS figures it out for itself from the values you supply via the SETTRK and SETSEC calls. Your responsibility is to map the track/sector values that are passed by this program onto the corresponding values expected by your BIOS. */ static int lastTrk = -1; static int lastSec = -1; int retCode, physSec, logSec, track; char *bp, btm; static char phybuf[512]; physSec = (sec+3) / 4; logSec = (sec+3) % 4; track = trk; switch(dsMethod) { case HISECBIT: if (sides == 2 && sec > spt/2) physSec = (sec-spt/2+3)/4 + 128; break; case XSURFACE: if (sides == 2 && sec > spt/2) { physSec = (sec-spt/2+3)/4; track += NCYLS; } break; case CYLINDER: if (sides == 2) { track <<= 1; if (sec > spt/2) { ++track; physSec = (sec-spt/2+3)/4; } } break; default: check2(); } /* switch */ if ((physSec != lastSec) || (trk != lastTrk)) /* Need to do physical read */ { #ifdef DEBUG printf("Reading track %d sector %d\n",trk,physSec); #endif bios3(SETTRK,0,track,0,0); bios3(SETSEC,0,physSec,0,0); bios3(SETDMA,0,phybuf,0,0); do { if (retCode = bios3(RDSEC,0,0,0,0)) { char ask[3]; fputs("\nDisk read error\nAbort, Ignore or Retry? ", stdout); fgets(ask, 3, stdin); if (toupper(*ask) == 'I') retCode = 0; else if (toupper(*ask) == 'A') { bios3(SELDSK,0,cpmdsk,1,0); bdos(LOGDSK,cpmdsk); exit(); } } /* if */ } /* do */ while (retCode); } bp = &phybuf[logSec<<7]; /* bp = &phybuf[logSec*128] */ for(btm=128;btm--;) /* Copy logical sector out of */ *buf++ = *bp++; /* the physical sector buffer */ lastTrk = trk; /* Update the disk access determinants */ lastSec = physSec; } /* readSec3 */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ void readSec2(trk, sec, buf) int trk, sec; char *buf; { int retCode; switch (dsMethod) { case HISECBIT: if (sides == 2 && sec > spt/2) sec = (sec-spt/2+3)/4 + 128; break; case XSURFACE: if (sides == 2 && sec > spt/2) { sec = (sec-spt/2+3)/4; trk += NCYLS; } break; case CYLINDER: if (sides == 2) { trk <<= 1; if (sec > spt/2) { ++trk; sec = (sec-spt/2+3)/4; } } break; default: check2(); } /* switch */ bios3(SETTRK,0,trk,0); bios3(SETSEC,0,sec,0); bios3(SETDMA,0,buf,0); do { if (retCode = bios(RDSEC,0,0,0)) { char ask[3]; fputs("\nDisk read error\nIgnore or Retry? ", stdout); fgets(ask, 3, stdin); if (toupper(*ask) == 'I') retCode = 0; } /* if */ } /* do */ while (retCode); } /* readSec */ /*----------------------------------------------------------------------------- c h e c k 2 =========== This routine is only called if you have modified the program to communicate with the BIOS disk routines in a way not previously supported without including a "case" selector for the new method. -----------------------------------------------------------------------------*/ void check2() begin if (sides == 2) begin printf("I don't know how your BIOS handles 2-sided disks!"); exit(); end end /*----------------------------------------------------------------------------- n e x t B l o c k ================= Looks up the File Allocation Table and determines the next cluster in a file chain. -----------------------------------------------------------------------------*/ word nextBlock(block) word block; { word nblk; union { word *wp; byte *bp; } FAT; /* The allocation map is declared as a character array but really contains 12-bit things. */ FAT.bp = & fat[(block*3) >> 1]; nblk = *FAT.wp; if (block & 1) nblk >>= 4; return (nblk & 0x0fff); } /*----------------------------------------------------------------------------- d o D i r ========= Displays the current MS-DOS directory on the screen. -----------------------------------------------------------------------------*/ void doDir(dirBuf) char *dirBuf; { struct DirEnt *dirPtr = (struct DirEnt *)dirBuf; unsigned int names = 0; while (dirPtr->fn[0]) { if (dirPtr->fn[0] != 0xE5 && dirPtr->fn[0] != ' ') printf("%-8.8s.%-3.3s%s",dirPtr->fn, dirPtr->attrib & DIRBIT ? "dir" : (char *)(dirPtr->ft), ++names % 5 ? " | " : "\n"); ++dirPtr; } printf("\n%d files on disk\n",names); } /*----------------------------------------------------------------------------- f i n d ======= Finds file name [xfile] in directory buffer. If the search is successful then it puts the name of the file into [dfile] unless the file found is a directory (whereupon the action is quite different). The global item "foundF" controls the initialisation of the search. -----------------------------------------------------------------------------*/ short int find(dirBuf) char dirBuf[]; { char name[12]; static struct DirEnt *dirPtr; char wild; #ifdef DEBUG word fb; #endif fmtFileName(xfile, name); wild = (index(name,'?') != (char *)NULL); if (!foundF) dirPtr = (struct DirEnt *)(&dirBuf[0]); /* We skip over directories in a wildcard filename search otherwise we might miss a file with the same name as a directory. We also skip files whose names are null or begin with 0xE5 (erased files). */ #ifdef DEBUG printf("\nFile name @%04x: %-11.11s\n",&dirPtr->fn[0],&dirPtr->fn[0]); #endif while (dirPtr->fn[0] && (dirPtr->fn[0] == 0xE5 || dirPtr->fn[0] == ' ' || (!wcMatch(dirPtr->fn, name, 11) && dirPtr->fn[0]) || (wild && (*dirPtr).attrib & DIRBIT))) ++dirPtr; if ((*dirPtr).fn[0]) { blk = (*dirPtr).start; blks = (byteCount=(*dirPtr).filen) / blklen; xtra = (*dirPtr).filen % blklen; if ((*dirPtr).attrib & DIRBIT) if (blk == 0) /* Switching back to root directory */ { fputs("*** Root directory ***\n"); foundF = 0; return -2; } else /* Subdirectory */ blks = 0xFFF; #ifdef DEBUG printf("Reading %-8.8s.%-3.3s (%ld bytes) (blks=%d, xtra=%d)\n", &(*dirPtr).fn[0],&(*dirPtr).ft[0],(*dirPtr).filen,blks,xtra); printf("Blocks: %03x",blk); for (fb=blk;fb < 0xff7;) { fb = nextBlock(fb); printf(", %03x",fb); } putchar('\n'); #endif if ((*dirPtr).attrib & DIRBIT) begin foundF = 0; return -1; end else begin compressFn(dirPtr->fn,dfile); strncpy(cpmFcb.fn,dirPtr->fn,11); #ifdef DEBUG printf("CP/M file name: {%-11.11s}\n",cpmFcb.fn); #endif foundF = 1; ++dirPtr; return 1; end } return 0; /* not found */ } /*----------------------------------------------------------------------------- c o m p r e s s F n =================== This routine serves a vagary of the C runtime library which expects file names passed to an fopen() call to be in delimited display form. The file name(s) extracted from the MS-DOS directory are in FCB blank-filled format. This procedure compresses the file name so that fopen() can go ahead and un-compress it again! ----------------------------------------------------------------------------*/ void compressFn(np,cp) char np[], *cp; begin short int nx = 0; for(;nx<8 && np[nx] != ' ';) *cp++ = np[nx++]; *cp++ = '.'; for(nx=8;nx<11 && np[nx] != ' ';) *cp++ = np[nx++]; *cp = '\0'; end /*----------------------------------------------------------------------------- f m t F i l e N a m e ===================== This routine is essentially the reverse of compressFn() with a couple of specials. -----------------------------------------------------------------------------*/ void fmtFileName(text, buf) char *text, *buf; { if (!strcmp(text,"..")) strcpy(buf,".. "); else { if (text[1] == ':') text += 2; /* skip over drive */ fmtbit(&text, buf, 8); fmtbit(&text, buf+8, 3); } buf[11] = 0; } void fmtbit(tptr, dest, cnt) char **tptr; char *dest; int cnt; { char *text; text = *tptr; while (--cnt >= 0) { if (*text && *text != '.') if (*text=='*') *dest = '?'; else *dest = toupper(*text++); /* no macro allowed */ else *dest = ' '; ++dest; } while (*text && *text != '.') ++text; if (*text == '.') ++text; /* scan past . */ *tptr = text; } /*----------------------------------------------------------------------------- w c M a t c h ============= This is a wildcard string-comparison function used in scanning a directory for matches with an ambiguous file name. Returns TRUE if the match succeeds. -----------------------------------------------------------------------------*/ short int wcMatch(string,wcString,len) char *wcString, *string; short int len; begin short int match = 1; while (match && len--) if (*wcString != '?' && *string != *wcString) match = 0; else begin ++wcString; ++string; end return match; end /*----------------------------------------------------------------------------- b i o s 3 ========= This routine handles calls to the host system BIOS. On CP/M 2.2 systems it simply calls the compiler's run-time library function bios() but on CP/M 3.1 systems it uses BDOS function 50 instead. ----------------------------------------------------------------------------*/ word bios3(fn,aReg,bcReg,deReg,hlReg) byte fn, aReg; word bcReg,deReg,hlReg; { extern word bios(); extern word bdos(), bdoshl(); struct BiosPB { byte func; byte aVal; word bcVal; word deVal; word hlVal; } biosPB; if (CPMversion < 0x30) return bios(fn,bcReg,deReg); biosPB.func = fn; biosPB.aVal = aReg; biosPB.bcVal = bcReg; biosPB.deVal = deReg; biosPB.hlVal = hlReg; switch (fn) { case 9: /* SELDSK */ case 16: /* SECTRAN (not used by this program) */ case 20: /* DEVTBL (not used by this program) */ case 22: /* DRVTBL (not used by this program) */ return bdoshl(50,(char *)&biosPB); default: return bdos (50,(char *)&biosPB); } } /*----------------------------------------------------------------------------- s e t L e n g t h ================= Preserves the number of bytes in the last record of a file in the CP/M directory. Only works under CP/M+ and CCP/M. -----------------------------------------------------------------------------*/ void setLength(lrbc) byte lrbc; { byte *f = &cpmFcb.ex; if (CPMversion > 0x30) /* Sufficient test for CCP/M ???? */ { cpmFcb.drv = 0; cpmFcb.fn[5] |= 0x80; do *f++ = 0; while (f < &cpmFcb.cr); cpmFcb.cr = 128-lrbc; bdos(SETATT,&cpmFcb.drv); } }