/*** error.c - error handling functions
*
*       Copyright <C> 1989, Microsoft Corporation
*
* Purpose:
*   This module contains all error message functions and variuos
*   functions that check if errror condition has occured.
*
*   This Module contains Proprietary Information of Microsoft
*   Corporation and should be treated as Confidential.
*
* Revision History:
*
*   [WJK]  28-Jun-1990             Created
*
*************************************************************************/

#include                <minlit.h>      /* Types, constants */
#include                <bndtrn.h>      /* More types and constants */
#include                <bndrel.h>      /* Types and constants */
#include                <lnkio.h>       /* Linker I/O definitions */
#include                <lnkmsg.h>      /* Error messages */
#include                <nmsg.h>        /* Near message strings */
#include                <extern.h>      /* External declarations */
#include                <string.h>
#if (defined(WIN_NT) OR defined(DOSX32)) AND (NOT defined( _WIN32 ))
#define i386
#endif
#include                <stdarg.h>
#if EXE386
#include                <exe386.h>
#endif
#if WIN_3
#include                <windows.h>
#endif
#if NEWIO
#include                <errno.h>       /* System error codes */
#endif

#define DEBUG_WIN FALSE
#if DEBUG_WIN
char    szDebugBuffer[80];
#define DEBUGW(parm1,parm2)\
    {\
    wsprintf(szDebugBuffer,parm1,(char far*)parm2);\
    OutputDebugString(szDebugBuffer);\
    }
#else
#define DEBUGW(parm1,parm2)
#endif


#if OSEGEXE AND NOT QCLINK
extern int              yylineno;       /* Current line in definitions file */
#else
#define yylineno        -1
#endif


#if AUTOVM
extern BYTE FAR * NEAR  FetchSym1(RBTYPE rb, WORD Dirty);
#define FETCHSYM        FetchSym1
#else
#define FETCHSYM        FetchSym
#endif


LOCAL char              chErr = 'L';    /* Error message prefix */


/*
 *  LOCAL FUNCTION PROTOTYPES
 */

LOCAL void cdecl NEAR ErrSub(MSGTYPE msg, WORD fWarn, va_list pArgList);
LOCAL void            vFmtPrint(char *fmt, va_list pArgList);


/*** ChkInput - check input file for I/O errors
*
* Purpose:
*   Check if there were any I/O errors on input file.
*
* Input:
*   No explicit value is passed. The global input file bsInput is
*   used.
*
* Output:
*   If everything is OK function returns, otherwise it calls Fatal
*   with appropriate error message.
*
* Exceptions:
*   None.
*
* Notes:
*   None.
*
*************************************************************************/

void                        ChkInput(void)
{
    if (feof(bsInput))
        Fatal(ER_eofobj);
    else if (ferror(bsInput))
        Fatal(ER_ioerr, strerror(errno));
}

// Comment : GetMsg and __NMSG_TEXT are generated by MKMSG [jp]

/*** ErrPrefix - write error message prefix
*
* Purpose:
*   Write out error message prefix.  If we are parsinf .DEF file or reading
*   .OBJ files then the error message prefix takes form "<filename> : "
*   otherwise  this is "LINK : ".
*
* Input:
*   No explicit value is passed.
*
* Output:
*   No explicit value is returned.
*
* Exceptions:
*   None.
*
* Notes:
*   None.
*
*************************************************************************/

void                    ErrPrefix(void)
{
    DisplayBanner();
    if (fDrivePass || yylineno > 0)
        OutFileCur(bsErr);
    else
        FmtPrint(lnknam);
    FmtPrint(" : ");
}

#pragma check_stack(on)

/*** OutFileCur - write out current input file name
*
* Purpose:
*   Write out current input file name. Used by error message functions.
*   File name is written in the following formats:
*       <filename>(nnn)
*       <filename>(<modulename>)
*       <filename>
*
* Input:
*   bs           - file to which current input file name is written
*
* Output:
*   No explicit value is returned.
*
* Exceptions:
*   None.
*
* Notes:
*   None.
*
*************************************************************************/

