home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
pcmagazi
/
1991
/
11
/
chkpath.c
< prev
next >
Wrap
C/C++ Source or Header
|
1991-03-27
|
59KB
|
1,649 lines
/* Copyright Notice:
This program was written for PC Magazine by John Deurbrouck.
This program is a published, copyrighted work.
Copyright 1991. All rights reserved.
*/
/* Compiling instructions:
Borland C++ 2.0, in C mode:
for debug version
bcc -w -v -N -ml -f- -d -DDEBUG -edchkpath chkpath.c
for production version
bcc -Z -w -N- -ml -O -G- -f- -d chkpath.c
Borland Turbo C++ 1.01, in C mode:
for debug version
tcc -w -v -N -ml -f- -d -DDEBUG -edchkpath chkpath.c
for production version
tcc -Z -w -N- -ml -O -G- -f- -d chkpath.c
Microsoft C 6.00A:
for debug version
cl /DDEBUG /Zp1 /W4 /AL /Zi /Fedchkpath /F 2000 chkpath.c
for production version
cl /Zp1 /W4 /AL /Gs /F 2000 chkpath.c
Microsoft Quick C 2.51:
for debug version
qcl /DDEBUG /Zp1 /W4 /AL /Zi /Fedchkpath /F 2000 chkpath.c
for production version
qcl /Zp1 /W4 /AL /Gs /F 2000 chkpath.c
Zortech C++ 2.18, in C mode:
for debug version
ztc -ml -a1 -s -gl -gs -DDEBUG -odchkpath chkpath.c
for production version
ztc -ml -a1 chkpath.c -o
I use Gimpel's PC Lint, and the code in this file lints clean in
Turbo C mode. I don't have PC Lint set up for Microsoft or
Zortech code, so I didn't lint it in that mode.
I used LZEXE to compact the executables. This product reduces the
amount of disk space an .EXE file occupies, at the price of a
fraction of a second's decompression time when the program runs.
How did the size of the executables compare? Here's a list of the
sizes I got for version 0.80:
LZEXE'd
DEBUG PRODUCTION PRODUCTION
Turbo C++ 2.0 34565 19258 12148
Microsoft C 6.00A 31307 16549 10722
Microsoft Quick C 2.51 35721 19607 10998
Zortech C++ 2.18 33804 21812 13502
*/
/* Version History
1.0 27Mar91 * Initial release.
*/
/***
Includes
***/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<dos.h>
#include<time.h>
#include<conio.h>
#ifdef _MSC_VER
#include<sys\types.h>
#include<direct.h>
#endif
#ifdef __ZTC__
#include<sys\types.h>
#include<direct.h>
#endif
#include<sys\stat.h>
#ifdef __TURBOC__
#include<dir.h>
#endif
/*
** We MUST have Zortech, Borland or Microsoft.
**
** Here we ensure that one identifier or the other is present.
*/
#ifndef __TURBOC__
#ifndef _MSC_VER
#ifndef __ZTC__
#error This requires Zortech, Turbo C, MSC, or Quick C
#endif
#endif
#endif
/***
Defines
***/
/*
** The EXIT() macro is always used to return to DOS, even if we have
** a successful completion and are returning from main().
*/
#ifdef DEBUG
#define EXIT(arg) {trap();myquit(__FILE__,__LINE__,arg);}
#else
#define EXIT(arg) myquit(arg);
#endif
/*
** COM, EXE and BAT are used to index into char *comexebat[]
*/
#define COM 0
#define EXE 1
#define BAT 2
/*
** IS_ON is the character printed for executables on the path.
** IS_OFF is the character printed for executables off the path.
** _STR variants used to build header string
*/
#define IS_ON ' '
#define IS_OFF '-'
#define IS_ON_STR " "
#define IS_OFF_STR "-"
/*
** MALLOC_BLOCK_SIZE determines how much space we ask malloc() for
** when we need space. See the comments in malloc_nofree() for
** further details.
*/
#define MALLOC_BLOCK_SIZE 8096
/*
** SCREEN_LINES determines how often we stop for a keypress when the
** /p command line parameter is present.
*/
#define SCREEN_LINES 22
/*
** POSSIBLE_DRIVES is 26, the number of letters in the alphabet.
*/
#define POSSIBLE_DRIVES 26
/*
** mprintf is a function macro. It simply replaces every mprintf
** with a call to check_screen_overflow() and then a printf. This
** can be DANGEROUS if you aren't aware of it, because this is not
** a single statement like it appears to be. DON'T make this the
** object of an if statement, or you'll be sorry:
** if(0)mprintf("this is a very bad idea\n");
** translates to what you would normally type as:
** if(0)check_screen_overflow();
** printf("this is a very bad idea\n");
** which is not how you would normally read the source, since the
** message gets printed and not skipped as you would expect.
** BEWARE!!!
*/
#define MPRINTF check_screen_overflow();printf
/*
** This program was originally written in Borland's Turbo C dialect.
** The following defines, along with the differences in which system
** include files had to be used, are all that is necessary to make
** the original code work under others. There are no conditional
** compilation statements in the code proper, except that Zortech's
** dos_getdrive works differently enough from Borland's and
** Microsoft's that a wrapper function, getdrive() had to be
** written.
*/
#ifdef _MSC_VER
#define MAXDIR _MAX_DIR
#define MAXPATH _MAX_PATH
#define FA_RDONLY _A_RDONLY
#define FA_HIDDEN _A_HIDDEN
#define FA_SYSTEM _A_SYSTEM
#define FA_LABEL _A_VOLID
#define FA_DIREC _A_SUBDIR
#define findfirst(name,blok,att) _dos_findfirst(name,att,blok)
#define findnext _dos_findnext
#define ffblk find_t
#define ff_attrib attrib
#define ff_name name
#define ff_ftime wr_time
#define ff_fdate wr_date
#define ff_fsize size
/*
** Disk drive access functions:
**
** Note that while for Borland, A=0, B=1, C=2...,
** for Microsoft, A=1, B=2, C=3...
** Rather than have the dependencies in the code, I elected
** to have them here.
*/
#define getdisk() (_getdrive()-1)
#define setdisk(drive) _chdrive((drive)+1)
#endif
#ifdef __ZTC__
#define MAXDIR FILENAME_MAX
#define MAXPATH FILENAME_MAX
#define findfirst(name,blok,att) _dos_findfirst(name,att,blok)
#define findnext _dos_findnext
#define ffblk FIND
#define ff_attrib attribute
#define ff_name name
#define ff_ftime time
#define ff_fdate date
#define ff_fsize size
/*
** Disk drive access functions:
**
** Note that while for Borland, A=0, B=1, C=2...,
** for Zortech, A=1, B=2, C=3...
** Rather than have the dependencies in the code, I elected
** to have them here.
*/
#define setdisk(drive) dos_setdrive((drive)+1,&numdrives)
#endif
/***
Structures and typedefs and enums
***/
/*
** This structure used to accumulate information about every
** directory to be searched. The entries in this linked list
** are in search order.
**
** Note that a SUBST can make a directory look as though it is
** repeated. To avoid this, we must store information about both
** the true and substituted name. To verify uniqueness, we need
** only verify uniqueness of truename.
*/
typedef struct dir_queue_struct dir_q;
struct dir_queue_struct{
char *dirname;
char *truename;
dir_q *next;
char on_search_path; /* nonzero if on specified search path */
};
/*
** This structure is used to gather information about every
** executable file found. progname points to an ASCIIZ string which
** is just the base name. prog_directory points to an ASCIIZ string
** which is the drive and directory. next points to the next element
** in this linked list, and ext_type is COM, EXE or BAT. This was
** more compact than storing the extension in each progname. So,
** if you're storing information about C:\UTIL\CRUNCH.EXE,
** progname points to an ASCIIZ string, "CRUNCH"
** prog_directory points to "C:\UTIL\"
** ext_type will be equal to EXE, #defined above.
** Note also that to conserve memory, many prog_queue_struct entries
** may share one or more pointers. C:\UTIL\UNCRUNCH.EXE will have
** the same prog_directory pointer as the example above. And
** C:\UTIL\CRUNCH.BAT will have the same progname and prog_directory
** pointers.
*/
struct prog_queue_struct{
char *progname;
char *truename;
dir_q *dir_queue_ptr;
struct prog_queue_struct *next;
char ext_type;
};
/*
** struct big_prog_queue_struct is the same as prog_queue_struct, but
** has space to store the file's date, time and size. It's a bit
** larger, so you might run out of room. That's why there's the /n
** switch, which causes use of prog_queue_struct instead of
** big_prog_queue_struct. This makes big_prog_queue_struct the
** default.
*/
struct big_prog_queue_struct{
char *progname;
dir_q *dir_queue_ptr;
struct big_prog_queue_struct *next;
union time_splitter{
unsigned int ff_ftime;
struct time_bit_struct{
unsigned ft_tsec:5;
unsigned ft_min:6;
unsigned ft_hour:5;
}time_bits;
}ft;
union date_splitter{
unsigned int ff_fdate;
struct date_bit_struct{
unsigned fd_day:5;
unsigned fd_month:4;
unsigned fd_year:7;
}date_bits;
}fd;
long ff_fsize;
char ext_type;
};
/***
Global Variables
***/
char *comexebat[]={
{".COM"},
{".EXE"},
{".BAT"}
};
/*
** a nonzero entry in this array means the user wants that drive
** checked for nonpath programs. set with /d= command-line switch.
*/
int nonpath_drives[POSSIBLE_DRIVES];
int show_nonpath=0;
/*
** nonzero for dos_has_subst means the DOS version number is high
** enough that we ought to be accounting for SUBST possibility.
*/
int dos_has_subst=0;
/*
** this is the number of executable files found.
*/
int num_dir_queue_entries=0;
/*
** this is nonzero if the /a option is present on the command line.
*/
int show_all_files=0;
/*
** this is nonzero if we're accumulating date, time and size. (/n)
*/
int show_details=1;
/*
** this is nozero if the /p option is present on the command line
*/
int screenful_pause=0;
int lines_left_on_screen=SCREEN_LINES;
/*
** this is nonzero if any directories were specified
*/
int got_dir_specs=0;
/*
** nonzero if we have printed at least one entry from prog_queue or
** big_prog_queue.
*/
int showed_something=0;
/*
** global_last_conflict is a pointer to a character string that
** gives the user-readable representation of a conflicting
** directory entry. This can be problematic if the user has
** a SUBST directory on the command line, and a normal
** representation of the same thing. In other words, if the user
** does the following DOS commands:
** C:\>subst b: c:\dos
** C:\>set path=c:\;c:\dos;b:\
** Then assume the user performs the following:
** C:\>chkpath /e=path
** obviously he has a conflict. But a report simply stating that
** C:\DOS is specified twice may not be illuminating enough. This
** global_last_conflict holds a pointer to the alias for the
** directory name being tested, so it can be reported by CHKPATH
** to make the exact problem clearer.
*/
char *global_last_conflict=NULL;
/*
** initialize linked lists as empty.
*/
dir_q *dir_queue=(dir_q *)0;
struct prog_queue_struct *prog_queue=(struct prog_queue_struct *)0;
struct big_prog_queue_struct *big_prog_queue=
(struct big_prog_queue_struct *)0;
#ifdef DEBUG
/*
** these values are used to track memory usage in DEBUG mode only.
*/
unsigned long total_malloc_memory=0UL;
unsigned long total_memory_used=0UL;
unsigned long total_memory_wasted=0UL;
#endif
/*
** nofiles_str and noconf_str are used when no output was
** generated (keys off showed_something).
*/
char nofiles_str[]="No .COM, .EXE, or .BAT files in specified "
"directories.";
char noconf_str[]="All .COM, .EXE and .BAT files in specified "
"directories are unique.";
/*
** text array with copyright information
*/
char *aacCopyrightArray[]={
{"CHKPATH 1.0 "
#ifdef DEBUG
"***Debug version*** "
#endif
"Copyright (c) 1991 Ziff Communications Co."},
{"PC Magazine * John Deurbrouck"},
{""}
};
/*
** text array with usage information. this is easier to maintain
** than a large series of printf() calls.
*/
char *aacUsageArray[]={
{"Syntax: CHKPATH [/a] [/d=drive(s)] [/n] [/p] [/s=dir1 ... "
"dirn] [/e=var]"},
{" /a ALL. Lists all .COM, .EXE and .BAT files, "
"conflicting or not."},
{" /d=drive(s) DRIVES. Searches all dirs on each listed "
"drive."},
{" /n NO_DETAIL. No display of file date, time"
" and size."},
{" /p PAUSE. Pauses after each screenful of report "
"lines."},
{"/s=dir1 ... dirn SPECIFIED. Specifies dir(s) to search."},
{" /e=var ENVIRONMENT variable."},
{""}
};
/*
** Including exits.h twice and taking different data from it each
** time. This ensures that error messages and enumerated error
** values stay in sync.
*/
char *aacErrorArray[]={
#ifdef EXITS
#undef EXITS
#endif
#define EXITS(name,string) {string},
#include"exits.h"
{""}
};
/*
** another text array
*/
char *aacReptHdr[]={
{"-------- -----------------------------------------"
"-----------------------------\n"},
{" NAME LOCATION (NAME printed only for "
"'winner')\n"},
{" '" IS_OFF_STR "' replaces '" IS_ON_STR "' before LOC"
"ATION if not on specified search path.\n"}
};
/*
** the arguments defined here are the only arguments allowed to the
** EXIT() macro. This syncronizes readable error exit values (like
** ERROR_MALLOC) with the appropriate error messages.
*/
typedef enum{
#ifdef EXITS
#undef EXITS
#endif
#define EXITS(name,string) name,
/*lint -e537 yes, exits.h included twice OK*/
/*lint -e726 extra comma on enum is OK*/
#include"exits.h"
ERROR_ERROR____ /* ANSI doesn't allow comma after last enum*/
/*lint +e537*/
}exit_code; /*lint +e726*/
#ifdef __TURBOC__
unsigned _stklen=0x2000; /* set stack size for Turbo C */
/*
** Note that Turbo C resets the stack here using the runtime,
** rather than using the linker. This means that when you look
** at CHKPATH.EXE's header, you'll see the default 0x800 (2k)
** stack size. Step through the code and look at the CPU
** registers to confirm to yourself that the stack is, in fact,
** set to the desired size...
*/
#endif
#ifdef __ZTC__
int _stack=0x2000; /* set stack size for Turbo C */
/*
** Note that Zortech C resets the stack here using the runtime,
** rather than using the linker.
*/
#endif
#ifdef __ZTC__
/*
** Zortech needs another parameter for its setdisk() equivalent.
*/
unsigned numdrives;
#endif
/***
Function Prototypes
***/
void main(int argc,char *argv[]);
void show_copyright(void);
void show_usage(void);
#ifdef DEBUG
void trap(void);
void myquit(char *file,int line,exit_code arg);
#else
void myquit(exit_code arg);
#endif
void process_cmd_arg(char *ptr);
int set_nonpath_drives(char *ptr);
void add_nonpath_directories(void);
void add_dir_and_recurse(char *dir);
int normalize_path_name(char *inpath,char *outpath,int buflen);
dir_q *add_dir_queue(char *newdir,int on_path);
dir_q *get_dir_queue_space(char *dir,char *true,int on_path);
void scan_dirs(void);
void find_ext_in_dir(dir_q *directory,int extension);
void add_prog_queue(dir_q *directory,char *name,int extension);
void add_big_prog_queue(dir_q *directory,struct ffblk *ff,
int extension);
void show_results(void);
void show_big_results(void);
void make_big_output(char *out_array,char *label,struct
big_prog_queue_struct *ptr);
void check_screen_overflow(void);
void *malloc_nofree(size_t size);
int cur_drive_is_subst(void);
int get_truename(char *true,char *original);
int does_dos_have_subst(void);
#ifdef __ZTC__
int getdisk(void);
#endif
/***
Lint setup
***/
/*lint -e720 boolean test of assignment is intentional*/
/*lint -esym(534,int86x) ok to ignore return value */
/***
Function Definitions
***/
void main(int argc,char *argv[]){
/** void main(int argc,char *argv[]);
calls process_cmd_arg() with each command line argument, then
calls scan_dirs() and show_results() or show_big_results()
**/
show_copyright();
argc--; /* scan off program name */
argv++;
memset(nonpath_drives,0,sizeof(nonpath_drives)); /* assure all 0*/
dos_has_subst=does_dos_have_subst();
if(!argc)EXIT(ERROR_USAGE) /* no args at all */
while(argc--){ /* process cmd line args */
process_cmd_arg(*argv);
argv++;
}
if(num_dir_queue_entries)got_dir_specs=1;
add_nonpath_directories();
if(!num_dir_queue_entries)EXIT(ERROR_NODIRS)
scan_dirs();
if(show_details)show_big_results();
else show_results();
if(!showed_something){
/*
** pick correct message based on prog_queue or big_prog_queue
*/
MPRINTF("%s\n",
!prog_queue&&!big_prog_queue?nofiles_str:noconf_str);
}
EXIT(ERROR_SUCCESS);
}
void show_copyright(void){
/** void show_copyright(void);
puts the copyright notice to stderr
**/
char **t=aacCopyrightArray;
while(**t)fprintf(stderr,"%s\n",*t++);
}
void show_usage(void){
/** void show_usage(void);
puts the usage information to stderr
**/
char **t=aacUsageArray;
while(**t)fprintf(stderr,"%s\n",*t++);
}
#ifdef DEBUG
void trap(void){
/** void trap(void);
Does nothing. Just here to provide for debugger trapping
before error goes out of scope. If you make changes and have
problems, put a breakpoint on the closing brace of this function.
When a fatal problem occurs, you can then step out of this
function back to the scope in which the EXIT() macro occurs.
**/
}
#endif
#ifdef DEBUG
void myquit(char *file,int line,exit_code arg){
#else
void myquit(exit_code arg){
#endif
/** void myquit(exit_code arg);
DEBUG version has following prototype:
void myquit(char *file,int line,exit_code arg);
Shows the appropriate exit string, then bails out with exit()
to return errorlevel.
Also displays aacUsageArray if arg==ERROR_USAGE.
**/
if(arg==ERROR_USAGE)show_usage();
#ifndef DEBUG
else{
if(arg!=ERROR_SUCCESS){
fprintf(stderr,"EXITING: %s\n",aacErrorArray[(int)arg]);
}
}
#endif
#ifdef DEBUG
fprintf(stderr,"MEMORY: malloc=%lu, used=%lu, wasted=%lu\n",
total_malloc_memory,total_memory_used,total_memory_wasted);
fprintf(stderr,"EXITING:%s(%d): %s\n",
file,line,aacErrorArray[(int)arg]);
#endif
exit((int)arg);
}
void process_cmd_arg(char *ptr){
/** void process_cmd_arg(char *ptr);
takes a string that may be a directory name or the name of an
environment variable. Figures out which one, then adds all
possible directory names to the dir_queue, incrementing
num_dir_queue_entries as it goes. Calls add_dir_queue() to do
actual update. Converts all input to uppercase.
Since we don't actually scan any directory until all the
command line arguments have been processed, we don't have to
look ahead for the /d parameter.
**/
char *work,input_buffer[512],full_path[MAXDIR+3],*env_ptr;
int was_environment_variable=0;
if(!*ptr)return;
/*
** first we coerce to uppercase
*/
work=ptr;
while(*work){
*work=(char)toupper(*work);
work++;
}
/*
** now check for command line arguments
*/
if(!strcmp(ptr,"/A")){ /* check for cmd line /a flag */
show_all_files=1;
return;
}
if(!strcmp(ptr,"/N")){ /* check for cmd line /n flag */
show_details=0;
return;
}
if(!strcmp(ptr,"/P")){ /* check for cmd line /p flag */
screenful_pause=1;
return;
}
if(!memcmp(ptr,"/D=",3)){ /* check for cmd line /d flag */
if(!set_nonpath_drives(&ptr[3]))EXIT(ERROR_BAD_DRIV)
show_nonpath=1;
return;
}
if(!memcmp(ptr,"/E=",3)){ /* environment variable */
was_environment_variable=1;
env_ptr=&ptr[3];
ptr=getenv(&ptr[3]);
if(!ptr||!*ptr){
fprintf(stderr,"Specified environment variable '%s'\n",
env_ptr);
EXIT(ERROR_ENVIRON);
}
}
if(!memcmp(ptr,"/S=",3)){ /* specified directory */
ptr+=3; /* throw away switch, not needed */
}
/*
** what we have is a semicolon-separated directory list, and we
** want to separate it into a series of usable directory names.
** The process is pictured with the input_buffer= comments below.
*/
input_buffer[0]=0;
strcpy(&input_buffer[1],ptr); /* input_buffer=\0DIR;DIR;DIR\0 */
work=&input_buffer[1];
/*
** now we replace all semicolons with nulls, and add an extra
** null on the end (for later loop control)
*/
for(;;){
switch(*work){
case 0:
work[1]=0;
break;
case ';':
*work=0;
work++;
continue;
default:
work++;
continue;
}
break;
} /* input_buffer=\0DIR\0DIR\0DIR\0\0 */
/*
** now we have a series of directories, all null-separated.
** let's cycle through them, processing them one at a time
** until there are none left.
*/
work=input_buffer;
while(work[1]){
int is_relative,is_multi,is_nonexistent;
is_multi=is_nonexistent=0;
work++;
is_relative=(work[1]!=':'||work[2]!='\\')?1:0;
if(normalize_path_name(work,full_path,MAXDIR+3)){
if(add_dir_queue(full_path,1))num_dir_queue_entries++;
else is_multi=1;
}
else is_nonexistent=1;
if(is_relative||is_multi||is_nonexistent){
if(was_environment_variable)printf("ENV VAR '%s', ",
env_ptr);
printf("Directory %s",work);
if(is_relative)printf(", relative");
if(is_multi){
printf(", specified more than once");
if(global_last_conflict){
printf(" (%s)",global_last_conflict);
global_last_conflict=NULL;
}
}
if(is_nonexistent)printf(", could not be located");
MPRINTF("\n");
}
while(*work)work++; /* processed, so scan off this entry */
}
}
int set_nonpath_drives(char *ptr){
/** int set_nonpath_drives(char *ptr);
Goes through the character string pointed to by ptr.
Expects a string in the following form:
ltr_a[-ltr_b]...
where ltr_a is any letter, and ltr_b is any letter higher
than ltr_a in the alphabet. it is expected that all
letters are uppercase.
returns 0 for failure
1 for success
primary function is to set nonpath_drives entries to nonzero
for every letter specified
yes, the letters at each end of the range get set twice, but
the runtime cost is so tiny it's not worth any code space
to fix
note that we allow the user to enter colons, for a string
that looks like /d=c:-e:g:
last is set to 120 so if a dash ('-') is encountered before
any drive letters, we'll fail as we should
**/
int count,last=120;
if(!isupper((int)*ptr))return 0;
for(;;){
/*
** we don't really use the colons, so just discard them
*/
if(*ptr==':'){
ptr++;
continue;
}
if(*ptr=='-'){
if(!(last<ptr[1]&&
isupper(last)&&isupper((int)ptr[1]))){
return 0;
}
for(count=last;count<(int)ptr[1];count++){
nonpath_drives[count-'A']=1;
}
ptr++;
continue;
}
last=(int)*ptr;
if(!isupper(last))return 1;
nonpath_drives[last-'A']=1;
ptr++;
}
}
void add_nonpath_directories(void){
/** void add_nonpath_directories(void);
Goes through all drives from A: to Z:, adding all directories
on each to the dir_queue. Checks only if user wanted
that particular drive searched.
For DOS 3.1+, goes through twice. The first time, only
processes SUBST drives. The second time, only processes
non-SUBST drives. This is so the user gets the most
familiar representation.
**/
/* Pseudocode:
save current drive
for candidate=A: to Z:
if (user wants, and change to candidate drive works)
call add_dir_and_recurse() with '\'
endif
end for
restore current drive
*/
int old_drive,candidate,big_loop;
char root_dir[4];
old_drive=getdisk(); /* save current drive */
root_dir[1]=':';
root_dir[2]='\\';
root_dir[3]=0;
for(big_loop=0;big_loop<2;big_loop++){
for(candidate=0;candidate<POSSIBLE_DRIVES;candidate++){
if(!nonpath_drives[candidate])continue;
setdisk(candidate);
if(dos_has_subst&&(big_loop==cur_drive_is_subst())){
continue;
}
if(getdisk()!=(candidate)){
MPRINTF("Could not change to drive %c:\n",
'A'+candidate);
continue; /* reject invalid */
}
root_dir[0]=(char)(candidate+'A');
add_dir_and_recurse(root_dir);
}
if(!dos_has_subst)break;
}
setdisk(old_drive); /* restore current drive */
if(getdisk()!=old_drive)EXIT(ERROR_DOS) /* drive restore error */
}
void add_dir_and_recurse(char *dir){
/** void add_dir_and_recurse(char *dir);
this takes a directory of the form C:\, C:\DOS\, etc.
it tries to add the directory to the dir_queue as a nonpath
directory, then calls itself with each directory it contains.
**/
/* Pseudocode
try to add self to dir queue as nonpath member
if an entry in directory
if it's a directory
add_dir_and_recurse() it
endif
while further entries in directory
if one is a directory
add_dir_and_recurse() it
endif
end while
endif
*/
static char buffer[MAXPATH+15]; /* static to reduce stack space */
struct ffblk ffblock;
char *null_past_original;
if(add_dir_queue(dir,0))num_dir_queue_entries++;
strcpy(buffer,dir);
null_past_original=buffer;
while(*null_past_original)null_past_original++;
strcpy(null_past_original,"*.*");
if(!findfirst(buffer,&ffblock,
FA_RDONLY|FA_HIDDEN|FA_SYSTEM|FA_DIREC)){
if(ffblock.ff_attrib&FA_DIREC&&ffblock.ff_name[0]!='.'){
*null_past_original=0;
strcat(buffer,ffblock.ff_name);
strcat(buffer,"\\");
add_dir_and_recurse(buffer);
}
while(!findnext(&ffblock)){
if(ffblock.ff_attrib&FA_DIREC&&ffblock.ff_name[0]!='.'){
*null_past_original=0;
strcat(buffer,ffblock.ff_name);
strcat(buffer,"\\");
add_dir_and_recurse(buffer);
}
}
}
}
int normalize_path_name(char *inpath,char *outpath,int buflen){
/** int normalize_path_name(char *inpath,char *outpath,int buflen);
Takes a pointer to a path, and returns 1 for success, 0 for
failure
Failure is possible in the case of either an invalid drive or
a nonexistent directory
In the case of success, copies the full path name, which will
always have a trailing '\\' character.
In the case of failure, the contents of *outpath are
unreliable
We actually use DOS to normalize these path names. We'd
access DOS nearly as much if we parsed it all ourselves,
and the code would be much more complex. This procedure
gets called only once per directory, and that's at most
20 or so times. Total runtime consumed will be under
one second, so increasing the complexity was not
worthwhile.
**/
/* Pseudocode
if a drive is specified
save old drive
attempt to change to new drive
if fail return fail endif
endif
if a path is specified
save old path
attempt to change to new path
if fail
if changed drive
change back to old drive
if fail quit program
endif
return fail
endif
endif
get current drive and directory into *outpath
append '\\' to *outpath if necessary
if path was specified
change back to old path
if fail quit program
endif
if drive was specified
change back to old drive
if fail quit program
endif
return success
*/
int old_drive,drive_changed=0,path_changed=0;
char *startpath=inpath,*add_slash=outpath;
char old_path[MAXDIR+2];
/*
** first we need to be logged onto the same drive as the
** target file.
*/
if(inpath[1]==':'){
startpath+=2; /* point at path after drive */
old_drive=getdisk();
drive_changed=1;
setdisk((int)*inpath-'A');
if(getdisk()!=(int)*inpath-'A'){
setdisk(old_drive);
if(getdisk()!=old_drive)EXIT(ERROR_DOS)
return 0;
}
}
/*
** now we change to the specified directory if there is one
*/
if(*startpath){
path_changed=1;
/*
** save old directory
*/
if(getcwd(old_path,sizeof(old_path)-1)!=old_path)
EXIT(ERROR_DOS)
/*
** change to new directory
*/
if(chdir(startpath)){
if(drive_changed){
setdisk(old_drive);
if(getdisk()!=old_drive)EXIT(ERROR_DOS)
}
return 0;
}
}
/*
** whether we changed or not, this getcwd() call will get us the
** full directory specification of the potentially relative
** specification handed to us.
*/
if(!getcwd(outpath,buflen-1))EXIT(ERROR_DOS)
while(add_slash[1])add_slash++; /* point at last char */
/*
** make sure return string has trailing backslash (\)
*/
if(*add_slash!='\\'){
add_slash[1]='\\'; /* add backslash */
add_slash[2]=0; /* add null delimiter */
}
/*
** clean up and return
*/
if(path_changed){ /* changed path, so restore */
if(chdir(&old_path[2]))
EXIT(ERROR_DOS) /* path restore err */
}
if(drive_changed){
setdisk(old_drive);
if(getdisk()!=old_drive)EXIT(ERROR_DOS) /* drive restore err*/
}
return 1;
}
dir_q *add_dir_queue(char *newdir,int on_path){
/** dir_q *add_dir_queue(char *newdir,int on_path);
tries to add newdir to dir_queue
copies *newdir to a newly allocated buffer, and allocates
space for a dir_queue entry as well
returns newly allocated pointer for success
NULL for failure due to newdir already being present
**/
/* Pseudocode
get truename for this path into true_name[]
if truename is different
point true_name_ptr at true_name[]
otherwise
point true_name_ptr at newdir
endif
if no entries
initialize dir_queue to point to the get_dir_queue_space
result
get space for the true name if it's different from newdir,
and insert it into node
return 1
endif
current_pointer=dir_queue
do forever
if new entry duplicates current_pointer
also report name in global_last_conflict if one or the
other was a SUBST result
return 0
endif
if current_pointer -> next
current_pointer=current_pointer->next
else
add new entry at end
return 1
endif
end do
*/
char true_name[MAXPATH+15],*true_name_ptr;
dir_q *ptr,*current_pointer;
true_name_ptr=get_truename(true_name,newdir)?true_name:newdir;
if(!dir_queue){
dir_queue=get_dir_queue_space(newdir,true_name_ptr,on_path);
return dir_queue;
}
current_pointer=dir_queue;
for(;;){
if(!strcmp(true_name_ptr,current_pointer->truename)){
if(strcmp(newdir,current_pointer->dirname)){
global_last_conflict=current_pointer->dirname;
}
else{
if(true_name_ptr!=newdir){
global_last_conflict=true_name_ptr;
}
}
return (dir_q *)0;
}
if(current_pointer->next){
current_pointer=current_pointer->next;
}
else{
ptr=get_dir_queue_space(newdir,true_name_ptr,on_path);
current_pointer->next=ptr;
return ptr;
}
}
}
dir_q *get_dir_queue_space(char *dir,char *true,int on_path){
/** dir_q *get_dir_queue_space(char *dir,char *true,int on_path);
allocates room for dirname and an dir_queue entry, and for
the true string if it's not equal to dir
copies data into new space and points the dir_queue->dirname
at the new space, sets the dir_queue->next to NULL,then
returns the dir_q pointer
does EXIT(ERROR_MALLOC) if failure
**/
dir_q *ptr;
if(!(ptr=malloc_nofree(sizeof(dir_q)))){
EXIT(ERROR_MALLOC)
}
if(!(ptr->dirname=malloc_nofree(strlen(dir)+1)))EXIT(ERROR_MALLOC)
strcpy(ptr->dirname,dir);
if(dir!=true){
if(!(ptr->truename=malloc_nofree(strlen(true)+1)))
EXIT(ERROR_MALLOC)
strcpy(ptr->truename,true);
}
else ptr->truename=ptr->dirname;
ptr->on_search_path=(char)(on_path?1:0);
ptr->next=(dir_q *)0;
return ptr;
}
void scan_dirs(void){
/** void scan_dirs(void);
goes through each entry in dir_queue, calling
find_ext_in_dir() for COM, EXE and BAT
**/
dir_q *ptr;
ptr=dir_queue;
while(ptr){
if(ptr->dirname){
find_ext_in_dir(ptr,COM);
find_ext_in_dir(ptr,EXE);
find_ext_in_dir(ptr,BAT);
}
ptr=ptr->next;
}
}
void find_ext_in_dir(dir_q *directory,int extension){
/** void find_ext_in_dir(dir_q *directory,int extension);
finds all occurances of files in the given directory with the
given extension, and calls add_prog_queue with each one
to add it to prog_queue
**/
char target[MAXPATH+15];
int done_looking;
struct ffblk ffblock;
strcpy(target,directory->dirname);
strcat(target,"*");
strcat(target,comexebat[extension]);
if(!(done_looking=findfirst(target,&ffblock,
FA_RDONLY|FA_HIDDEN|FA_SYSTEM))){
while(!done_looking){
if(!(ffblock.ff_attrib&(FA_LABEL|FA_DIREC))){
if(show_details){
add_big_prog_queue(directory,&ffblock,extension);
}
else{
add_prog_queue(directory,ffblock.ff_name,
extension);
}
}
done_looking=findnext(&ffblock);
}
}
}
void add_prog_queue(dir_q *directory,char *name,int extension){
/** void add_prog_queue(dir_q *directory,char *name,int extension);
adds the specified program to the queue. no effort made to
eliminate redundancy, since we have already eliminated
duplicate directories
will EXIT(ERROR_MALLOC) if can't get memory
**/
/* pseudocode
chop extension, if any, off name
we add to prog_queue for every invocation, so get space
copy all data but progname into new prog_queue entry,next=NULL
if no previous entries or goes before first entry
allocate space for progname
point prog_queue to new entry
new entry -> next = old prog_queue
return
endif
old=current=prog_queue
do forever
switch on comparison, name to current->name
name EQUALS current->name
copy current->name pointer into ptr->name
while (current->next) and
(current->next->name==ptr->name)
current=current->next
end while
ptr->next=current->next
current->next=ptr
return
name GREATER THAN current->name
old=current
if there is a next entry
current=current->next
continue the do loop
endif
fall through to LESS THAN case
name LESS THAN current->name
get string space, copy, ptr->progname=address
ptr->next=old->next
old->next=ptr
return
end switch
end do
*/
char *temp;
struct prog_queue_struct *ptr,*current,*old;
int strcmp_result;
temp=name; /* chop extension off name */
while(*temp){
if(*temp=='.')*temp=0;
else temp++;
}
if((ptr=malloc_nofree(sizeof(struct prog_queue_struct)))==NULL){
EXIT(ERROR_MALLOC)
} /* get space */
ptr->progname=(char *)0; /* copy data */
ptr->ext_type=(char)extension;
ptr->dir_queue_ptr=directory;
if(!prog_queue||strcmp(name,prog_queue->progname)<0){
ptr->progname=malloc_nofree(strlen(name)+1);
strcpy(ptr->progname,name);
ptr->next=prog_queue;
prog_queue=ptr;
return;
}
old=current=prog_queue;
for(;;){
strcmp_result=strcmp(name,current->progname);
if(strcmp_result>0)strcmp_result=1;
else if(strcmp_result<0)strcmp_result=-1;
switch(strcmp_result){
case 0: /* name EQUALS current->name */
ptr->progname=current->progname;
while(current->next&&
current->next->progname==ptr->progname){
current=current->next;
}
ptr->next=current->next;
current->next=ptr;
return;
case 1: /* name GREATER THAN current->name */
old=current;
if(current->next){
current=current->next;
continue;
} /* fall through to LESS THAN case */
case -1: /* name LESS THAN current->name */
ptr->progname=malloc_nofree(strlen(name)+1);
strcpy(ptr->progname,name);
ptr->next=old->next;
old->next=ptr;
return; /*lint -e744 no default on switch*/
} /*lint +e744*/
}
}
void add_big_prog_queue(dir_q *directory,struct ffblk *ff,
int extension){
/** void add_big_prog_queue(dir_q *directory,struct ffblk *ff,
int extension);
adds the specified program to the queue. no effort made to
eliminate redundancy, since we have already eliminated
duplicate directories
will EXIT(ERROR_MALLOC) if can't get memory
this function is exactly like add_prog_queue(), except that
it collects the extra information and addresses
big_prog_queue instead of prog_queue
**/
/* pseudocode
chop extension, if any, off name
we add to big_prog_queue for every invocation, so get space
copy all data but progname into new big_prog_queue entry,
next=NULL
if no previous entries or goes before first entry
allocate space for progname
point big_prog_queue to new entry
new entry -> next = old big_prog_queue
return
endif
old=current=big_prog_queue
do forever
switch on comparison, name to current->name
name EQUALS current->name
copy current->name pointer into ptr->name
while (current->next) and
(current->next->name==ptr->name)
current=current->next
end while
ptr->next=current->next
current->next=ptr
return
name GREATER THAN current->name
old=current
if there is a next entry
current=current->next
continue the do loop
endif
fall through to LESS THAN case
name LESS THAN current->name
get string space, copy, ptr->progname = address
ptr->next=old->next
old->next=ptr
return
end switch
end do
*/
char *temp,*name=ff->ff_name;
struct big_prog_queue_struct *ptr,*current,*old;
int strcmp_result;
temp=ff->ff_name; /* chop extension off name */
while(*temp){
if(*temp=='.')*temp=0;
else temp++;
}
if((ptr=malloc_nofree(sizeof(struct big_prog_queue_struct)))
==NULL){
EXIT(ERROR_MALLOC)
} /* get space */
ptr->progname=(char *)0; /* copy data */
ptr->ext_type=(char)extension;
ptr->dir_queue_ptr=directory;
ptr->ft.ff_ftime=ff->ff_ftime;
ptr->fd.ff_fdate=ff->ff_fdate;
ptr->ff_fsize=ff->ff_fsize;
if(!big_prog_queue||strcmp(name,big_prog_queue->progname)<0){
ptr->progname=malloc_nofree(strlen(name)+1);
strcpy(ptr->progname,name);
ptr->next=big_prog_queue;
big_prog_queue=ptr;
return;
}
old=current=big_prog_queue;
for(;;){
strcmp_result=strcmp(name,current->progname);
if(strcmp_result>0)strcmp_result=1;
else if(strcmp_result<0)strcmp_result=-1;
switch(strcmp_result){
case 0: /* name EQUALS current->name */
ptr->progname=current->progname;
while(current->next&¤t->next->progname
==ptr->progname){
current=current->next;
}
ptr->next=current->next;
current->next=ptr;
return;
case 1: /* name GREATER THAN current->name */
old=current;
if(current->next){
current=current->next;
continue;
} /* fall through to LESS THAN case */
case -1: /* name LESS THAN current->name */
ptr->progname=malloc_nofree(strlen(name)+1);
strcpy(ptr->progname,name);
ptr->next=old->next;
old->next=ptr;
return; /*lint -e744 no default on switch*/
} /*lint +e744*/
}
}
void show_results(void){
/** void show_results(void);
If show_all_files is zero
Displays the the prog_queue entries, if any conflicts.
If not, displays a 'no conflicts found' message.
else
displays all prog_queue entries, if there are any
if not, displays a 'no files found' message.
endif
**/
/* pseudocode
if no entries
return
endif
do forever
if off end of list
quit
endif
if (showing all files) or
(this progname matches next progname)
if showed_something is zero
showed_something=1
print header
endif
print this entry with its name at the far left of line
do forever
if off end of list
quit
endif
if next entry not same as this entry
break from inner do loop
endif
print current element without its name at far left
point at next element in chain
end do
endif
point at next element in chain
end do
*/
struct prog_queue_struct *ptr;
int onpath_char;
ptr=prog_queue;
for(;;){
if(!ptr)return;
if(show_all_files||
(ptr->next&&
ptr->progname==ptr->next->progname&&
(!got_dir_specs||ptr->dir_queue_ptr->on_search_path))){
if(!showed_something){
showed_something=1;
MPRINTF("%s",aacReptHdr[0]);
MPRINTF("%s",aacReptHdr[1]);
MPRINTF("%s",
show_nonpath&&got_dir_specs?aacReptHdr[2]:"");
MPRINTF("%s",aacReptHdr[0]);
}
onpath_char=
ptr->dir_queue_ptr->on_search_path?IS_ON:IS_OFF;
if(!got_dir_specs)onpath_char=IS_ON;
MPRINTF("%8s%c%s%s%s\n",ptr->progname,onpath_char,
ptr->dir_queue_ptr->dirname,
ptr->progname,comexebat[ptr->ext_type]);
for(;;){
if(!ptr->next)return;
if(ptr->progname!=ptr->next->progname)break;
ptr=ptr->next;
onpath_char=
ptr->dir_queue_ptr->on_search_path?IS_ON:IS_OFF;
if(!got_dir_specs)onpath_char=IS_ON;
MPRINTF("%8s%c%s%s%s\n","",onpath_char,
ptr->dir_queue_ptr->dirname,
ptr->progname,comexebat[ptr->ext_type]);
}
}
ptr=ptr->next;
}
}
void show_big_results(void){
/** void show_big_results(void);
If show_all_files is zero
Displays the the big_prog_queue entries, if any conflicts.
If not, displays a 'no conflicts found' message.
else
displays all big_prog_queue entries, if there are any.
if not, displays a 'no files found' message.
endif
this function is exactly like show_results, but uses
big_prog_queue instead of prog_queue, and prints out
file dates, times and sizes.
**/
/* pseudocode
if off end of list
return
endif
do forever
if no entry after this
quit
endif
if (showing all files) or
(this progname matches next progname)
if showed_something is zero
showed_something=1
print header
endif
print this entry with basename in far left of line
do forever
if off end of list
quit
endif
if next entry not same as this entry
break from inner do loop
endif
print current element without name at far left
point at next element in chain
end do
endif
point at next element in chain
end do
*/
struct big_prog_queue_struct *ptr;
char out_array[200]; /* plenty big */
ptr=big_prog_queue;
for(;;){
if(!ptr)return;
if(show_all_files||
(ptr->next&&
ptr->progname==ptr->next->progname&&
(!got_dir_specs||ptr->dir_queue_ptr->on_search_path))){
if(!showed_something){
showed_something=1;
MPRINTF("%s",aacReptHdr[0]);
MPRINTF("%s",aacReptHdr[1]);
MPRINTF("%s",
show_nonpath&&got_dir_specs?aacReptHdr[2]:"");
MPRINTF("%s",aacReptHdr[0]);
}
make_big_output(out_array,ptr->progname,ptr);
MPRINTF("%s\n",out_array);
for(;;){
if(!ptr->next)return;
if(ptr->progname!=ptr->next->progname)break;
ptr=ptr->next;
make_big_output(out_array,"",ptr);
MPRINTF("%s\n",out_array);
}
}
ptr=ptr->next;
}
}
void make_big_output(char *out_array,char *label,
struct big_prog_queue_struct *ptr){
/** void make_big_output(char *out_array,char *label,
struct big_prog_queue_struct *ptr);
this takes info about a file to print out, and tries to make it 79
chars long. if it must be longer, ensures a space before file
size and date.
**/
char *temp_ptr=out_array;
int chars_to_go=53,am_pm,onpath_char;
unsigned int hour,minute,day,month,year;
hour=ptr->ft.time_bits.ft_hour;
minute=ptr->ft.time_bits.ft_min;
day=ptr->fd.date_bits.fd_day;
month=ptr->fd.date_bits.fd_month;
year=ptr->fd.date_bits.fd_year;
/*
** set am/pm
*/
am_pm=ptr->ft.time_bits.ft_hour>=12?'p':'a';
/*
** normalize hour
*/
if(!hour)hour=12;
else if(hour>12)hour-=12;
/*
** normalize year
*/
year+=1980;
year%=100; /* get rid of century */
/*
** set onpath_char
*/
onpath_char=ptr->dir_queue_ptr->on_search_path?IS_ON:IS_OFF;
if(!got_dir_specs)onpath_char=IS_ON;
/*
** print out text data
*/
sprintf(out_array,"%8s%c%s%s%s",label,onpath_char,
ptr->dir_queue_ptr->dirname,
ptr->progname,comexebat[ptr->ext_type]);
/*
** append spaces if desired
*/
while(*temp_ptr){temp_ptr++;chars_to_go--;};
*temp_ptr++=' '; /* guarantee one space */
while(chars_to_go>0){*temp_ptr++=' ';chars_to_go--;}/*blank fill*/
/*
** write file size, date, etc.
*/
sprintf(temp_ptr,"%7ld %2u-%02u-%02u %2u:%02u%c",
ptr->ff_fsize,month,day,year,hour,minute,am_pm);
}
void check_screen_overflow(void){
/** void check_screen_overflow(void);
this function simply notes whether the screen is filled up, and
forces the user to press a key before continuing if so, if
screenful_pause is nozero.
**/
if(!screenful_pause)return;
if(!lines_left_on_screen){
lines_left_on_screen=SCREEN_LINES;
fprintf(stderr,"Press a key to continue, ESC for nonstop\n");
if(getch()==27)screenful_pause=0;
return;
}
lines_left_on_screen--;
}
void *malloc_nofree(size_t size){
/** void *malloc_nofree(size_t size);
malloc() allows you to free any allocation you make. this is
great, but if you're allocating a lot of small amounts of memory,
malloc's housekeeping information can take up more space than
your data!
This lean front end to malloc() allocates MALLOC_BLOCK_SIZE
bytes at a time, and only keeps track of how much is left to
give out and its address. This means you can't free() anything
you get from malloc_nofree() -- hence the name.
It is not considered good form to allocate memory and then
allow your program to quit without freeing it. Since DOS does in
fact clean up, there's no real need to manually free all the
blocks we get from malloc(). If you port this to another system,
you might have to implement a method of freeing the memory blocks
you get from malloc().
**/
static void *current_block=(void *)0;
static size_t on_hand=0;
static int try_malloc=1;
void *ret_ptr;
if(size>MALLOC_BLOCK_SIZE){
#ifdef DEBUG
ret_ptr=malloc(size);
if(ret_ptr)total_malloc_memory+=(unsigned long)size;
return ret_ptr;
#else
return malloc(size);
#endif
}
if(on_hand<size){ /* forget leftover stub, malloc() */
if(try_malloc){
current_block=malloc(MALLOC_BLOCK_SIZE);
#ifdef DEBUG
total_memory_wasted+=(unsigned long)on_hand;
if(current_block){
total_malloc_memory+=MALLOC_BLOCK_SIZE;
}
#endif
on_hand=current_block?MALLOC_BLOCK_SIZE:0;
if(!on_hand)try_malloc=0;
}
}
if(on_hand>=size){
ret_ptr=current_block;
on_hand-=size;
current_block=&((char *)current_block)[size];
#ifdef DEBUG
total_memory_used+=(unsigned long)size;
#endif
return ret_ptr;
}
return malloc(size);
}
int cur_drive_is_subst(void){
/** int cur_drive_is_subst(void);
works by getting the current working directory, then
performing get_truename() on it. if get_truename()
returns nonzero (it got a different answer), we are
on a SUBST drive. Used this instead of int 21h, 1Fh
since 1Fh is undocumented (in MS-DOS Encyclopedia,
anyway) and we already need the TRUENAME function.
Why introduce more undocumented functions than is
absolutely necessary?
returns 1 yes, current drive is SUBST drive
0 no, current drive is not SUBST
always returns 0 if !dos_has_subst
**/
char buffer[MAXPATH+15],true[MAXPATH+15],*ptr;
if(!dos_has_subst)return 0;
if(!getcwd(buffer,sizeof(buffer)-1))EXIT(ERROR_DOS)
ptr=buffer;
while(*ptr)ptr++;
if(ptr[-1]!='\\'){ /* ensure ends in backslash */
*ptr='\\';
ptr[1]=0;
}
return get_truename(true,buffer);
}
int get_truename(char *true,char *original){
/** int get_truename(char *true,char *original);
tries to get the true name corresponding to the name in
*original. ensures the new name ends with '\\'
returns 1 yes got a new name and it's not the same as the
old one
0 no, didn't get a new name or !dos_has_subst or it
was the same as the old
**/
char *ptr;
union REGS inregs,outregs;
struct SREGS segregs;
if(!dos_has_subst)return 0;
inregs.x.ax=0x6000;
/*lint -e10 -e67 -e507 lint chokes on Borland macros */
segregs.ds=FP_SEG(original);
inregs.x.si=FP_OFF(original);
segregs.es=FP_SEG(true);
inregs.x.di=FP_OFF(true);
/*lint +e10 +e67 +e507*/
int86x(0x21,&inregs,&outregs,&segregs);
ptr=true;
if(!*ptr)return 0;
while(*ptr)ptr++;
if(ptr[-1]!='\\'){ /* ensure ends in backslash */
*ptr='\\';
ptr[1]=0;
}
return strcmp(original,true)?1:0;
}
int does_dos_have_subst(void){
/** int does_dos_have_subst(void);
since DOS did not supply SUBST until version 3.1 (MS-DOS
Encyclopedia, Microsoft, p. 938), we don't
have to worry about it until then.
returns 1 for DOS 3.1 or greater
0 otherwise
**/
union REGS inregs,outregs;
struct SREGS segregs;
inregs.h.ah=0x30;
inregs.h.al=0x00;
int86x(0x21,&inregs,&outregs,&segregs);
#ifdef DEBUG
printf("DOS version is %d.%02d\n",
(int)outregs.h.al,(int)outregs.h.ah);
#endif
if(outregs.h.al<3)return 0; /* 0.00 - 2.99 */
if((outregs.h.al==3)&&(outregs.h.ah<10))return 0; /* 3.00-3.09 */
return 1; /* 3.10 + */
}
#ifdef __ZTC__
int getdisk(void){
/** int getdisk(void);
returns the number for the current disk drive, A=1, B=2, etc.
**/
unsigned int retval;
dos_getdrive(&retval);
return (int)retval;
}
#endif