/*++

Copyright (c) 1988-1999  Microsoft Corporation

Module Name:

    cenv.c

Abstract:

    Environment variable support

--*/

#include "cmd.h"

struct envdata CmdEnv ;    // Holds info to manipulate Cmd's environment
struct envdata * penvOrig; // original environment setup used with eStart

extern TCHAR PathStr[], PromptStr[] ;
extern TCHAR AppendStr[]; /* @@ */

extern CHAR InternalError[] ;
extern TCHAR Fmt16[], Fmt17[], EnvErr[] ;
extern TCHAR SetArithStr[] ;
extern TCHAR SetPromptStr[] ;
extern unsigned flgwd ;
extern TCHAR CdStr[] ;
extern TCHAR DatStr[] ;
extern TCHAR TimStr[] ;
extern TCHAR ErrStr[] ;
extern TCHAR CmdExtVerStr[] ;

extern unsigned LastRetCode;
extern BOOL CtrlCSeen;
extern UINT CurrentCP;
extern BOOLEAN PromptValid;

extern int  glBatType;     // to distinguish OS/2 vs DOS errorlevel behavior depending on a script file name

int SetArithWork(TCHAR *tas);


unsigned
SetLastRetCodeIfError(
    unsigned RetCode
    )
{
    if (RetCode != 0) {
        LastRetCode = RetCode;
    }

    return RetCode;
}

/***    ePath - Begin the execution of a Path Command
 *
 *  Purpose:
 *      If the command has no argument display the current value of the PATH
 *      environment variable.  Otherwise, change the value of the Path
 *      environment variable to the argument.
 *
 *  int ePath(struct cmdnode *n)
 *
 *  Args:
 *      n - the parse tree node containing the path command
 *
 *  Returns:
 *      If changing the PATH variable, whatever SetEnvVar() returns.
 *      SUCCESS, otherwise.
 *
 */

int ePath(n)
struct cmdnode *n ;
{
    if (glBatType != CMD_TYPE)  {
        //  if set command is executed from .bat file OR entered at command prompt
        return( SetLastRetCodeIfError(PathWork( n, 1 )));
    }
    else {
        return( LastRetCode = PathWork( n, 1 ) );
    }

}

/***    eAppend - Entry point for Append routine
 *
 *  Purpose:
 *      to call Append and pass it a pointer to the command line
 *      arguments
 *
 *  Args:
 *      a pointer to the command node structure
 *
 */

int eAppend(n)
struct cmdnode *n ;
{

    if (glBatType != CMD_TYPE)  {
        //  if set command is executed from .bat file OR entered at command prompt
        return( SetLastRetCodeIfError(PathWork( n, 0 )));
    }
    else {
        return( LastRetCode = PathWork( n, 0 ) );
    }

}

int PathWork(n, flag)
struct cmdnode *n ;
int flag;   /* 0 = AppendStr, 1 = PathStr */
{
        TCHAR *tas ;    /* Tokenized argument string    */
        TCHAR c ;

/*  M014 - If the only argument is a single ";", then we have to set
 *  a NULL path.
 */
        if ( n->argptr ) {
            c = *(EatWS(n->argptr, NULL)) ;
        } else {
            c = NULLC;
        }

        if ((!c || c == NLN) &&         /* If args are all whitespace      */
            mystrchr(n->argptr, TEXT(';'))) {

                return(SetEnvVar(flag ? PathStr : AppendStr, TEXT(""), &CmdEnv)) ;

        } else {

                tas = TokStr(n->argptr, TEXT(";"), TS_WSPACE | TS_NWSPACE) ;

                if (*tas)
                  {
                   return(SetEnvVar(flag ? PathStr : AppendStr, tas, &CmdEnv)) ;
                  }

               cmd_printf(Fmt16, flag ? PathStr : AppendStr,
                          GetEnvVar(flag ? PathStr : AppendStr), &CmdEnv) ;
        }
        return(SUCCESS) ;
}




/***    ePrompt - begin the execution of the Prompt command
 *
 *  Purpose:
 *      To modifiy the Prompt environment variable.
 *
 *  int ePrompt(struct cmdnode *n)
 *
 *  Args:
 *      n - the parse tree node containing the prompt command
 *
 *  Returns:
 *      Whatever SetEnvVar() returns.
 *
 */

int ePrompt(n)
struct cmdnode *n ;
{
    if (glBatType != CMD_TYPE)  {
        //  if set command is executed from .bat file OR entered at command prompt
        return(SetLastRetCodeIfError(SetEnvVar(PromptStr, TokStr(n->argptr, NULL, TS_WSPACE), &CmdEnv))) ;
    }
    else {
        return(LastRetCode = SetEnvVar(PromptStr, TokStr(n->argptr, NULL, TS_WSPACE), &CmdEnv) ) ;
    }
}




/***    eSet - execute a Set command
 *
 *  Purpose:
 *      To set/modify an environment or to display the current environment
 *      contents.
 *
 *  int eSet(struct cmdnode *n)
 *
 *  Args:
 *      n - the parse tree node containing the set command
 *
 *  Returns:
 *      If setting and the command is syntactically correct, whatever SetEnvVar()
 *      returns.  Otherwise, FAILURE.
 *
 *      If displaying, SUCCESS is always returned.
 *
 */