void                    OutFileCur(BSTYPE bs)
{
    APROPFILEPTR        apropFile;      /* Pointer to file property cell */
    AHTEPTR             ahte;           /* Pointer symbol name */
    BSTYPE              bsTmp;          /* Temporary file pointer */
    SBTYPE              fileName;       /* File name buffer */
    SBTYPE              moduleName;     /* Object module name */
    int                 n;              /* String length counter */

#if OSEGEXE
    if (yylineno > 0)
    {
        apropFile = (APROPFILEPTR) FETCHSYM(rhteDeffile,FALSE);
        ahte = GetHte(rhteDeffile);
    }
    else
#endif
    {
        apropFile = (APROPFILEPTR ) FETCHSYM(vrpropFile,FALSE);
        ahte = GetHte(vrpropFile);
    }
    bsTmp = bsErr;
    bsErr = bs;

    // Copy file name

    n = (ahte->cch[0] < sizeof(fileName) - 1) ? ahte->cch[0] : sizeof(fileName) - 1;
    FMEMCPY((char FAR *) fileName, &ahte->cch[1], n);
    fileName[n] = '\0';
    if (yylineno > 0)
        FmtPrint("%s(%d)", fileName, yylineno);
    else if (apropFile->af_rMod)
    {
        // Get object module name

        ahte = (AHTEPTR ) FETCHSYM(apropFile->af_rMod,FALSE);
        while(ahte->attr != ATTRNIL)
            ahte = (AHTEPTR ) FETCHSYM(ahte->rhteNext,FALSE);
        n = (ahte->cch[0] < sizeof(moduleName) - 1) ? ahte->cch[0] : sizeof(moduleName) - 1;
        FMEMCPY((char FAR *) moduleName, &ahte->cch[1], n);
        moduleName[n] = '\0';
        FmtPrint("%s(%s)", fileName, moduleName);

    }
    else
        FmtPrint("%s", fileName);
    bsErr = bsTmp;
}

#pragma check_stack(off)

#if (QCLINK OR OSEGEXE) AND NOT EXE386
typedef int (cdecl FAR * FARFPTYPE)(char FAR *buf);
                                        /* Far function pointer type */
extern FARFPTYPE FAR    *pfQTab;        /* Table of addresses */
#endif

/*** vFmtPrint - print formated message
*
* Purpose:
*   Print on bsErr formated error or warning message.
*   Check for any I/O errors.
*
* Input:
*   fmt          - error message format string
*   pArgList     - pointer to variable number of parameters desrcibing error message
*   bsErr        - error file - global variable
*   bsLst        - listing file - global variable
*
* Output:
*   No explicit value is returned.
*
* Exceptions:
*   I/O errors. If error detected and stdout is an error file then silently
*   exit to system with return code 4 (something must be terribly wrong
*   if stdout is not working). If we are writing to listing file and
*   error was detected then close listing file and notify user.
*
* Notes:
*   This function handles output to QC enviroment.
*
*************************************************************************/

LOCAL void              vFmtPrint(char *fmt, va_list pArgList)
{
#if WIN_3
    char        buf[512];
    vsprintf(buf, fmt, pArgList);
    ErrMsgWin(buf);
    #if DEBUG_WIN2
    OutputDebugString((char far*)"\r\nDebS: ");
    OutputDebugString((char far*)buf);
    #endif
#else

#if (QCLINK) AND NOT EXE386
    SBTYPE              buf;
    if (fZ1)
    {
        // Output via QC call-back

        vsprintf(buf, fmt, pArgList);
        (*pfQTab[0])((char far *) buf);
    }
    else
#endif
    {
        vfprintf(bsErr, fmt, pArgList);
        if (ferror(bsErr))
        {
            if (bsErr == stdout)
            {
#if USE_REAL
                RealMemExit();
#endif
                exit(4);
            }
            else if (bsErr == bsLst)
            {
                fclose(bsLst);
                fLstFileOpen = FALSE;
                bsErr = stdout;
            }
            ExitCode = 4;
            Fatal(ER_spclst);
        }
        fflush(bsErr);
    }
#endif // WIN_3
}

/*** FmtPrint - print formated message
*
* Purpose:
*   Print on bsErr formated error or warning message.
*   Check for any I/O errors.
*
* Input:
*   fmt          - error message format string
*   ...          - variable number of parameters desrcibing error message
*
* Output:
*   No explicit value is returned.
*
* Exceptions:
*   I/O errors.
*
* Notes:
*   The actual job is done by the vFmtPrint.
*
*************************************************************************/

void cdecl              FmtPrint(char *fmt, ...)
{
    va_list             pArgList;


    va_start(pArgList, fmt);
    vFmtPrint(fmt, pArgList);
}


#if OSMSDOS AND NOT WIN_3
/*
 *  PromptStd : Standard prompt routine
 *
 *  Display a warning message and prompt, with optional arguments.
 *  Optionally read a response into a given buffer.  If the given
 *  buffer is null, get a yes/no response with <ENTER> being yes.
 *
 *  Returns:
 *      TRUE if yes or response read.
 *      FALSE if no.
 */
