/*
 * cc -- unix-like compiler driver for amiga lattice c
 *
 * adapted for lattice version 5.0 and extensively modified
 *   by Miles Bader, from the original by Fred Fish (copyright
 *   follows).
 *
 * Modified 8/29/90 by The Starbound Software Group, Dave Lowrey - programmer.
 *   The invocation of blink used the explicit path name of blink's
 *   executable (i.e. usr:lc/bin/blink) instead of relying on PATH (as is
 *   done with all of the other commands, such as lc1). I changed the
 *   code to just specify 'BLINK'. This simplifies things when making BLINK
 *   resident.
 *
 *   Made the '-g' option add the ADDSYM parameter to the blink
 *   command line (cpr can guru the system if the executable wasn't linked
 *   with ADDSYM).
 *
 *	Moved the "-m" flag to the lc2 command. It was incorrectly placed on the
 *	lc1 command line.
 *
 *	Fixed to work with SAS/C as well as Lattice/C
 *
 */

/************************************************************************
 *                                                                      *
 *                      Copyright (c) 1985, Fred Fish                   *
 *                          All Rights Reserved                         *
 *                                                                      *
 *      This software and/or documentation is released into the         *
 *      public domain for personal, non-commercial use only.            *
 *      Limited rights to use, modify, and redistribute are hereby      *
 *      granted for non-commercial purposes, provided that all          *
 *      copyright notices remain intact and all changes are clearly     *
 *      documented.  The author makes no warranty of any kind with      *
 *      respect to this product and explicitly disclaims any implied    *
 *      warranties of merchantability or fitness for any particular     *
 *      purpose.                                                        *
 *                                                                      *
 ************************************************************************
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <dos.h>
#include <proto/dos.h>
#include <ios1.h> /* magic */

#include "common.h"
#include "list.h"
#include "dstr.h"
#include "op.h"
#include "options.h"

#include "config.h"

static char *locate(char *file,struct list *list);      /* find a file */
static void cleanObjects(int mustExist);        /* Remove .o for link and go mode */
static int makeObjs();                  /* Reduce to list of object files */
static int parseCommandLine();          /* Deal with command line */
static int compile(struct op *op);      /* Translate from .c to .o */
static int assemble(struct op *op);     /* Translate from .s to .o */
static int link();                      /* Gather .o's into executable */
static int runCmd(struct dstr *cmd);

#ifdef amiga
#define CHECK_ABORT chkabort()
#else
#define CHECK_ABORT                             /* NULL expansion */
#endif  /* amiga */

DBUG_GLOBALS

/*
 *      flags set by command line arguments
 */
static int debugLevel=DEFAULT_DEBUGLEVEL;       /* -g (debugging level) */

struct list *libDirs=NULL;
struct list *incDirs=NULL;
struct list *binDirs=NULL;
struct list *defines=NULL;
struct list *undefines=NULL;
struct list *libs=NULL;

struct list *pass1PassThrus=NULL;
struct list *pass2PassThrus=NULL;
struct list *optPassThrus=NULL;
struct list *linkPassThrus=NULL;

struct list *ops=NULL;

static char *outfile=DEFAULT_EXEC;      /* output file name from linker */
static char *tempDir=TEMPDIR;           /* where to keep quad files */
static char *sinkFile=SINKFILE;         /* where to put output from passes */

struct dstr *cmdLine=NULL;

static int abortmsg()
{
    if(options_IsSet(OPT_ECHO))
        fprintf(stderr,"cc: aborting\n");
    return 1; /* abort */
}