int eSet(n)
struct cmdnode *n ;
{
    if (glBatType != CMD_TYPE)  {
        //  if set command is executed from .bat file OR entered at command prompt
        return( SetLastRetCodeIfError(SetWork( n )));
    }
    else {
        return( LastRetCode = SetWork( n ) );
    }
}

/***    SetPromptUser - set environment variable to value entered by user.
 *
 *  Purpose:
 *      Set environment variable to value entered by user.
 *
 *  int SetPromptUser(TCHAR *tas)
 *
 *  Args:
 *      tas - pointer to null terminated string of the form:
 *
 *          VARNAME=promptString
 *
 *  Returns:
 *      If valid expression, return SUCCESS otherwise FAILURE.
 *
 */

int SetPromptUser(TCHAR *tas)
{
    TCHAR *wptr;
    TCHAR *tptr;
    ULONG    dwOutputModeOld;
    ULONG    dwOutputModeCur;
    ULONG    dwInputModeOld;
    ULONG    dwInputModeCur;
    BOOLEAN  fOutputModeSet = FALSE;
    BOOLEAN  fInputModeSet = FALSE;
    HANDLE   hndStdOut = NULL;
    HANDLE   hndStdIn = NULL;
    DWORD    cch;
    TCHAR    szValueBuffer[ 1024 ];

    //
    // Find first non-blank argument.
    //
    if (tas != NULL)
    while (*tas && *tas <= SPACE)
        tas += 1;


    // If no input, declare an error
    //
    if (!tas || !*tas) {
        PutStdErr(MSG_BAD_SYNTAX, NOARGS);
        return(FAILURE) ;
    }

    //
    // See if first argument is quoted.  If so, strip off
    // leading quote, spaces and trailing quote.
    //
    if (*tas == QUOTE) {
        tas += 1;
        while (*tas && *tas <= SPACE)
            tas += 1;
        tptr = _tcsrchr(tas, QUOTE);
        if (tptr)
            *tptr = NULLC;
    }

    //
    // Find the equal sign in the argument.
    //
    wptr = _tcschr(tas, EQ);

    //
    // If no equal sign, error.
    //
    if (!wptr) {
        PutStdErr(MSG_BAD_SYNTAX, NOARGS);
        return(FAILURE) ;
    }

    //
    // Found the equal sign, so left of equal sign is variable name
    // and right of equal sign is prompt string.  Dont allow user to set
    // a variable name that begins with an equal sign, since those
    // are reserved for drive current directories.
    //
    *wptr++ = NULLC;

    //
    // See if second argument is quoted.  If so, strip off
    // leading quote, spaces and trailing quote.
    //
    if (*wptr == QUOTE) {
        wptr += 1;
        while (*wptr && *wptr <= SPACE)
            wptr += 1;
        tptr = _tcsrchr(wptr, QUOTE);
        if (tptr)
            *tptr = NULLC;
    }

    if (*wptr == EQ) {
        PutStdErr(MSG_BAD_SYNTAX, NOARGS);
        return(FAILURE) ;
    }

    hndStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (GetConsoleMode( hndStdOut, &dwOutputModeOld) ) {

        // make sure CRLF is processed correctly

        dwOutputModeCur = dwOutputModeOld | ENABLE_PROCESSED_OUTPUT;
        fOutputModeSet = TRUE;
        SetConsoleMode(hndStdOut,dwOutputModeCur);
        GetLastError();
    }

    hndStdIn = GetStdHandle(STD_INPUT_HANDLE);
    if (GetConsoleMode( hndStdIn, &dwInputModeOld) ) {

        // make sure input is processed correctly

        dwInputModeCur = dwInputModeOld | ENABLE_LINE_INPUT |
                         ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT;
        fInputModeSet = TRUE;
        SetConsoleMode(hndStdIn,dwInputModeCur);
        GetLastError();
    }

    //
    // Loop till the user enters a value for the variable.
    //

    while (TRUE) {
        PutStdOut(MSG_LITERAL_TEXT, ONEARG, wptr );
        szValueBuffer[0] = NULLC;
        if (ReadBufFromInput( GetStdHandle(STD_INPUT_HANDLE),
                              szValueBuffer,
                              sizeof(szValueBuffer)/sizeof(TCHAR),
                              &cch
                            ) != 0 &&
                            cch != 0
           ) {
            //
            // Strip off any trailing CRLF
            //
            while (cch > 0 && szValueBuffer[cch-1] < SPACE)
                cch -= 1;

            break;
        }
        else {
            cch = 0;
            break;
        }

        if (!FileIsDevice(STDIN) || !(flgwd & 1))
            cmd_printf(CrLf) ;
    }

    if (fOutputModeSet) {
        SetConsoleMode( hndStdOut, dwOutputModeOld );
    }
    if (fInputModeSet) {
        SetConsoleMode( hndStdIn, dwInputModeOld );
    }

    if (cch) {
        szValueBuffer[cch] = NULLC;
        return(SetEnvVar(tas, szValueBuffer, &CmdEnv)) ;
    } else {
        return(FAILURE);
    }
}