int cdecl               PromptStd (sbNew,msg,msgparm,pmt,pmtparm)
BYTE                    *sbNew;         /* Buffer for response */
MSGTYPE                 msg;            /* Error message */
int                     msgparm;        /* Message parameter */
MSGTYPE                 pmt;            /* Prompt */
int                     pmtparm;        /* Prompt parameter */
{
    register BYTE       *p;
    int                 ch;
    int                 n;

    if(msg)
        OutWarn(msg, msgparm);
    if(!pmt)
        return(TRUE);
    fprintf(stderr,GetMsg(pmt),pmtparm);
    fflush(stderr);                     /* Flush stderr */
#if CPU286
    flskbd();                           /* Flush DOS keyboard buffer */
#endif
    fflush(stdin);                      /* Flush console input */
    if(sbNew != NULL)
    {
                                        /* Read response */
        for(p = &sbNew[1], n = 0;
            (ch = fgetc(stdin)) != '\n' && ch != EOF && n < sizeof(SBTYPE); )
        {
#if CRLF
            if(ch == '\r')
                continue;
#endif
            *p++ = (BYTE) ch;
            n++;
        }
        sbNew[0] = (BYTE) n;
        return(TRUE);
    }
#if CRLF
    if(fgetc(stdin) != '\r')
        return(FALSE);
#endif
    if(fgetc(stdin) != '\n')
        return(FALSE);
    return(TRUE);
}
#endif /* OSMSDOS */

/*
 *  CputcStd : standard console character output routine.
 *      Call fputc to stdout.  Will be called through pfCputc.
 */
void                    CputcStd (ch)
int                     ch;
{
    putc(ch,stdout);
    if (ferror(stdout))
        exit(4);
}

/*
 *  CputsStd : standard console string output routine
 *      Call fputs to stdout.  Will be called through pfCputs.
 */
void                    CputsStd (str)
char                    *str;
{
    fputs(str,stdout);
    if (ferror(stdout))
    {
#if USE_REAL
        RealMemExit();
#endif
        exit(4);
    }
    fflush(stdout);
}

/*** ErrSub - write out nonfatal error message
*
* Purpose:
*   Fromat and write out nonfatal error message. If error message number
*   is equal to zero then we treat it as a prompt.
*
* Input:
*   msg          - error message number
*   fWarn        - TRUE if this warnnig
*   ...          - variable number of parameters for message
*   bsErr        - error file - global variable
*   bsLst        - listing file - global variable
*
* Output:
*   No explicit value is returned.
*
* Exceptions:
*   None.
*
* Notes:
*   None.
*
*************************************************************************/

LOCAL void cdecl NEAR   ErrSub(MSGTYPE msg, WORD fWarn, va_list pArgList)
{

    if (fLstFileOpen && bsErr == bsLst && vgsnLineNosPrev)
    {                                   /* If we've listed line numbers */
        NEWLINE(bsErr);                 /* Newline */
        vgsnLineNosPrev = 0;            /* Reset */
    }
    if (msg)
    {
        /* If there is any message to print */
#if WIN_3
        if(fWarn)
            fSeverity=SEV_WARNING;
        else
            fSeverity=SEV_ERROR;
#endif


#if MSGMOD AND NOT WIN_3
        if (msg >= 1000)
        {
#endif
            /* Error or warning */

            ErrPrefix();
#if MSGMOD
            FmtPrint("%s %c%04d: ",
                    fWarn ? __NMSG_TEXT(N_warning) : __NMSG_TEXT(N_error),
                    (int) chErr, msg);
#else
            FmtPrint("%s: ",fWarn ? "warning" : "error");
#endif
            vFmtPrint(GetMsg(msg), pArgList);
#if NOT WIN_3
#if QCLINK
            if (fZ1)
                FmtPrint("\n");
            else
#endif
                NEWLINE(bsErr);
#else
            FmtPrint("\r\n");
            // for the second part of the message
            fSeverity = SEV_WARNING;
#endif

            if (fDrivePass && !fWarn
#if MSGMOD
               && (msg >= 2005 && msg < 2022) || msg == 1101
#endif
               )
                FmtPrint("%s: %lx %s: %02x\r\n",
                            __NMSG_TEXT(N_pos),ftell(bsInput),
                            __NMSG_TEXT(N_rectyp),rect & 0xff);

#if MSGMOD AND NOT WIN_3
        }
        else
        {
            /* Prompt */
#if QCLINK
            if (fZ1)
                (*pfPrompt)(NULL, msg, (int) pArgList, 0, 0);
            else
            {
#endif
                vFmtPrint(GetMsg(msg), pArgList);
                NEWLINE(bsErr);
#if QCLINK
            }
#endif
        }
#endif
    }
}

/*** OutError - write out nonfatal error message
*
* Purpose:
*   Top level function called when error message has to be displayed.
*   Bumps the error counter and calls ErrSub to do the job.
*
* Input:
*   msg          - error message number
*   ...          - variable number of error parameters
*
* Output:
*   No explicit value is returned. Global error counter cErrors is
*   incremented.
*
* Exceptions:
*   None.
*
* Notes:
*   None.
*
*************************************************************************/