static int cleanup(status)
int status;
{
    if(cmdLine!=NULL)   dstr_Free(cmdLine);

    if(libDirs!=NULL)   list_Free(libDirs);
    if(incDirs!=NULL)   list_Free(incDirs);
    if(binDirs!=NULL)   list_Free(binDirs);
    if(defines!=NULL)   list_Free(defines);
    if(undefines!=NULL) list_Free(undefines);
    if(libs!=NULL)      list_Free(libs);

    if(pass1PassThrus!=NULL)    list_Free(pass1PassThrus);
    if(pass2PassThrus!=NULL)    list_Free(pass2PassThrus);
    if(optPassThrus!=NULL)      list_Free(optPassThrus);
    if(linkPassThrus!=NULL)     list_Free(linkPassThrus);

    if(ops!=NULL)       list_Free(ops);

    return status;
}

static void init()
{
    int *opt;

    options_Init();
    for(opt=defaultOptions; *opt!=0; opt++)
        options_Set(*opt);

    cmdLine=dstr_Create(30);            /* global command buffer */

    libDirs=list_Create((void **)libDirsInit);
    incDirs=list_Create((void **)incDirsInit);
    binDirs=list_Create((void **)binDirsInit);
    defines=list_Create((void **)definesInit);
    undefines=list_Create((void **)undefinesInit);
    libs=list_Create((void **)libsInit);

    pass1PassThrus=list_Create(NULL);
    pass2PassThrus=list_Create(NULL);
    optPassThrus=list_Create(NULL);
    linkPassThrus=list_Create(NULL);

    ops=list_Create(NULL);
    list_SetFree(ops,op_Free);
}

void main(argc, argv)
int argc;
char *argv[];
{
    int status;

    DBUG_ENTER("main");

    onexit(cleanup);
    init();

    onbreak(abortmsg);

    if(parseCommandLine(argc,argv)==0)
        fatal("no files specified");

    cleanObjects(FALSE);        /* get rid of existing object files if any */

    status=makeObjs();
    if(status==0 && options_IsSet(OPT_LINK)){
        status=link();
        if(list_GetLen(ops)==1)
            cleanObjects(TRUE);
    }

    exit(status);
}

/*
 *      The following macro is used to allow optional whitespace between
 *      an option and it's argument.  Argp is left pointing at the option
 *      and argv and argc are adjusted accordingly if necessary.
 *
 *      Note that there is no check for missing option arguments.  In
 *      particular, -o -V will blindly take -V as the output file name.
 *
 */

#define XARG(argc,argv,argp) {if(*++argp=='\0'){argp=(*argv++);argc--;}}

static int parseCommandLine(argc,argv)
int argc;
char **argv;
{
    register char *argp;

    DBUG_ENTER("parseCommandLine");

    argc--;
    argv++;
    while(argc-- > 0){
        CHECK_ABORT;

        argp=*argv++;

        if(*argp=='+')
            options_SetByName(argp+1);
        else if(*argp!='-')
            list_Add(ops,(void *)op_Create(argp));
        else
            switch(*++argp){
                case 'c':
                    options_Clear(OPT_LINK);    /* don't link object modules */
                    break;
                case 'p':                       /* pass thru args to a pass */
                    switch(*++argp){
                        case '1':               /* pass 1 */
                            XARG(argc,argv,argp);
                            list_Add(pass1PassThrus,argp);
                            break;
                        case '2':               /* pass 2 */
                            XARG(argc,argv,argp);
                            list_Add(pass2PassThrus,argp);
                            break;
                        case 'o':               /* optimizer */
                            XARG(argc,argv,argp);
                            list_Add(optPassThrus,argp);
                            break;
                        case 'l':               /* linking */
                            XARG(argc,argv,argp);
                            list_Add(linkPassThrus,argp);
                            break;
                        default:
                            fatal("unknown pass '%c'; must be one of [12ol]",argp[-1]);
                    }
                    break;
                case 'D':
                    XARG(argc,argv,argp);
                    list_Add(defines,(void *)argp);
                    break;
                case 'E':
                    warning("-E unimplemented, converted to -P instead");
                    /* fall through */
                case 'P':
                    options_Clear(OPT_COMPILE);
                    options_Clear(OPT_ASSEMBLE);
                    options_Clear(OPT_LINK);
                    break;
                case 'g':
                    if(*++argp!='\0') /* optional debuggin level */
                        debugLevel=atoi(argp);
                    options_Set(OPT_DEBUG);
                    break;
                case 'I':
                    XARG(argc,argv,argp);
                    list_Add(incDirs,(void *)argp);
                    break;
                case 'l':
                    XARG(argc,argv,argp);
                    list_Add(libs,(void *)argp);
                    break;
                case 'L':
                    XARG(argc,argv,argp);
                    list_Add(libDirs,(void *)argp);
                    break;
                case 'B':
                    XARG(argc,argv,argp);
                    list_Add(binDirs,(void *)argp);
                    break;
                case 'O':
                    options_Set(OPT_OPTIMIZE);
                    break;
                case 'o':
                    XARG(argc,argv,argp);
                    outfile=argp;
                    break;
                case 't':               /* warning, non-standard */
                    XARG(argc,argv,argp);
                    tempDir=argp;
                    break;
                case 'S':
                    warning("-S option not yet implemented, ignored");
                    options_Clear(OPT_ASSEMBLE);
                    break;
                case 'U':
                    XARG(argc,argv,argp);
                    list_Add(undefines,(void *)argp);
                    break;
                default:
                    fatal("unknown option '%c'",*argp);
                    break;
            }
    }

    DBUG_RETURN(int,list_GetLen(ops));
}