int SetWork(n)
struct cmdnode *n ;
{
        TCHAR *tas ;    /* Tokenized argument string    */
        TCHAR *wptr ;   /* Work pointer                 */
        int i ;                 /* Work variable                */

        //
        // If extensions are enabled, things are different
        //
        if (fEnableExtensions) {
            tas = n->argptr;
            //
            // Find first non-blank argument.
            //
            if (tas != NULL)
            while (*tas && *tas <= SPACE)
                tas += 1;

            //
            // No arguments, same as old behavior.  Display current
            // set of environment variables.
            //
            if (!tas || !*tas)
                return(DisplayEnv()) ;

            //
            // See if /A switch given.  If so, let arithmetic
            // expression evaluator do the work.
            //
            if (!_tcsnicmp(tas, SetArithStr, 2))
                return SetArithWork(tas+2);

            //
            // See if /P switch given.  If so, prompt user for value
            //
            if (!_tcsnicmp(tas, SetPromptStr, 2))
                return SetPromptUser(tas+2);

            //
            // See if first argument is quoted.  If so, strip off
            // leading quote, spaces and trailing quote.
            //
            if (*tas == QUOTE) {
                tas += 1;
                while (*tas && *tas <= SPACE)
                    tas += 1;
                wptr = _tcsrchr(tas, QUOTE);
                if (wptr)
                    *wptr = NULLC;
            }

            //
            // Dont allow user to set a variable name that begins with
            // an equal sign, since those are reserved for drive current
            // directories.  This check will also detect missing variable
            // name, e.g.
            //
            //      set %LOG%=c:\tmp\log.txt
            //
            // if LOG is not defined is an invalid statement.
            //
            if (*tas == EQ) {
                PutStdErr(MSG_BAD_SYNTAX, NOARGS);
                return(FAILURE) ;
            }

            //
            // Find the equal sign in the argument.
            //
            wptr = _tcschr(tas, EQ);

            //
            // If no equal sign, then assume argument is variable name
            // and user wants to see its value.  Display it.
            //
            if (!wptr)
                return DisplayEnvVariable(tas);

            //
            // Found the equal sign, so left of equal sign is variable name
            // and right of equal sign is value.
            //
            *wptr++ = NULLC;
            return(SetEnvVar(tas, wptr, &CmdEnv)) ;
        }

        tas = TokStr(n->argptr, ONEQSTR, TS_WSPACE|TS_SDTOKENS) ;
        if (!*tas)
                return(DisplayEnv()) ;

        else {
                for (wptr = tas, i = 0 ; *wptr ; wptr += mystrlen(wptr)+1, i++)
                        ;
                /* If too many parameters were given, the second parameter */
                /* wasn't an equal sign, or they didn't specify a string   */
                /* return an error message.                                */
                if ( i > 3 || *(wptr = tas+mystrlen(tas)+1) != EQ ||
                    !mystrlen(mystrcpy(tas, StripQuotes(tas))) ) {
/* M013 */              PutStdErr(MSG_BAD_SYNTAX, NOARGS);
                        return(FAILURE) ;

                } else {
                        return(SetEnvVar(tas, wptr+2, &CmdEnv)) ;
                }
        }
}




/***    DisplayEnvVariable -  display a specific variable from the environment
 *
 *  Purpose:
 *      To display a specific variable from the current environment.
 *
 *  int DisplayEnvVariable( tas )
 *
 *  Returns:
 *      SUCCESS if all goes well
 *      FAILURE if it runs out of memory or cannot lock the env. segment
 */

int DisplayEnvVariable(tas)
TCHAR *tas;
{
    TCHAR *envptr ;
    TCHAR *vstr ;
    unsigned size ;
    UINT PrefixLength;
    int rc;

    //
    //  Get environment.  If there's none, we're done.
    //

    envptr = GetEnvironmentStrings();
    if (envptr == (TCHAR *)NULL) {
        fprintf ( stderr, InternalError , "Null environment" ) ;
        return ( FAILURE ) ;
    }

    //
    //  Isolate the prefix to match against.
    //

    tas = EatWS(tas, NULL);
    if ((vstr = mystrrchr(tas, SPACE)) != NULL) {
        *vstr = NULLC;
    }

    PrefixLength = mystrlen(tas);

    //
    //  Walk through the environment looking for prefixes that match.
    //

    rc = FAILURE;
    while ((size = mystrlen(envptr)) > 0) {

        //
        //  Stop soon if we see ^C
        //

        if (CtrlCSeen) {
            return(FAILURE);
        }

        //
        //  If the prefix is long enough, then terminate the string and
        //  look for a prefix match.  If we match, restore the string
        //  and display it
        //

        if (size >= PrefixLength) {
            TCHAR SavedChar = envptr[PrefixLength];
            envptr[PrefixLength] = NULLC;
            if (!lstrcmpi( envptr, tas )) {
                envptr[PrefixLength] = SavedChar;
                cmd_printf(Fmt17, envptr );
                rc = SUCCESS;
            } else {
                envptr[PrefixLength] = SavedChar;
            }

        }

        //
        //  Advance to the next string
        //

        envptr += size+1 ;
    }

    if (rc != SUCCESS) {
        PutStdErr(MSG_ENV_VAR_NOT_FOUND, ONEARG, tas);
    }

    return(rc) ;
}


