home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 8 Other
/
08-Other.zip
/
eidete19.zip
/
CDTEST.CPP
next >
Wrap
C/C++ Source or Header
|
1995-10-29
|
25KB
|
950 lines
/* CDtest 1.2
Tests if simultaneous I/O corrupts CD-ROM reading. If so, likely causes are the
PCI RZ-1000 EIDE chip, the CMD-640 EIDE chip or a faulty DMA controller.
Runs under DOS, DESQview, Windows, NT and OS/2 in DOS emulation mode.
This program may be copied freely and used for any purpose except military.
(c) copyright 1995 by
Roedy Green
Canadian Mind Products
#601 - 1330 Burrard Street
Vancouver, BC Canada V6Z 2B8
(604) 685-8412
Internet Roedy@bix.com
compiled under SMALL model
It works by reading a set of files repeatedly from the CD-ROM drive
and computing a checksum on each file. It reads the same set of
files multiple times with as much I/o as possible
interfering. It ensures the same checksum is computed each time.
Version history
1.2 - consistent use of term CMD-640.
1.1 - add warning about turnin on background execution.
1.0 - first version, cloned and modified from EIDEtest
*/
/* D E F I N E S */
#define BufferSizeInBytes 31744
/* size of buffer for reading the files.
This size should generate lots of physical I/O.
Smaller sizes might come out of cache.
Bigger sizes do only a few big I/Os */
#define TestSizeInBytes 7616560L
/* aggregate bytes to read for the test per pass, spread over many files. */
#define MaxFiles 2000
/* maximum number of files on CDROM that will be processed.
Up to this many will have checksums computed.
Usually TestSizeInBytes will be the limiting factor. */
#define StartDecorate "\xb0\xb1\xb2\xdb"
/* line of pretty blobs in oemfont to attract attention */
#define EndDecorate "\xdb\xb2\xb1\xb0"
#define Esc '\x1b'
/* T Y P E D E F S */
typedef unsigned short WORD;
typedef unsigned char BYTE;
typedef enum BOOL
{
FALSE, TRUE
} BOOL;
typedef enum TESTRESULT
{
PASS, FAIL, INCOMPLETE
} TESTRESULT;
typedef unsigned int ATTRIB;
typedef char ATTRIBPATTERN [7];
/* I N C L U D E S */
#include <stdlib.h>
// e.g. exit, min, max
#include <stdio.h>
// e.g. FILE, printf, fopen
#include <conio.h>
// e.g. getch kbhit crlscr
#include <alloc.h>
// e.g. malloc free
#include <dos.h>
// _dos_findfirst _dos_findnext
#include <string.h>
// strcpy
/* G L O B A L S */
char WildCard[] = "E:\\*.*";
/* Wildcard to describe all files on the EIDE CD-ROM */
/* P R O T O T Y P E S */
int main( int argc, char *argv[] );
void Banner ( void );
void beep (void );
int pause ( void );
int DoesOSDoSimultaneousIO ( void );
int CheckForEsc ( void );
/* ***************************************** */
/* C L A S S D E C L A R A T I O N */
class FILETREE
{
/* class to process a set of files matching a wildcard */
public:
FILETREE() {};
~FILETREE() {};
void ForEachFile(char * WildCard, ATTRIBPATTERN AttribPattern, BOOL Recurse );
/* calls ProcessOneFile for each match
* ADVSHR
* e.g. C:\XXX\*.TXT, "?00+1-", TRUE if /S style apply recursively to subdirs */
/* attribute pattern string is of form:
* ADVSHR A-archive D-directory V-volume S-system H-hidden R-read-only
* "ADVSHR"
* "?00+1-" <-- example
* ?=ignore, irrelevant.
* 1=must be 1
* 0=must be 0
* +=must have some 1s, all plus slots many not be 0
* -=must have some 0s, all minus slots may not be 1 */
virtual BOOL ProcessOneFile ( char * QualFileName, long FileSize ) = 0;
/* returns true to abort entire process.
Code to process one file, custom-written for each use. */
protected:
BOOL Stop; /* true if we want to stop ForEachFile early */
void ProcessSubs( char * WildCard );
/* recursively process all files in all subdirs matching the
* given wildcard pattern.
* WildCard will be of form C:\xxx\*.TXT */
void ProcessFiles ( char * WildCard );
/* process all files matching given WildCard. No recursion.
* WildCard will be of form C:\xxx\*.TXT */
char * Join ( char * Result, char * WildCard, char * SubDir, char * FileName );
/* append Subdir and FileName onto WildCard,
* e.g. C:\xxx\*.* , yyy , a.b -> C:\xxx\yyy\a.b
* Subdir or FileName might be null */
BOOL FineAttribFilter[64];
/* whether to accept each of 64 possible
* attributes. Built by invoking the
* ProbeAttribFilter function with each of the
* 64 possible attributes. */
ATTRIB CoarseAttribFilter;
/* Attribute mask to grossly filter out files never wanted. DOS behaves a
* little strangely when fed a coarse filter. It completely ignores the
* read-only and archive bits. If you ask for DIRs, you get normal files
* too. But, if you ask for Vol, you DON'T get anything but VOL. It is
* all bizarrely asymmetric. We ignore this, and carry on as if DOS were
* consistent. It comes out ok in the wash, since the FineAttribFilter
* filters the mess DOS gives us. */
void InitAttribFilters (ATTRIBPATTERN AttribPattern);
/* calculate FineAttribFilter and CoarseAttribFilter */
BOOL ProbeAttribFilter(ATTRIB Attrib, ATTRIBPATTERN AttribPattern);
/* returns true if given attribute
bit pattern is acceptable according to AttribPattern */
};
/*********************/
class CRC
{
/* calculate 16-bit checksums */
public:
CRC(void) { work = 0; }
~CRC() { }
WORD Result(void) { return work; }
void Reset(void) { work = 0; }
void Append( BYTE *String, WORD Length );
static int InitCRCTable(WORD Poly = 0x8408);
/* usually we would use Poly = 0x8408, the 16-bit CCITT polynomial.
build lookup table to help compute the CRC function.
Only needs be called once. */
protected:
static WORD CRCTable[256];
/* magic numbers used to compute CRC .
table of constants. Only one copy needed */
WORD work;
/* where we accumulate the crc */
};
/* ***************************************** */
class VERIFY : public FILETREE
{
/* class to repeatedly read files off the CD-ROM and make sure they are
the same each time. Computes and compares checksums */
public:
VERIFY(void);
~VERIFY() {};
void VERIFY::Phase1 ( void );
void VERIFY::Phase2 ( void );
void DisplayResults( void );
BOOL ProcessOneFile ( char * QualFileName, long FileSize );
/* returns true to abort entire process.
Computes checksum for one file.
It gets passed a fully qualified FileName and its size */
CRC OneFileCRC;
/* CRC object to compute CRC for current file. */
int Phase; /* 1=compute CRC or 2=verify CRC */
TESTRESULT TestResult;
/* result of the entire test */
BOOL TestDone;
/* true if no more processing to do */
WORD ExpectedCRC [MaxFiles];
/* CRC expected for each file */
int FileIndex; /* which file we are processing on CD ROM */
long ToGo;
/* How many more bytes we need to read for the pass.
We don't read the entire CD-ROM, just the first 7 MB or so */
};
/* ***************************************** */
/* S T A T I C D E F I N I T I O N S */
/* must define, as opposed to declare, static variables in class
in order to allocate static RAM for them. */
WORD CRC::CRCTable[256];
int dummy = CRC::InitCRCTable();
/* ***************************************** */
/* F U N C T I O N D E F I N I T I O N S */
int main( int argc, char *argv[] )
{
Banner();
if ( argc != 2 /* 0=CDtest.Exe 1=E: */ )
{
printf("Oops! usage: CDtest E:"
"\nwhere E: is the EIDE CD-ROM to test.");
exit (2);
}
/* replace first letter of WildCard with Drive Letter */
/* e.g. WildCard might now read: E:\*.* */
WildCard[0] = *argv[1];
VERIFY Verify; /* object to do all the CD-ROM reading tests */
printf("\nYou must run CDtest with background execution configured on."
"\nAbort now if you have not done so."
"\n");
if ( pause() == 2 ) Verify.TestDone = TRUE; /* user ESC abort */
else
{
printf("\nInsert any CD-ROM in drive %s", argv[1]);
if ( pause() == 2 ) Verify.TestDone = TRUE; /* user ESC abort */
}
/* do Phase 1 */
Verify.Phase1();
beep(); /* announce Phase1 over */
/* do Phase 2 */
Verify.Phase2();
beep(); /* announce Phase2 over */
Verify.DisplayResults(); /* report good or bad news */
return (int) Verify.TestResult ; /* 0 = pass, 1 = fail, 2 = incomplete */
} /* end main */
/*****************/
void Banner(void)
{
/* display copyright banner. */
clrscr(); /* clear screen */
printf("\n" StartDecorate " CDtest 1.2 " EndDecorate
"\n"
"\nFreeware to test for the flawed EIDE controllers such as the"
"\nPC-Tech RZ-1000 and the CMD-640."
"\nRuns under DESQview, Windows, Windows For WorkGroups, Windows-95, NT"
"\nand OS/2 all in DOS emulation mode."
"\n"
"\nCopyright (c) 1995 Canadian Mind Products"
"\n#601 - 1330 Burrard Street, Vancouver BC CANADA V6Z 2B8 (604) 685-8412"
"\nInternet: Roedy@bix.com <Roedy Green>"
"\nMay be used freely for non-military use only."
"\n\n");
} /* end Banner */
/*****************/
VERIFY::VERIFY(void)
{
/* constructor */
TestResult = INCOMPLETE;
TestDone = FALSE;
FileIndex = 0;
ToGo =TestSizeInBytes;
}
/*****************/
void VERIFY::Phase1 ( void )
{
Phase = 1;
if (TestDone) return;
clrscr(); /* clear screen */
printf("\n" StartDecorate " PHASE 1 " EndDecorate
"\nDuring phase 1, do not run any other programs."
"\nEspecially avoid using the tape or floppy drive."
"\nShutdown all background programs BEFORE you proceed."
"\nIf you can't get them all, not to worry;"
"\nthe test results will still be valid."
"\n");
if ( pause() == 2 )
{
TestDone = TRUE;
return; /* user abort */
}
printf("\nPhase 1 in progress. Please stand by...\n");
/* read about 7 Mb of files, computing checksums. User may abort by hitting ESC */
FileIndex = 0;
ToGo =TestSizeInBytes;
ForEachFile ( WildCard,/* ADVSHR */ "?000??", TRUE /* recurse */ );
return;
} /* end Phase1 */
/*****************/
void VERIFY::Phase2 ( void )
{
if (TestDone) return;
clrscr(); /* clear screen */
Phase = 2;
switch (DoesOSDoSimultaneousIO())
{
case 0: /* DOS */
clrscr(); /* clear screen */
printf( "\n" StartDecorate " PHASE 2 " EndDecorate
"\n"
"\nDURING phase 2, do as many of these tests with as you can"
"\neach with background execution configured ON,"
"\nto generate simultaneous I/O:"
"\n"
"\n- Backup some files to floppy with MWBackup, MSBackup, Norton Backup"
"\n or other high performance floppy backup program with fast-mode"
"\n simultaneous disk I/O configured."
"\n- Backup a file using your mag tape backup software with fast-mode"
"\n simultaneous disk I/O configured."
"\n- Exercise any simultaneous I/O devices e.g. modem, mouse or sound card."
"\n");
break;
case 1: /* OS */
clrscr(); /* clear screen */
printf( "\n" StartDecorate " PHASE 2 " EndDecorate
"\n"
"\nDURING phase 2, do as many of these tests with as you can"
"\neach with background execution configured ON,"
"\nto generate simultaneous I/O:"
"\n"
"\n- Format a floppy."
"\n- Backup some files to floppy with MWBackup, MSBackup, Norton Backup"
"\n or other high performance floppy backup program with fast-mode"
"\n simultaneous disk I/O configured."
"\n- Backup a file using your mag tape backup software with fast-mode"
"\n simultaneous disk I/O configured."
"\n- Scan a document with your scanner."
"\n- Browse some files on other hard disks and CD-ROM."
"\n- Exercise any simultaneous I/O devices e.g. modem, mouse or sound card."
"\n");
break;
case 2: /* esc */
TestDone = TRUE;
return;
}
printf("\nYour experiments may run slowly because of CDtest's I/O overhead."
"\n"
"\nBEGIN YOUR EXPERIMENTS NOW!"
"\n"
"\nTake all the time you want -- days even."
"\nWhen you have finished all your tests, come back and hit the space bar."
"\n"
"\nPhase 2 in progress..."
"\n");
while ( ! TestDone )
{
/* keep repeating the test till user hits ESC or space bar */
FileIndex = 0;
ToGo = TestSizeInBytes;
ForEachFile ( WildCard,/* ADVSHR */ "?000??", TRUE /* recurse */ );
}
return;
} /* Phase2 */
/*****************/
void VERIFY::DisplayResults( void )
{
clrscr(); /* clear screen */
printf( "\n" StartDecorate " TEST RESULTS " EndDecorate
"\n");
switch (TestResult)
{
case PASS:
printf("\nCDtest passed. No flaws found."
"\nYou may still have a flawed EIDE controller chip with a"
"\nsuccessful software bypass."
"\nKeep in mind, that unless you carefully followed the"
" instructions on the,"
"\nscreen, especially running all tests with background execution"
"\nconfigured on, the results of this test are meaningless."
"\n");
break;
case FAIL:
printf("\nCDtest failed."
"\nIf you have a PCI motherboard the failure is probably due to a faultly"
"\nPC-Tech RZ-1000 or CMD-640 EIDE controller chip."
"\nIt could also be due to overheating, a faulty DMA controller,"
"\nor software bugs.");
break;
case INCOMPLETE:
default:
printf("\nCDtest not completed."
"\nPossibly a user abort or problems reading the CD-ROM drive."
"\n");
break;
}
printf("\nPlease admire the test results.\n");
pause();
} /* end DisplayResults */
/* ***************************************** */
BOOL VERIFY::ProcessOneFile ( char * QualFileName, long FileSize )
{
/* Gets called once for each file on the CD-ROM matching
WildCard and attribute filter.
It opens the file, and computes the checksum.
In Phase 1, computes checksums. In Phase 2, verifies them */
FILE *TempFile;
BYTE *Buffer = new BYTE [BufferSizeInBytes];
if (FileSize)
{
TempFile = fopen(QualFileName,"rb"); /* read binary mode */
if (TempFile )
{
/* got an acceptable file, start a fresh checksum */
OneFileCRC.Reset();
size_t BytesActuallyRead;
size_t BytesToRead;
long ToGoInThisFile = min ( FileSize, ToGo );
/* read test file, till eof, or we have enough bytes, failure or user abort */
while (ToGoInThisFile && !TestDone)
{
BytesToRead = (size_t) min ( (long) BufferSizeInBytes ,ToGoInThisFile );
BytesActuallyRead = fread(Buffer, 1, BytesToRead, TempFile);
if (BytesActuallyRead == BytesToRead)
{
OneFileCRC.Append(Buffer,BytesActuallyRead);
ToGo -= BytesActuallyRead;
ToGoInThisFile -= BytesActuallyRead;
}
else
{
TestResult = FAIL;
TestDone = TRUE;
break;
}
if (CheckForEsc()) TestDone = TRUE;
/* check for user abort every block read */
}
fclose(TempFile);
}
else
{
/* file would not open */
TestResult = FAIL;
TestDone = TRUE;
}
}
else /* NOP */; /* ignore 0-length files */
if (!TestDone) switch (Phase)
{
case 1:
/* save computed checksum */
ExpectedCRC [FileIndex++] = OneFileCRC.Result();
break;
case 2:
/* ensure checksum is same as last time */
if (ExpectedCRC [FileIndex++] == OneFileCRC.Result())
TestResult = PASS;
else
{
/* crc check failed */
TestResult = FAIL;
TestDone = TRUE;
}
break;
}
delete Buffer;
return (BOOL) ((ToGo <= 0) || (FileIndex >= MaxFiles) || TestDone);
/* quit calling this function as soon as enough bytes read,
enough files read,
or test aborted by user or test failure. */
} /* end ProcessOneFile */
/* ***************************************** */
void beep (void)
{
/* long beep to attract attention */
printf("\x07\x07\x07\x07");
} /* end beep */
/*****************/
int pause (void)
{
/*
returns 1=user hit space
returns 2=user hit ESC
*/
int Result = 0;
/* pause until the user hits the space bar */
printf ("\nWhen you are ready to proceed, hit the space bar; hit ESC to abort");
while (!Result)
{
switch (getch())
{
case ' ':
Result = 1; /* space we are done */
break;
case Esc:
Result = 2; /* Esc, Abort */
break;
default:
printf("\x07" ); /* beep to indicate char ignored. */
break;
}
}
printf("\r \n\n");
/* erase the prompt line, to ack the space bar */
return Result;
} /* end pause */
/*****************/
int DoesOSDoSimultaneousIO (void)
{
/*
returns 0= no, DESQiew, Windows,...
returns 1= yes. OS/2, NT...
returns 2=user hit ESC
*/
int Result = -1;
printf( "\nDoes your operating system do more than one I/O at a time?"
"\nAnswer N for DOS, DESQview, Windows, Windows for Workgroups or Windows-95."
"\nAnswer Y for OS/2, Warp, NT, Linux, SCO XENIX or UNIX.");
/* don't advertise the ESC possibility */
while (Result == -1)
{
switch (getch())
{
case 'N':
case 'n':
Result = 0; /* No */
break;
case 'Y':
case 'y':
Result = 1; /* Yes */
break;
case Esc:
Result = 2; /* Esc, Abort */
break;
default:
printf("\x07" ); /* beep to indicate char ignored. */
break;
}
}
return Result;
} /* end DoesOSDoSimultaneousIO */
/* ***************************************** */
int CheckForEsc (void)
{
/*
returns 0=user did nothing. We don't wait for him to hit a key.
returns 1=user hit space
returns 2=user hit ESC
*/
int Result = 0;
if (kbhit())
{
switch (getch())
{
case ' ':
Result = 1; /* space we are done */
break;
case Esc:
Result = 2; /* Esc, Abort */
break;
default:
printf("\x07" ); /* beep to indicate char ignored. */
Result = 0;
break;
}
}
else
Result = 0; /* no key hit */
return Result;
} /* end CheckForEsc */
/* ***************************************** */
int CRC::InitCRCTable (WORD Poly)
{
WORD i;
WORD crc;
/* usually we would use Poly = 0x8408, the 16-bit CCITT polynomial */
for (i = 0; i < 256; i++)
{
crc = (WORD) i;
/* ^ is bitwise XOR */
crc = (crc >> 1) ^ ((crc & 1) ? Poly : 0);
crc = (crc >> 1) ^ ((crc & 1) ? Poly : 0);
crc = (crc >> 1) ^ ((crc & 1) ? Poly : 0);
crc = (crc >> 1) ^ ((crc & 1) ? Poly : 0);
crc = (crc >> 1) ^ ((crc & 1) ? Poly : 0);
crc = (crc >> 1) ^ ((crc & 1) ? Poly : 0);
crc = (crc >> 1) ^ ((crc & 1) ? Poly : 0);
crc = (crc >> 1) ^ ((crc & 1) ? Poly : 0);
CRCTable[i] = crc;
}
return 0; /* dummy */
} /* end InitCRCTable */
/* ***************************************** */
void CRC::Append ( BYTE* String, WORD Length )
{
/* cumulatively compute the CRC of this string. */
/* string may contain nulls */
/* character of the string, start with len byte */
while (Length--)
work = (work >> 8) ^ CRCTable[(work & 0xff) ^ *String++];
/* ^ is bitwise XOR */
} /* end Append */
/* ***************************************** */
void FILETREE::ForEachFile (char * WildCard, ATTRIBPATTERN AttribPattern, BOOL Recurse )
{
Stop = FALSE;
InitAttribFilters(AttribPattern);
if (Recurse) ProcessSubs(WildCard);
else ProcessFiles(WildCard);
} /* end ForEachFile */
/* ***************************************** */
void FILETREE::ProcessSubs(char *WildCard)
/* We recursively process all WildCard-matching ordinary files and all
* subdirs of this dir. Since we
* are processing recursively we use new rather than the stack to avoid
* overflowing it. Wildcard describes files, not subdirs. */
{
struct find_t * Hit = new struct find_t;
/* a subdir found by _dos_findnext */
char * FindSubsWildCard = new char [FILENAME_MAX];
char * SubWildCard = new char [FILENAME_MAX];
if (WildCard && WildCard[0]) /* not null */
{
ProcessFiles(WildCard); /* process ordinary files first */
/* ignore file part when searching for dirs.
* e.g. E:/MyDir/*.* to search for any subdirs.
* Process only Subdirs, including hidden, system etc. subdirs.
* Ignore all hits but true subdirs.
* User's FineAttribFilter is irrelevant */
if (!Stop && _dos_findfirst(Join ( FindSubsWildCard, WildCard, NULL, "*.*"),
(_A_SUBDIR | _A_ARCH | _A_RDONLY | _A_HIDDEN | _A_SYSTEM),
Hit) == 0)
do
{
if ((Hit->attrib & _A_SUBDIR) && Hit->name[0] != '.')
{
/* e.g. E:/MyDir/MySub/*.TXT */
ProcessSubs(Join(SubWildCard, WildCard, Hit->name, NULL ));
/* recursively */
}
}
while (!Stop && _dos_findnext(Hit) == 0);
}
delete FindSubsWildCard;
delete SubWildCard;
delete Hit;
} /* end ProcessSubs */
/* ***************************************** */
void FILETREE::ProcessFiles( char * WildCard )
{
/* non-recursively process files in directory described by WildCard. */
struct find_t * Hit = new struct find_t;
/* a file found by _dos_findnext */
char * QualFileName = new char [FILENAME_MAX];
if (WildCard && WildCard[0]) /* not null */
if (_dos_findfirst(WildCard, CoarseAttribFilter, Hit) == 0)
do
{
if (FineAttribFilter[Hit->attrib])
{
if (ProcessOneFile
(Join(QualFileName, WildCard, NULL, Hit->name),
Hit->size ))
{
Stop = TRUE;
break;
}
}
} while (_dos_findnext(Hit) == 0);
delete QualFileName;
delete Hit;
} /* end ProcessFiles */
/* ***************************************** */
void FILETREE::InitAttribFilters (ATTRIBPATTERN AttribPattern)
{
/* Prepare FineAttribFilter so we don't have to keep invoking the
* slow ProbeAttribFilter function.
* Also prepare CoarseAttribFilter -- special types of files
* EVER wanted.
* We can use CoarseAttribFilter to get DOS to do some gross filtering. */
ATTRIB Attrib;
CoarseAttribFilter = 0;
for (Attrib = 0; Attrib < 64; Attrib++)
{
if ((FineAttribFilter[Attrib] = ProbeAttribFilter(Attrib, AttribPattern)))
CoarseAttribFilter |= Attrib;
}
} /* end InitAttribFilters */
/* ***************************************** */
BOOL FILETREE::ProbeAttribFilter(ATTRIB Attrib, ATTRIBPATTERN AttribPattern)
{
/* true if Attrib matches the given string pattern .
* attribute pattern string is of form:
* ADVSHR A-archive D-directory V-volume S-system H-hidden R-read-only
* "ADVSHR"
* "?00+1-" <-- example
* ?=ignore, irrelevant.
* 1=must be 1
* 0=must be 0
* +=must have some 1s, all plus slots many not be 0
* -=must have some 0s, all minus slots may not be 1 */
BOOL Bit;
BOOL NeedPlus = FALSE; /* no + seen yet, so no need for 1s in + slots */
BOOL NeedMinus = FALSE; /* ditto for minus */
int i;
WORD PlusCount = 0;
WORD MinusCount = 0;
/* work right to left comparing the Pattern char string with the Attrib bits */
for (i = 5; i >= 0; i--)
{
Bit = (BOOL) (((WORD) Attrib >> (5 - i)) & 1);
switch (AttribPattern[i])
{
case '1':
if (!Bit)
return (FALSE);
break;
case '0':
if (Bit)
return (FALSE);
break;
case '+':
NeedPlus = TRUE;
if (Bit)
PlusCount++;
break;
case '-':
NeedMinus = TRUE;
if (!Bit)
MinusCount++;
break;
case '?':
break;
default:
printf(
"\n Programmer error: Invalid WildCard attribute specifier string: %s.\n",
AttribPattern);
exit(99);
} /* end switch */
} /* end for */
if (NeedPlus && (PlusCount == 0))
return (FALSE);
if (NeedMinus && (MinusCount == 0))
return (FALSE);
return (TRUE);
} /* end ProbeAttribFilter */
/* ***************************************** */
char * FILETREE::Join(char *Result, char *WildCard, char *SubDir, char *FileName)
{
/* Glue Wildcard, Subdir and FileName together */
/* SubDir and FileName might be null */
char Drive[_MAX_DRIVE];
char Dir[_MAX_DIR];
char Fname[_MAX_FNAME];
char Ext[_MAX_EXT];
_splitpath(WildCard, Drive, Dir, Fname, Ext);
/* Dir will usually have lead and trail backslash */
if (SubDir) strcat(Dir, SubDir);
if (FileName) _makepath(Result, Drive, Dir, FileName, NULL);
else _makepath(Result, Drive, Dir, Fname, Ext);
return Result;
} /* end Join */
/* ***************************************** */
/* -30- */