/*
 *      For each operand, do compilation or assembly as necessary, to
 *      reduce to an object file in the current directory.
 */
static int makeObjs()
{
    int n;
    void **els;
    int numOps=list_GetLen(ops);
    int status=0;

    DBUG_ENTER("makeObjs");

    for(n=numOps,els=list_GetEls(ops); n>0; n--,els++){
        struct op *op=(struct op *)*els;

        CHECK_ABORT;

        if(numOps>1 && (op_IsC(op) || op_IsS(op)))
            printf("%s.%s:\n",op->rootname,op->suffix);

        if(op_IsC(op))
            status=compile(op);
        else if(op_IsS(op))
            status=assemble(op);
    }

    DBUG_RETURN(int,status);
}

/* handy little routine */
void addfList(ds,fmt,list)
struct dstr *ds;
char *fmt;
struct list *list;
{
    int n;
    void **els;

    for(n=list_GetLen(list),els=list_GetEls(list); n>0; n--,els++)
        dstr_Addf(ds,fmt,(char *)*els);
}

/*
 *      Note that commands to cc of the form "-l<name>" get interpreted
 *      to mean use a library called "name.lib" from the library
 *      directory.
 *      -lm is specially interpreted to be the appropriate math library.
 */
static int link()
{
    int n;
    void **els;

    DBUG_ENTER("link");

    dstr_Clear(cmdLine);
/* Change 'blink' command invocation to use normal PATH search DWL */
/*    dstr_Addf(cmdLine,"%s ",locate("blink",binDirs)); */  /* DWL */
    dstr_Addf(cmdLine,"blink  ");							/* DWL */

    if(options_IsSet(OPT_DETACH))
        dstr_Append(cmdLine,locate("cback.o",libDirs));
    else if(options_IsSet(OPT_RESIDENT)){
        if(options_IsSet(OPT_CATCH))
            dstr_Append(cmdLine,locate("catchres.o",libDirs));
        else
            dstr_Append(cmdLine,locate("cres.o",libDirs));
    }else if(options_IsSet(OPT_CATCH))
        dstr_Append(cmdLine,locate("catch.o",libDirs));
    else
        dstr_Append(cmdLine,locate("c.o",libDirs));

    for(n=list_GetLen(ops),els=list_GetEls(ops); n>0; n--,els++){
        struct op *op=(struct op *)*els;
        char *name=(op_IsO(op) ? op->rootname : op->basename);

        dstr_Addf(cmdLine,"+%s.o",name);
    }

    if(options_IsSet(OPT_TINYMAIN))
        dstr_Addf(cmdLine," define __main=__tinymain ");

    dstr_Addf(cmdLine," library ");
    for(n=list_GetLen(libs),els=list_GetEls(libs); n>0; n--,els++){
        char buffer[50];
        char *lib=(char *)*els;

        if(strcmp(lib,"m")==0){
            /* map -lm to appropiate library */

            strcpy(buffer,"lib:lcm");

            if(options_IsSet(OPT_FFP))
                strcat(buffer,"ffp");
            else if(options_IsSet(OPT_IEEE))
                strcat(buffer,"ieee");
            else if(options_IsSet(OPT_881))
                strcat(buffer,"881");

            /*
             * there are only r and s libraries for lcm.lib in the lattice
             * distribution; if there is a way to get the lcmieee, lcmffp,
             * & lcm881 libraries to work with reg-args & short-ints, I
             * couldn't find it.
             */
            if(options_IsSet(OPT_SHORTINTS))
                strcat(buffer,"s");
            if(options_IsSet(OPT_REGARGS))
                strcat(buffer,"r");

            strcat(buffer,".lib");
        }else if(strcmp(lib,"lc")==0){
            /* do the standard c library */

            strcpy(buffer,"lib:lc");

            if(options_IsSet(OPT_SHORTINTS))
                strcat(buffer,"s");
            if(options_IsSet(OPT_REGARGS))
                strcat(buffer,"r");
            if(options_IsSet(OPT_ABSDATA))
                strcat(buffer,"nb");

            strcat(buffer,".lib");

            if(options_IsSet(OPT_REGARGS)){
                /*
                 * Now, do it AGAIN, but without registers, to catch any missing refs.
                 * I don't think this is exactly the right thing to do, but there seem
                 * to be internal functions missing from the register-fied libraries
                 */

                strcat(buffer,"+lib:lc");

                if(options_IsSet(OPT_SHORTINTS))
                    strcat(buffer,"s");
                if(options_IsSet(OPT_ABSDATA))
                    strcat(buffer,"nb");

                strcat(buffer,".lib");
            }
        }else{
            sprintf(buffer,"%s.lib",lib);
            strcpy(buffer,locate(buffer,libDirs));
        }

        if(n==1)
            dstr_Append(cmdLine,buffer);
        else
            dstr_Addf(cmdLine,"%s+",buffer);
    }

    if(outfile==NULL)
        /* there better be at least one source file! */
        outfile=((struct op *)list_GetEls(ops))->basename;
/* For some reason, this doesn't work with SAS/C 5.10          */
/* It isn't really needed, as there is no map file produced DWL */
/*    dstr_Addf(cmdLine," to %s map nil:",outfile); */  /* DWL */
    dstr_Addf(cmdLine," to %s",outfile);
    											/* DWL */
    if(options_IsSet(OPT_DEBUG))				/* DWL */
    	dstr_Append(cmdLine, " ADDSYM ");		/* DWL */

    addfList(cmdLine," %s",linkPassThrus);

    DBUG_RETURN(int,runCmd(cmdLine));
}