/***    MyGetEnvVar - get a pointer to the value of an environment variable
 *
 *  Purpose:
 *      Return a pointer to the value of the specified environment variable.
 *
 *      If the variable is not found, return NULL.
 *
 *  TCHAR *MyGetEnvVar(TCHAR *varname)
 *
 *  Args:
 *      varname - the name of the variable to search for
 *
 *  Returns:
 *      See above.
 *
 *  Side Effects:
 *      Returned value points to within the environment block itself, so is
 *      not valid after a set environment variable operations is perform.
 */


const TCHAR *
MyGetEnvVarPtr(TCHAR *varname)
{
        TCHAR *envptr ; /* Ptr to environment                      */
        TCHAR *vstr ;
        unsigned size ;         /* Length of current env string             */
        unsigned n ;

        if (varname == NULL) {
            return ( NULL ) ;
        }

        envptr = GetEnvironmentStrings();
        if (envptr == (TCHAR *)NULL) {
            return ( NULL ) ;
        }

        varname = EatWS(varname, NULL);
        if ((vstr = mystrrchr(varname, SPACE)) != NULL)
            *vstr = NULLC;

        n = mystrlen(varname);
        while ((size = mystrlen(envptr)) > 0) {                 /* M015    */
                if (CtrlCSeen) {
                    return(NULL);
                }
                if (!_tcsnicmp(varname, envptr, n) && envptr[n] == TEXT( '=' )) {
                    return envptr+n+1;
                }

                envptr += size+1 ;
        }

        return(NULL);
}


/***    DisplayEnv -  display the environment
 *
 *  Purpose:
 *      To display the current contents of the environment.
 *
 *  int DisplayEnv()
 *
 *  Returns:
 *      SUCCESS if all goes well
 *      FAILURE if it runs out of memory or cannot lock the env. segment
 */

int DisplayEnv()
{
    TCHAR *envptr ; /* Ptr to environment                      */
    unsigned size ;         /* Length of current env string             */

    envptr = GetEnvironmentStrings();
    if (envptr == (TCHAR *)NULL) {
        fprintf ( stderr, InternalError , "Null environment" ) ;
        return( FAILURE ) ;
    }
    
    while ((size = mystrlen(envptr)) > 0) {                 /* M015    */
        if (CtrlCSeen) {
            return(FAILURE);
        }
#if !DBG
        // Dont show current directory variables in retail product
        if (*envptr != EQ)
#endif // DBG
            cmd_printf(Fmt17, envptr) ;   /* M005 */
        envptr += size+1 ;
    }

    return(SUCCESS) ;
}




/***    SetEnvVar - controls adding/changing an environment variable
 *
 *  Purpose:
 *      Add/replace an environment variable.  Grow it if necessary.
 *
 *  int SetEnvVar(TCHAR *varname, TCHAR *varvalue, struct envdata *env)
 *
 *  Args:
 *      varname - name of the variable being added/replaced
 *      varvalue - value of the variable being added/replaced
 *      env - environment info structure being used
 *
 *  Returns:
 *      SUCCESS if the variable could be added/replaced.
 *      FAILURE otherwise.
 *
 */

int SetEnvVar(varname, varvalue, env)
TCHAR *varname ;
TCHAR *varvalue ;
struct envdata *env ;
{
    int retvalue;

    PromptValid = FALSE;        // Force it to be recalculated

    DBG_UNREFERENCED_PARAMETER( env );
    if (!_tcslen(varvalue)) {
        varvalue = NULL; // null to remove from env
    }
    retvalue = SetEnvironmentVariable(varname, varvalue);
    if (CmdEnv.handle != GetEnvironmentStrings()) {
        MEMORY_BASIC_INFORMATION MemoryInfo;

        CmdEnv.handle = GetEnvironmentStrings();
        CmdEnv.cursize = GetEnvCb(CmdEnv.handle);
        if (VirtualQuery( CmdEnv.handle, &MemoryInfo, sizeof( MemoryInfo ) ) == sizeof( MemoryInfo )) {
            CmdEnv.maxsize = (UINT)MemoryInfo.RegionSize;
            }
        else {
            CmdEnv.maxsize = CmdEnv.cursize;
            }
        }
    else {
        CmdEnv.cursize = GetEnvCb(CmdEnv.handle);
        }

    return !retvalue;
}

/***    GetEnvVar - get the value of an environment variable
 *
 *  Purpose:
 *      Return a string containing the value of the specified environment
 *      variable. The string value has been placed into a static buffer
 *      that is valid until the next GetEnvVar call.
 *
 *      If the variable is not found, return NULL.
 *
 *  TCHAR *GetEnvVar(TCHAR *varname)
 *
 *  Args:
 *      varname - the name of the variable to search for
 *
 *  Returns:
 *      See above.
 *
 */


TCHAR GetEnvVarBuffer[LBUFLEN];