void cdecl              OutError(MSGTYPE msg, ...)
{
    va_list             pArgList;

    va_start(pArgList, msg);
    ++cErrors;                      /* Increment error count */
    ErrSub(msg, FALSE, pArgList);
}

/*** OutWarn - write out warning message
*
* Purpose:
*   Top level function called when warning message has to be displayed.
*   Calls ErrSub to do the job.
*
* Input:
*   msg          - error message number
*   ...          - variable number of error parameters
*
* Output:
*   No explicit value is returned.
*   incremented.
*
* Exceptions:
*   None.
*
* Notes:
*   None.
*
*************************************************************************/

void cdecl              OutWarn (MSGTYPE msg, ...)
{
    va_list             pArgList;
    DEBUGW("\r\nOutWarn entered",0);
    va_start(pArgList, msg);
    ErrSub(msg, TRUE, pArgList);
}

/*** KillRunFile - delete .EXE file
*
* Purpose:
*   Delete the .EXE file created by the linker.
*
* Input:
*   No explicit value is passed.
*   bsRunfile - output file handle - global variable.
*   psbRun    - output file name   - global variable.
*
* Output:
*   No explicit value is returned.
*
* Exceptions:
*   None.
*
* Notes:
*   None.
*
*************************************************************************/

void                    KillRunfile(void)
{
    if (bsRunfile != NULL)
    {
        CloseFile(bsRunfile);
        _unlink(&psbRun[1]);
    }
}

/*** Fatal - write out fatal error message
*
* Purpose:
*   Format and write out fatal error message. Terminate linker.
*
* Input:
*   msg          - error message number
*   ...          - variable number of message parameters
*   bsLst        - listing file - global variable
*
* Output:
*   No explicit value is returned. Linker terminates.
*
* Exceptions:
*   None.
*
* Notes:
*   None.
*
*************************************************************************/

void cdecl              Fatal (MSGTYPE msg, ...)
{
    static              WORD cInvoked =0;
    va_list             pArgList;

    va_start(pArgList, msg);            /* Get start of argument list */

    if (++cInvoked > 1) // Fatal during Fatal
    {
#if USE_REAL
        RealMemExit();
#endif
        if (ExitCode)
                EXIT(ExitCode);
        else
                EXIT(2);                        /* Program error */
    }

    /* If msg is nonzero then print a message */

    if (msg)
    {
        if (fLstFileOpen)
            fflush(bsLst);
        ErrPrefix();
#if MSGMOD
        FmtPrint("%s %c%04d: ", __NMSG_TEXT(N_fatal), chErr, msg);
#else
        FmtPrint("fatal error: ");
#endif
        vFmtPrint(GetMsg(msg), pArgList);
#if WIN_3
        fSeverity = SEV_ERROR;          // Send as error to QCwin
        FmtPrint("\r\n");
#else
        NEWLINE(stderr);
#endif
        if(fDrivePass && ftell(bsInput)
#if MSGMOD
               && msg >= 2005 && msg < 2022 || msg == 1101
#endif
               )
            FmtPrint("%s: %lx %s: %02x\r\n",
                    __NMSG_TEXT(N_pos),ftell(bsInput),
                    __NMSG_TEXT(N_rectyp),rect & 0xff);
    }
    KillRunfile();
    if (fLstFileOpen) fclose(bsLst);
#if OWNSTDIO
    FlsStdio();
#endif

    // If someone else assigned the exit code, use it.  Otherwise,
    // assume program error.

    if (ExitCode)
        EXIT(ExitCode);
    else
        EXIT(2);                        /* Program error */
}

/*** CtrlC - display Ctrl-C error message and die
*
* Purpose:
*   Do minimal work required to display error message and die.
*
* Input:
*   No explicit value is passed.
*
* Output:
*   No explicit value is returned.
*
* Exceptions:
*   None.
*
* Notes:
*   This function doesn't return.
*
*************************************************************************/

void                    CtrlC(void)
{
#if USE_REAL
        RealMemExit();
#endif
#ifdef OS2
    if (_osmode == OS2_MODE)
        fputs("\r\n", stdout);
#endif

#if DOSEXTENDER AND NOT WIN_NT
    if (!_fDosExt)
    {
#endif
        if (fLstFileOpen)
            fflush(bsLst);
        ErrPrefix();
        FmtPrint("%s %c%04d: %s", __NMSG_TEXT(N_fatal), chErr, ER_intrpt, GetMsg(ER_intrpt));
        KillRunfile();
        if (fLstFileOpen)
            fclose(bsLst);
#if OWNSTDIO
        FlsStdio();
#endif
        EXIT(4);
#if DOSEXTENDER AND NOT WIN_NT
    }
    else
        _exit(4);
#endif
}