/*
 *      compile one operand from a C source program to an object module.
 */
static int compile(op)
struct op *op;
{
    int status=0;

    DBUG_ENTER("compile");

    if(options_IsSet(OPT_ASSEMBLE)){
        status=pass1(op);
        if(status==0 && options_IsSet(OPT_COMPILE)){
            CHECK_ABORT;
            if(options_IsSet(OPT_OPTIMIZE))
                status=optimize(op);
            if(status==0)
                status=pass2(op);
        }
    }

    DBUG_RETURN(int,status);
}

/*
 *      Note that because of brain-damage in the fact that -p to lc1 removes
 *      all predefined defs, we must add them so replacing -c with -P in the
 *      cc command line will result in the same set of predefined symbols.
 *      This is rather ugly and leaves a hole for future problems if we
 *      get out of sync with respect to what names the compiler predefines.
 */
static int pass1(op)
register struct op *op;
{
    char tmpFile[MAXPATH];
    int status;
    int n;
    void **els;

    DBUG_ENTER("pass1");

    dstr_Clear(cmdLine);

    dstr_Append(cmdLine,locate((options_IsSet(OPT_BIGLC1) ? "lc1b" : "lc1"),binDirs));

    if(options_IsSet(OPT_DEBUG)){
        dstr_Addf(cmdLine," -d%d",debugLevel);
        if(options_IsSet(OPT_OPTIMIZE)){
            warning("-g incompatible with -O, -O turned off");
            options_Clear(OPT_OPTIMIZE);
        }
    }

    if(options_IsSet(OPT_FFP))
        dstr_Addf(cmdLine," -ff");
    else if(options_IsSet(OPT_881))
        dstr_Addf(cmdLine," -f8");
    else if(options_IsSet(OPT_IEEE))
        dstr_Addf(cmdLine," -fi");
    else
        dstr_Addf(cmdLine," -fl");

    if(options_IsSet(OPT_SHORTINTS))
        dstr_Addf(cmdLine," -w");
    if(options_IsSet(OPT_LONGALIGN))
        dstr_Addf(cmdLine," -l");
/*                                     Moved this to lc2
    if(options_IsSet(OPT_020))
        dstr_Addf(cmdLine," -m2");
    else if(options_IsSet(OPT_030))
        dstr_Addf(cmdLine," -m3");
*/
    if(options_IsSet(OPT_CATCH) && !options_IsSet(OPT_ABSDATA)){
        warning("+catch implies +abs-data");
        options_Set(OPT_ABSDATA);
    }

    dstr_Addf(cmdLine," -b%d",options_IsSet(OPT_ABSDATA) ? 0 : 1);
    dstr_Addf(cmdLine," -r%d",options_IsSet(OPT_ABSCODE) ? 0 : 1);

    if(options_IsSet(OPT_REGARGS))
        dstr_Addf(cmdLine,"r"); /* part of -r switch */

    {
        char buf[15], *p=buf;

        if(options_IsSet(OPT_ANSI))             *p++='a';
        if(options_IsSet(OPT_CPP))              *p++='+';
        if(options_IsSet(OPT_TRAD))             *p++='l', *p++='o';
        if(options_IsSet(OPT_REGARGS))          *p++='r';
        if(options_IsSet(OPT_PURESTRINGS))      *p++='s';

        if(p>buf){
            *p='\0';
            dstr_Addf(cmdLine," -c%s",buf);
        }
    }

    if(options_IsSet(OPT_COMPILE)){
        strcpy(tmpFile,tempDir);
        strcat(tmpFile,op->basename);
        strcat(tmpFile,".q");
        dstr_Addf(cmdLine," -o%s",tmpFile);
    }else{
        strcpy(tmpFile,op->basename);
        strcat(tmpFile,".pp");

        dstr_Addf(cmdLine," -o%s -p",tmpFile);
    }

    for(n=list_GetLen(undefines),els=list_GetEls(undefines); n>0; n--,els++){
        /*************************
        dstr_Addf(cmdLine," -u%s",(char *)*els);
        **************************/
        warning("-U%s ignored! (unimplemented)",(char *)*els);
    }

    addfList(cmdLine," -d%s",defines);
    addfList(cmdLine," %s",pass1PassThrus);

    for(n=list_GetLen(incDirs),els=list_GetEls(incDirs); n>0; n--,els++){
        char *id=(*els);
        int len=strlen(id);

        dstr_Addf(cmdLine," -i%s",*els);

        if(len>0 && id[len-1]!=':' && id[len-1]!='/')
            dstr_Addf(cmdLine,"/");
    }

    dstr_Addf(cmdLine," %s",op->rootname);

    status=runCmd(cmdLine);
    if(status==0 && options_IsSet(OPT_EXEC) && !readable(tmpFile))
        status=1;

    DBUG_RETURN(int,status);
}