PTCHAR GetEnvVar(varname)
PTCHAR varname ;
{
    GetEnvVarBuffer[0] = TEXT( '\0' );

    if (GetEnvironmentVariable(varname, GetEnvVarBuffer, sizeof(GetEnvVarBuffer) / sizeof(TCHAR))) {
        return(GetEnvVarBuffer);
    }
    else
    if (fEnableExtensions) {
        if (!_tcsicmp(varname, CdStr)) {
            GetDir(GetEnvVarBuffer, GD_DEFAULT) ;
            return GetEnvVarBuffer;
        }
        else
        if (!_tcsicmp(varname, ErrStr)) {
            _stprintf( GetEnvVarBuffer, TEXT("%d"), LastRetCode );
            return GetEnvVarBuffer;
        }
        else
        if (!_tcsicmp(varname, CmdExtVerStr)) {
            _stprintf( GetEnvVarBuffer, TEXT("%d"), CMDEXTVERSION );
            return GetEnvVarBuffer;
        }
        else
        if (!_tcsicmp(varname, TEXT("CMDCMDLINE"))) {
            return GetCommandLine();
        }
        else
        if (!_tcsicmp(varname, DatStr)) {
            GetEnvVarBuffer[ PrintDate(NULL, PD_DATE, GetEnvVarBuffer, LBUFLEN) ] = NULLC;
            return GetEnvVarBuffer;
        }
        if( !_tcsicmp(varname, TimStr)) {
            GetEnvVarBuffer[ PrintTime(NULL, PT_TIME, GetEnvVarBuffer, LBUFLEN) ] = NULLC;
            return GetEnvVarBuffer;
        }
        if( !_tcsicmp(varname, TEXT("RANDOM"))) {
            _stprintf( GetEnvVarBuffer, TEXT("%d"), rand() );
            return GetEnvVarBuffer;
        }
    }
    return(NULL);
}


/***    CopyEnv -  make a copy of the current environment
 *
 *  Purpose:
 *      Make a copy of CmdEnv and put the new handle into the newly
 *      created envdata structure.  This routine is only called by
 *      eSetlocal and init.
 *
 *  struct envdata *CopyEnv()
 *
 *  Returns:
 *      A pointer to the environment information structure.
 *      Returns NULL if unable to allocate enough memory
 *
 *  Notes:
 *    - M001 - This function was disabled, now reenabled.
 *    - The current environment is copied as a snapshot of how it looked
 *      before SETLOCAL was executed.
 *    - M008 - This function's copy code was moved to new function MoveEnv.
 *
 */

struct envdata *CopyEnv()
{
    struct envdata *cce ;   /* New env info structure          */

    cce = (struct envdata *) HeapAlloc( GetProcessHeap( ), HEAP_ZERO_MEMORY, sizeof( *cce ));
    if (cce == NULL) {
        return NULL;
    }

    cce->cursize = CmdEnv.cursize ;
    cce->maxsize = CmdEnv.maxsize ;
    cce->handle  = VirtualAlloc( NULL,
                                 cce->maxsize,
                                 MEM_COMMIT,
                                 PAGE_READWRITE
                               );
    if (cce->handle == NULL) {
        HeapFree( GetProcessHeap( ), 0, cce );
        PutStdErr( MSG_OUT_OF_ENVIRON_SPACE, NOARGS );
        return NULL;
    }

    if (!MoveEnv( cce->handle, CmdEnv.handle, GetEnvCb( CmdEnv.handle ))) {
        VirtualFree( cce->handle, 0, MEM_RELEASE );
        HeapFree( GetProcessHeap( ), 0, cce );
        return NULL;
    }

    return cce;
}


/***    ResetEnv - restore the environment
 *
 *  Purpose:
 *      Restore the environment to the way it was before the execution of
 *      the SETLOCAL command.  This function only called by eEndlocal.
 *
 *  ResetEnv(struct envdata *env)
 *
 *  Args:
 *      env - structure containing handle, size and max dimensions of an
 *            environment.
 *
 *  Notes:
 *    - M001 - This function was disabled, but has been reenabled.
 *    - M001 - This function used to test for OLD/NEW style batch files
 *             and delete the copy or the original environment as
 *             appropriate.  It now always deletes the original.
 *    - M014 - Note that the modified local environment will never be
 *             shrunk, so we can assume it will hold the old one.
 *
 */

void ResetEnv( struct envdata *env)
{
    ULONG cursize;

    cursize = GetEnvCb( env->handle );
    if (MoveEnv( CmdEnv.handle, env->handle, cursize )) {
        CmdEnv.cursize = cursize ;
    }

    VirtualFree( env->handle, 0, MEM_RELEASE );
    HeapFree( GetProcessHeap( ), 0, env );
}




/***    MoveEnv - Move the contents of the environment (M008 - New function)
 *
 *  Purpose:
 *      Used by CopyEnv, this function moves the existing
 *      environment contents to the new location.
 *
 *  MoveEnv(unsigned thndl, unsigned shndl, unsigned cnt)
 *
 *  Args:
 *      thndl - Handle of target environment
 *      shndl - Handle of source environment
 *      cnt   - byte count to move
 *
 *  Returns:
 *      TRUE if no errors
 *      FALSE otherwise
 *
 */

MoveEnv(tenvptr, senvptr, cnt)
TCHAR *senvptr ;                /* Ptr into source env seg         */
TCHAR *tenvptr ;                /* Ptr into target env seg         */
ULONG    cnt ;
{
        if ((tenvptr == NULL) ||
            (senvptr == NULL)) {
                fprintf(stderr, InternalError, "Null environment") ;
                return(FALSE) ;
        }
        memcpy(tenvptr, senvptr, cnt) ;         /* M015    */
        return(TRUE) ;
}


