From: jrs@panix.com (Jon Saxton)
Subject: Re: CP/M <-> DOS disks
Date: Tue, 14 Apr 1992 17:57:39 GMT
Organization: PANIX Public Access Unix, NYC

[References deleted]
>>
>>I am a new user and i could not find the FAQ for this newsgroup
>>So i'm sorry if i mess up your group.
>>
>>My question is the following:
>>
>>Does anybody out there know about the existance of a program which
>>runs under CP/M that can read DOS disks (360KB of course).

>Please respond to the group, as well as the original poster.
>I want to know this too.

OK, you asked for it.  After this little blurb follows a program called
RDMS written in C.  After compiling it, try

     rdms d:        (substitute name of drive holding MSDOS diskette for d:)

If that doesn't seem to work try

     rdms -h d:
or
     rdms -x d:
or
     rdms -c d:

If none of those seems to work then you'll need some very precise knowledge
of how the BIOS in your CP/M machine handles 2-sided diskettes.

I used this program a LOT in my CP/M days.  I even used to order CP/M software
on MSDOS diskettes because I always knew that the software distributors would
get that format right whereas I could never guarantee that they'd get my
system's native format correct.

There used to be a documentation file with this program but I have long since
lost it.  Has anyone checked the SIMTEL archives?  It may be in one of the
SIG/M volumes.

Here is the program source code in 'shar' format.  Please ignore the address
given in the source code as I now live in New York.


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