/* run the optimizer */
static int optimize(op)
struct op *op;
{
    DBUG_ENTER("optimize");

    dstr_Clear(cmdLine);
    dstr_Addf(cmdLine,"%s %s%s",locate("go",binDirs),tempDir,op->basename);

    addfList(cmdLine," %s",optPassThrus);

    DBUG_RETURN(int,runCmd(cmdLine));
}

/*
 *      Run second pass of compiler on a single operand.
 */
static int pass2(op)
struct op *op;
{
    char objFile[MAXPATH];
    int status;

#ifdef DBUG
    fprintf(stderr,"Entering pass2...\n");
#endif

    DBUG_ENTER("pass2");

    dstr_Clear(cmdLine);

    dstr_Append(cmdLine,locate("lc2",binDirs));

    if(!options_IsSet(OPT_STACKCHECK))
        dstr_Addf(cmdLine," -v");

/* Moved -m from lc1 to lc2 (The lattice doc doesn't say WHERE it goes!) DWL */

    if(options_IsSet(OPT_020))			/* DWL */
        dstr_Addf(cmdLine," -m2");		/* DWL */
    else if(options_IsSet(OPT_030))		/* DWL */
        dstr_Addf(cmdLine," -m3");		/* DWL */

    strcpy(objFile,op->basename);
    strcat(objFile,".o");