ULONG
GetEnvCb( TCHAR *penv ) {

        ULONG cb = 0;

        if (penv == NULL) {
            return (0);
        }

        while ( (*penv) || (*(penv+1))) {
                cb++;
                penv++;
        }
        return (cb+2) * sizeof(TCHAR);

}

//
//      expr -> assign [, assign]*                          ,
//
//      assign -> orlogexpr |                               
//                VAR ASSIGNOP assign                       <op>=
//
//      orlogexpr -> xorlogexpr [| xorlogexpr]*             |
//
//      xorlogexpr ->  andlogexpr [^ andlogexpr]*           ^
//
//      andlogexpr ->  shiftexpr [& shiftexpr]*             &
//
//      shiftexpr -> addexpr [SHIFTOP addexpr]*             <<, >>
//
//      addexpr -> multexpr [ADDOP multexpr]*               +, -
//
//      multexpr -> unaryexpr [MULOP unaryexpr]*            *, /, %
//
//      unaryexpr -> ( expr ) |                             ()
//                   UNARYOP unaryexpr                      +, -, !, ~
//

TCHAR szOps[] = TEXT("<>+-*/%()|^&=,");
TCHAR szUnaryOps[] = TEXT("+-~!");

typedef struct {
    PTCHAR Token;
    LONG Value;
    DWORD Error;
} PARSESTATE, *PPARSESTATE;

VOID
APerformUnaryOperation( PPARSESTATE State, TCHAR Op, LONG Value )
{
    switch (Op) {
    case TEXT( '+' ):
        State->Value = Value;
        break;
    case TEXT( '-' ):
        State->Value = -Value;
        break;
    case TEXT( '~' ):
        State->Value = ~Value;
        break;
    case TEXT( '!' ):
        State->Value = !Value;
        break;
    default:
        printf( "APerformUnaryOperation: '%c'\n", Op);
        break;
    }
}

VOID
APerformArithmeticOperation( PPARSESTATE State, TCHAR Op, LONG Left, LONG Right )
{
    switch (Op) {
    case TEXT( '<' ):
        State->Value = (Right >= 8 * sizeof( Left << Right))
                            ? 0
                            : (Left << Right);
        break;
    case TEXT( '>' ):
        State->Value = (Right >= 8 * sizeof( Left >> Right ))
                        ? (Left < 0 ? -1 : 0)
                        : (Left >> Right);
        break;
    case TEXT( '+' ):
        State->Value = Left + Right;
        break;
    case TEXT( '-' ):
        State->Value = Left - Right;
        break;
    case TEXT( '*' ):
        State->Value = Left * Right;
        break;
    case TEXT( '/' ):
        if (Right == 0) {
            State->Error = MSG_SET_A_DIVIDE_BY_ZERO;
        } else {
            State->Value = Left / Right;
        }
        break;
    case TEXT( '%' ):
        if (Right == 0) {
            State->Error = MSG_SET_A_DIVIDE_BY_ZERO;
        } else {
            State->Value = Left % Right;
        }
        break;
    case TEXT( '|' ):
        State->Value = Left | Right;
        break;
    case TEXT( '^' ):
        State->Value = Left ^ Right;
        break;
    case TEXT( '&' ):
        State->Value = Left & Right;
        break;
    case TEXT( '=' ):
        State->Value = Right;
        break;
    default:
        printf( "APerformArithmeticOperation: '%c'\n", Op);
    }
}


//
//  Return the numeric value of an environment variable (or 0)
//

LONG
AGetValue( PTCHAR Start, PTCHAR End )
{
    TCHAR c = *End;
    const TCHAR *Value;
    PTCHAR Dummy;
    
    *End = NULLC;

    Value = MyGetEnvVarPtr( Start );
    *End = c;

    if (Value == NULL) {
        return 0;
    }

    return _tcstol( Value, &Dummy, 0);
}

DWORD
ASetValue( PTCHAR Start, PTCHAR End, LONG Value )
{
    TCHAR Result[32];
    TCHAR c = *End;
    DWORD Return = SUCCESS;

    *End = NULLC;

    _sntprintf( Result, 32, TEXT("%d"), Value ) ;
    
    if (SetEnvVar( Start, Result, &CmdEnv ) != SUCCESS) {
        Return = GetLastError();
    }

    *End = c;
    return Return;
}


//
//  Forward decls
//
PARSESTATE AParseAddExpr( PARSESTATE State );
PARSESTATE AParseAndLogExpr( PARSESTATE State );
PARSESTATE AParseAssign( PARSESTATE State );
PARSESTATE AParseExpr( PARSESTATE State );
PARSESTATE AParseMultExpr( PARSESTATE State );
PARSESTATE AParseOrLogExpr( PARSESTATE State );
PARSESTATE AParseShiftExpr( PARSESTATE State );
PARSESTATE AParseUnaryExpr( PARSESTATE State );
PARSESTATE AParseXorLogExpr( PARSESTATE State );

//
//  Skip whitespace and return next character
//

BOOL ASkipWhiteSpace( PPARSESTATE State )
{
    while (*State->Token != NULLC && *State->Token <= SPACE) {
        State->Token++;
    }

    return *State->Token != NULLC;
}

TCHAR ANextChar( PPARSESTATE State )
{
    ASkipWhiteSpace( State );
    return *State->Token;
}

BOOL AParseVariable( PPARSESTATE State, PTCHAR *FirstChar, PTCHAR *EndOfName )
{
    TCHAR c = ANextChar( State );

    //
    //  Next char is a digit or operator, can't be a variable
    //
    
    if (c == NULLC
        || _istdigit( c )
        || _tcschr( szOps, c ) != NULL
        || _tcschr( szUnaryOps, c ) != NULL) {
        
        return FALSE;
    
    }

    *FirstChar = State->Token;

    //
    //  find end of variable
    //

    while (*State->Token &&
           *State->Token > SPACE &&
           !_tcschr( szUnaryOps, *State->Token ) &&
           !_tcschr( szOps, *State->Token ) ) {
        State->Token += 1;
    }

    *EndOfName = State->Token;
    return TRUE;
}

//      expr -> assign [, assign]*
PARSESTATE AParseExpr( PARSESTATE State )
{
    State = AParseAssign( State );

    while (State.Error == SUCCESS) {

        if (ANextChar( &State ) != TEXT( ',' )) {
            break;
        }
        State.Token++;

        State = AParseAssign( State );
    
    }
    
    return State;
}

//      assign -> VAR ASSIGNOP assign |                               
//                orlogexpr  
PARSESTATE AParseAssign( PARSESTATE State )
{
    TCHAR c = ANextChar( &State );
    PARSESTATE SavedState;

    SavedState = State;

    if (c == NULLC) {
        State.Error = MSG_SET_A_MISSING_OPERAND;
        return State;
    }
    
    //
    //  See if we have VAR ASSIGNOP
    //
    
    do {
        PTCHAR FirstChar;
        PTCHAR EndOfName;
        TCHAR OpChar;
        LONG OldValue;

        //
        //  Parse off variable
        //
        
        if (!AParseVariable( &State, &FirstChar, &EndOfName )) {
            break;
        }
        
        //
        //  Look for <op>=
        //

        OpChar = ANextChar( &State );

        if (OpChar == NULLC) {
            break;
        }

        if (OpChar != TEXT( '=' )) {
            if (_tcschr( szOps, OpChar ) == NULL) {
                break;
            }
            State.Token++;
            
            if (OpChar == TEXT( '<' ) || OpChar == TEXT( '>')) {
                if (ANextChar( &State ) != OpChar) {
                    break;
                }
                State.Token++;
            }
            
        }

        if (ANextChar( &State ) != TEXT( '=' )) {
            break;
        }
        State.Token++;
        
        //
        //  OpChar is the sort of operation to apply before assignment
        //  State has been advance to, hopefully, another assign.  Parse it
        //  and see where we get
        //

        State = AParseAssign( State );
        if (State.Error != SUCCESS) {
            return State;
        }

        OldValue = AGetValue( FirstChar, EndOfName );
        
        //
        //  Perform the operation and the assignment 
        //

        APerformArithmeticOperation( &State, OpChar, OldValue, State.Value );
        if (State.Error != SUCCESS) {
            return State;
        }

        State.Error = ASetValue( FirstChar, EndOfName, State.Value );

        return State;
    } while ( FALSE );

    //
    //  Must be orlogexpr.  Go back and parse over
    //

    return AParseOrLogExpr( SavedState );
}

//      orlogexpr -> xorlogexpr [| xorlogexpr]*             |
PARSESTATE
AParseOrLogExpr( PARSESTATE State )
{
    State = AParseXorLogExpr( State );
    while (State.Error == SUCCESS) {
        TCHAR Op = ANextChar( &State );
        LONG Value = State.Value;

        if (Op != TEXT( '|' )) {
            break;
        }
        State.Token++;
        
        State = AParseXorLogExpr( State );
        APerformArithmeticOperation( &State, Op, Value, State.Value );
    }
    return State;
}

//      xorlogexpr ->  andlogexpr [^ andlogexpr]*           ^
PARSESTATE
AParseXorLogExpr( PARSESTATE State )
{
    State = AParseAndLogExpr( State );
    while (State.Error == SUCCESS) {
        TCHAR Op = ANextChar( &State );
        LONG Value = State.Value;

        if (Op != TEXT( '^' )) {
            break;
        }
        State.Token++;
        
        State = AParseAndLogExpr( State );
        APerformArithmeticOperation( &State, Op, Value, State.Value );
    }
    return State;
}

//      andlogexpr ->  shiftexpr [& shiftexpr]*             &
PARSESTATE
AParseAndLogExpr( PARSESTATE State )
{
    State = AParseShiftExpr( State );
    while (State.Error == SUCCESS) {
        TCHAR Op = ANextChar( &State );
        LONG Value = State.Value;

        if (Op != TEXT( '&' )) {
            break;
        }
        State.Token++;
        
        State = AParseShiftExpr( State );
        APerformArithmeticOperation( &State, Op, Value, State.Value );
    }
    return State;
}