    dstr_Addf(cmdLine," -o%s",objFile);

    dstr_Addf(cmdLine," %s%s",tempDir,op->basename);

    addfList(cmdLine," %s",pass2PassThrus);

    status=runCmd(cmdLine);
    if(status==0 && options_IsSet(OPT_EXEC) && !readable(objFile))
        status=1;

    DBUG_RETURN(int,status);
}

/*
 *      I have not yet had occasion to use the macro assembler,so this
 *      part is not yet implemented.  If anyone wants to send me the
 *      appropriate code, I will be glad to install it.
 */
static int assemble(op)
struct op *op;
{
    DBUG_ENTER("assemble");
    warning("assembly pass not yet implemented");
    DBUG_RETURN(int,1);
}

static void filteredOutputFrom(source,filter)
FILE *source;
char **filter;
{
    char buf[200];

    DBUG_ENTER("FilteredOutputFrom");

    while(!feof(source)){
        char **ignore;

        fgets(buf,sizeof(buf),source);

        for(ignore=filter; *ignore!=NULL; ignore++)
            if(strncmp(*ignore,buf,strlen(*ignore))==0)
                break;

        if(*ignore==NULL)
            fputs(buf,stdout);
    }

    DBUG_VOID_RETURN;
}

#ifdef AMIGA

static int execToFile(cmd,sink)
char *cmd;
FILE *sink;
{
    /* this is a dopey version that uses Execute, since I can't get fork to work */
    long fh;

    DBUG_ENTER("execToFile");

    if(sink==NULL)
        fh=0;
    else
        fh=chkufb(fileno(sink))->ufbfh;

    DBUG_RETURN(int,Execute(cmd,0L,fh)!=(-1));

#if 0
    char *argv[50],**argp=argv;
    struct ProcID child;
    static struct FORKENV env={0,0,0,0,0,NULL};
    int i;

    DBUG_ENTER("execToFile");

    if(cmd==NULL)
        DBUG_RETURN(int,1);

    /* this is sort of stupid, cause forkv will just put them back together,
     * but oh well...
     */
    *argp=strtok(cmd," \t");
    while(*argp!=NULL)
        *++argp=strtok(NULL," \t");

    if(sink==NULL)
        env.std_out=0;
    else{
        struct UFB *ufb=chkufb(fileno(sink));
        int i;

        printf("argv[%d]:",argp-argv);
        for(argp=argv;*argp!=NULL;argp++)
            printf(" %s",*argp);
        putchar('\n');

        printf("env: %d, %d, %d, %d, %d, %d\n",
                env.priority, env.stack, env.std_in, env.std_out,
                env.console, env.msgport);

        printf("ufb==0x%x, fh==0x%x, go? ",ufb,ufb->ufbfh);

        fflush(stdout);

        env.std_out=ufb->ufbfh; /* magic to get amigados handle from FILE */
    }

    if((i=forkv(argv[0],argv,&env,&child))==-1){
        poserr("forkv");
        DBUG_RETURN(int,1);
    }

    printf("Fork returns: %d\n",i);

    DBUG_RETURN(int,wait(&child));
#endif
}