//      shiftexpr -> addexpr [SHIFTOP addexpr]*             <<, >>
PARSESTATE
AParseShiftExpr( PARSESTATE State )
{
    State = AParseAddExpr( State );
    while (State.Error == SUCCESS) {
        TCHAR Op = ANextChar( &State );
        LONG Value = State.Value;

        if (Op != TEXT( '<' ) && Op != TEXT( '>' )) {
            break;
        }
        State.Token++;
        
        if (Op != ANextChar( &State )) {
            State.Error = MSG_SET_A_MISSING_OPERATOR;
            return State;
        }
        State.Token++;
        
        State = AParseAddExpr( State );
        APerformArithmeticOperation( &State, Op, Value, State.Value );
    }
    return State;
}

//      addexpr -> multexpr [ADDOP multexpr]*               +, -
PARSESTATE
AParseAddExpr( PARSESTATE State )
{
    State = AParseMultExpr( State );
    while (State.Error == SUCCESS) {
        TCHAR Op = ANextChar( &State );
        LONG Value = State.Value;

        if (Op != TEXT( '+' ) && Op != TEXT( '-' )) {
            break;
        }
        State.Token++;
        
        State = AParseMultExpr( State );
        APerformArithmeticOperation( &State, Op, Value, State.Value );
    }
    return State;
}

//      multexpr -> unaryexpr [MULOP unaryexpr]*            *, /, %
PARSESTATE
AParseMultExpr( PARSESTATE State )
{
    State = AParseUnaryExpr( State );
    while (State.Error == SUCCESS) {
        TCHAR Op = ANextChar( &State );
        LONG Value = State.Value;

        if (Op != TEXT( '*' ) && Op != TEXT( '/' ) && Op != TEXT( '%' )) {
            break;
        }
        State.Token++;
        
        State = AParseUnaryExpr( State );
        APerformArithmeticOperation( &State, Op, Value, State.Value );
    }
    return State;
}

//      unaryexpr -> UNARYOP unaryexpr                      +, -, !, ~
//                   ( expr ) |                             ()
//                   NUMBER
//                   LITERAL
PARSESTATE
AParseUnaryExpr( PARSESTATE State )
{
    TCHAR c = ANextChar( &State );
    PTCHAR FirstChar;
    PTCHAR EndOfName;
    
    if (c == NULLC) {
        State.Error = MSG_SET_A_MISSING_OPERAND;
        return State;
    }
    
    //  ( expr )
    if (c == TEXT( '(' )) {
        State.Token++;
        State = AParseExpr( State );
        if (State.Error != SUCCESS) {
            return State;
        }
        c = ANextChar( &State );
        if (c != TEXT( ')' )) {
            State.Error = MSG_SET_A_MISMATCHED_PARENS;
        } else {
            State.Token++;
        }
        return State;
    }

    //  UNARYOP unaryexpr
    if (_tcschr( szUnaryOps, c ) != NULL) {
        State.Token++;
        State = AParseUnaryExpr( State );
        if (State.Error != SUCCESS) {
            return State;
        }
        APerformUnaryOperation( &State, c, State.Value );
        return State;
    }
    
    //  NUMBER
    if (_istdigit(c)) {
        State.Value = _tcstoul( State.Token, &State.Token, 0 );
        if (State.Value == ULONG_MAX && errno == ERANGE) {
            State.Error = MSG_SET_NUMBER_TOO_LARGE;
        } else if (_istdigit( *State.Token ) || _istalpha( *State.Token )) {
            State.Error = MSG_SET_A_INVALID_NUMBER;
        }
        return State;
    }
    
    //  Must be literal

    if (!AParseVariable( &State, &FirstChar, &EndOfName )) {
        State.Error = MSG_SET_A_MISSING_OPERAND;
        return State;
    }
    
    State.Value = AGetValue( FirstChar, EndOfName );
    return State;
}

/***    SetArithWork - set environment variable to value of arithmetic expression
 *
 *  Purpose:
 *      Set environment variable to value of arithmetic expression
 *
 *  int SetArithWork(TCHAR *tas)
 *
 *  Args:
 *      tas - pointer to null terminated string of the form:
 *
 *          VARNAME=expression
 *
 *  Returns:
 *      If valid expression, return SUCCESS otherwise FAILURE.
 *
 */

int SetArithWork(TCHAR *tas)
{
    PARSESTATE State;

    //
    // If no input, declare an error
    //
    if (!tas || !*tas) {
        PutStdErr(MSG_BAD_SYNTAX, NOARGS);
        return(FAILURE) ;
    }

    //
    //  Set up for parsing
    //
    
    State.Token = StripQuotes( tas );
    State.Value = 0;
    State.Error = SUCCESS;

    State = AParseExpr( State );
    if (State.Error == SUCCESS && ANextChar( &State ) != NULLC) {
        State.Error = MSG_SET_A_MISSING_OPERATOR;
    }
    
    if (State.Error != SUCCESS) {
        PutStdErr( State.Error, NOARGS );
        //printf( "%ws\n", tas );
        //printf( "%*s\n", State.Token - tas + 1, "^" );

    } else if (!CurrentBatchFile) {
        cmd_printf( TEXT("%d"), State.Value ) ;
    }

    return State.Error;
}