#endif

static int runCmd(cmd)
struct dstr *cmd;
{
    int status;
    char *buf=dstr_GetBuf(cmd);

    DBUG_ENTER("RunCommand");

    DBUG_3("cmd","execute '%s'",buf);
    if(options_IsSet(OPT_ECHO)){
        puts(buf);
        fflush(stdout);
    }

    CHECK_ABORT;

    if(options_IsSet(OPT_EXEC)){
        FILE *sink;

        if(sinkFile!=NULL && options_IsSet(OPT_FILTER)){
            sink=fopen(sinkFile,"w+");
            if(sink==NULL)
                perror(sinkFile);
        }else
            sink=NULL;

        status=execToFile(buf,sink);
        DBUG_3("sys","subcommand returns status %d",status);

        if(sink!=NULL){
            rewind(sink);
            filteredOutputFrom(sink,filteredPrefixes);
            fclose(sink);
            unlink(sinkFile);
        }

        DBUG_RETURN(int,status);
    }else
        DBUG_RETURN(int,0);
}

/*
 *      Look through the list of paths pointed to by "vec" until we find
 *      a file with name given pointed to by "namep".  If none is found,
 *      the name pointed to by namep is returned.
 */
static char *locate(name,paths)
char *name;
struct list *paths;
{
    static char namebuf[MAXPATH];
    void **els;
    int n;

    DBUG_ENTER("locate");

    if(strchr(name,':')!=NULL || *name=='/')
        return name;    /* absolute */

    for(n=list_GetLen(paths),els=list_GetEls(paths); n>0; n--,els++){
        int len=strlen((char *)*els);

        strcpy(namebuf,(char *)*els);
        if(len>0 && namebuf[len-1]!=':' && namebuf[len-1]!='/')
            strcat(namebuf,"/");
        strcat(namebuf,name);

        DBUG_3("try","look for '%s'",namebuf);
        if(readable(namebuf)){
            name=namebuf;
            break;
        }
    }

    DBUG_RETURN(char *,name);
}

/*
 *      Check to see if the file exists and is readable.
 */
static int readable(name)
char *name;
{
    register int status=0;
    register long fildes;

    DBUG_ENTER("readable");

#ifdef unix
    fildes=open(name,O_RDONLY);
    if(fildes >= 0){
        (void) close(fildes);
        status=1;
    }
#else
    fildes=Lock(name,ACCESS_READ);
    if(fildes != 0){
        UnLock(fildes);
        status=1;
    }
#endif

    DBUG_RETURN(int,status);
}

/*
 *      If an executable is made from a single C file, the normal behavior
 *      for the unix environment is to treat the .o file as an intermediate
 *      file and remove it, so we follow suit.
 */
static void cleanObjects(mustExist)
int mustExist;
{
    int n;
    void **els;

    DBUG_ENTER("cleanObjects");

    for(n=list_GetLen(ops),els=list_GetEls(ops); n>0; n--,els++){
        char buf[MAXPATH];
        struct op *op=(struct op *)*els;

        if(!op_IsO(op)){
            strcpy(buf,op->basename);
            strcat(buf,".o");
            if(unlink(buf)!=0 && mustExist)
                warning("can't delete '%s'",buf);
        }
    }

    DBUG_VOID_RETURN;
}
