Metropoli BBS
VIEWER: genv_c.c MODE: TEXT (ASCII)
/* ------------ [ SETTING GLOBAL ENVIRONMENT VARIABLES ]------------- *\ 
   alexad3@icebox.iceonline.com (Alexander J. Russell)
                                                                     
   The Environment is a section of Memory ( up to 32K in size )      
   which contains a series ( or a SET ) of 'Environment Variables'   
   in the format:                                                    
               VariableName=VariableText                             
                                                                     
   Both the VariableName and VariableText are ASCIIZ                 
   (NULL-terminated) strings.  Environment Variables are managed and 
   manipulated via the DOS 'SET' command ( See your DOS manual for   
   more details ).                                                   
                                                                     
   Before DOS executes an application ( or when a parent program     
   calls the DOS EXEC function - INT 21,4B - with the Parent's Env.  
   Segment set to zero ), DOS allocates a block of memory to which   
   it copies its ( or the parent program's ) current Environment.    
   The Segment value of the allocated block ( Environment Pointer )  
   is stored in the application's PSP ( at Offset 2Ch ).  Therefore  
   each application has a local copy of it's parent Environment.     
   The primary SHELL ( which may be set by the SHELL command in      
   CONFIG.SYS - usually COMMAND.COM ) creates the *MASTER*           
   Environment at boot time.                                         
                                                                     
   The size of Memory Blocks allocated for Local Copies of the       
   Environment are just big enough to accomodate the variables of    
   the parent's Environment.  The Borland C/C++ compilers, however,  
   provide the putenv() function which may 'realloc' the Local       
   Environment Memory Block to make room for additional Environment  
   Variables.  Local Environment copies, however, are transient: the 
   Memory Block of the Local Copy is freed when the application      
   terminates.   This implies that changes made to one's local copy  
   of the Environment are only active during one's lifetime.  An     
   application's changes to its Local Copy can be inherited by a     
   child of the application.  Therefore "you can leave a legacy for  
   your children but you do not have access to your parent's         
   belongings !".                                                    
                                                                     
                                                                     
   To SET a permanent/persistent Environment Variable, one must      
   modify the *MASTER* Environment.   The current versions of DOS do 
   not provide any documented interfaces to modify the *MASTER*      
   Environment or even detect its location.  Various methods have    
   been suggested and used for finding the Master Environment;  This 
   example uses a method which relies on the following:              
   Since DOS creates an Environment for every application, the Env.  
   segment always preceed the application which owns the Environment.
   However, this logic does not apply for the Env. Seg. created and  
   owned by DOS itself: Since DOS creates the Env., the owner in this
   case is at a lower address than its environment.  Scanning for the
   Process whose Env. Seg. is higher that the Seg. of the application
   allows us to determine the Segment of the Master Environment.     
\* ------------------------------------------------------------------ */

/*
    Modified by Lee Hamel (hamell@cs.pdx.edu) - 4/9/95
    Changed C code to be (closer to) 100% portable between compilers
    Added more comments
    Cleaned things up, added a few frills
    Added get_envsize() and dump_env().

    Compile with /Zp1 (for Microsoft) - pack structs on n-byte boundary
*/
 
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <string.h>
#include <malloc.h>

#define envSeg(p) (*((unsigned short far *)MK_FP(p->arenaOwner, 0x2C)))
#define parSeg(p) (*((unsigned short far *)MK_FP(p->arenaOwner, 0x16)))

#define VERSION         "1.1"
#define GENV_SUCCESS    0   // Successful
#define GENV_ERROR      -1  // General Error
#define GENV_NOSPACE    -2  // Insufficient space in env. for new var.

typedef struct Arena
{
    unsigned char   arenaSignature;
    unsigned short  arenaOwner;
    unsigned short  arenaSize;
    unsigned char   reserved[3];
    unsigned char   arenaName[8];
} ARENA;


char far *gEnvPtr;


/* ------------ [ DETECTING LOCATION OF MASTER ENV...  ]------------- *\ 

  The following section of the example determines the location of the
  Master Environment.   The setting/clearing of an Environment Var.  
  is separated from the Env. Seg. detection routines so that other   
  methods for locating the Master Environment can be easily          
  implemented.                                                       
\* ------------------------------------------------------------------ */
ARENA far * GetFirstArenaPtr(void)
{
/*
Int 21 Fn 52  U - DOS 2+ Internal - "sysvars" - Get List Of Lists          [D]
   AH = 52h

Return: ES:BX -> DOS list of lists (see #0793)

ES:[BX-2] contains the segment of the Memory Control Block

Format of DOS memory control block (see also below):
Offset Size    Description (Table 0794)
 00h   BYTE    block type: 5Ah if last block in chain, otherwise 4Dh
 01h   WORD    PSP segment of owner or special flag value (see #0795)
 03h   WORD    size of memory block in paragraphs
 05h  3 BYTEs  unused by MS-DOS
       (386MAX) if locked-out block, region start/prev region end
---DOS 2.x,3.x---
 08h  8 BYTEs  unused
---DOS 4+ ---
 08h  8 BYTEs  ASCII program name if PSP memory block or DR-DOS UMB,
         else garbage
       null-terminated if less than 8 characters

Notes: the next MCB is at segment (current + size + 1)
   under DOS 3.1+, the first memory block is the DOS data segment,
     containing installable drivers, buffers, etc.  Under DOS 4+ it is
     divided into subsegments, each with its own memory control block
     (see #0798), the first of which is at offset 0000h.
   for DOS 5+, blocks owned by DOS may have either "SC" or "SD" in bytes
     08h and 09h.  "SC" is system code or locked-out inter-UMB memory,
     "SD" is system data, device drivers, etc.

(Table 0795)
Values for special flag PSP segments:
 0000h free
 0006h DR-DOS XMS UMB
 0007h DR-DOS excluded upper memory ("hole")
 0008h belongs to DOS
 FFF7h 386MAX v6.01+ ???
 FFFAh 386MAX UMB control block (see #0655 at AX=4402h"386MAX")
 FFFDh 386MAX locked-out memory
 FFFEh 386MAX UMB (normally immediately follows its control block)
 FFFFh 386MAX v6.01+ device driver
   Some versions of DR-DOS use only seven characters of the program name,
     placing a NUL in the eighth byte.
*/

    unsigned int myseg;

    _asm
    {
        push    es
        push    ax
        push    bx

        mov     ah,52h
        int     21h
        mov     ax,es
        mov     ax,es:[bx-2]
        mov     [myseg],ax

        pop     bx
        pop     ax
        pop     es
    }

#ifdef DEBUG
    printf("myseg = 0x%04x\n",myseg);
#endif

    return ((ARENA far *) MK_FP(myseg, 0));
}
 
 
void far * GetMasterEnvPtr(void)
{
    ARENA far *aPtr;
    unsigned int myseg;
    
    aPtr = GetFirstArenaPtr();
    if (aPtr != NULL)
    {
        // while not last block in chain --- 0x5A signifies last block
        while (aPtr->arenaSignature == 0x4D)
        {
            myseg = FP_SEG(aPtr) + 1;
            if (aPtr->arenaOwner == myseg)
            {
                if (envSeg(aPtr) > aPtr->arenaOwner)
                {
                    if (parSeg(aPtr) == aPtr->arenaOwner)
                    {
                        return (MK_FP(envSeg(aPtr), 0));
                    }
                }
            }

            myseg += aPtr->arenaSize;
            aPtr = (ARENA far * ) MK_FP(myseg, 0);
        }
    }

    return(NULL);
}


unsigned int get_envsize(unsigned int envptr)
{
    unsigned int retval;

    retval = ( (ARENA far *) MK_FP(envptr-1, 0) )->arenaSize << 4;
    return (retval);
}


/* ------------ [ MODIFYING THE MASTER ENVIRONMENT...  ] ------------ *\

  The following function modifies the Master Environment...   The    
  Environment variable name as well as its value ( Text ) are the two
  variables required.   If the second variable is NULL, the variable 
  Name ( if currently SET ) will be cleared from the                 
  Master Environment.                                                
                                                                     
  The return values are :                                            
      0 :  Successful
     -1 :  General Error                                             
     -2 :  Insufficient space in Environment for New Variable        
                                                                     
  NOTE:  Changes made to the Master Environment do *NOT* affect the  
         application's local Environment.  Use the Borland C/C++     
         putenv() function to modify the Local Environment.          
\* ------------------------------------------------------------------ */

short global_put_env(char *varName, char *varText)
{
    void far *envPtr;
    char far *eptr;
    char far *p;
    char far *q;
    size_t varLen;
    size_t reqLen;
    size_t envSize;
                  
    envPtr = gEnvPtr;
    if (envPtr == NULL)
        return(GENV_ERROR);
    else
    {
        eptr = (char far *) envPtr;
        p = (char far *) envPtr;
        varLen = strlen(varName);

        if (varText)
            reqLen = varLen + strlen(varText) + 2;
        else
            reqLen = varLen + 2;

        envSize = get_envsize(FP_SEG(envPtr));
 
        while (*(unsigned short far *) eptr)
        {
            eptr++;
            if (FP_OFF(eptr) == 0x7FFF) return(-1);
        }

        if ((FP_OFF(eptr) + reqLen) >= envSize)
            return(GENV_NOSPACE);
 
        strupr(varName);
// Don't always want to upper-case the variable text
//        if (varText) strupr(varText);
 
        while (p < eptr)
        {
            if ((_fstrncmp(varName, p, varLen ) == 0) &&
                (*(p + varLen) == '='))
            {
                q = p;
                q += _fstrlen(q);
 
                if (*(unsigned short far *) q == 0)
                {
                    p--;
                    break;
                }

                q++;
                movedata(FP_SEG(q), FP_OFF(q), FP_SEG(p), FP_OFF(p),
                    FP_OFF(eptr) - FP_OFF(q) + 2);

                while(*(unsigned short far *) p) p++;
                break;
            }

            while (*p) p++;
            if (*(unsigned short far *) p) p++;
        }
 
        if (varText == NULL)
        {
            *(unsigned short far *) p = 0;
            return (GENV_SUCCESS);
        }
 
        p++;
        _fstrcpy(p, varName);
        _fstrcat(p, "=");
        _fstrcat(p, varText);
        p += reqLen;
        *p  = '\0';
    }
 
    return (GENV_SUCCESS);
}


void dump_env(void)
{
    // double 0 indicates end of environment
    printf("[Current Environment - Master EnvPtr is %Fp]\n",gEnvPtr);
 
    while (*( unsigned short far * ) gEnvPtr)
    {
        if (*gEnvPtr != 0)
            putchar(*gEnvPtr);
        else
            putchar('\n');
        gEnvPtr++;
    }

    printf("\n");
}


int main(int argc, char *argv[])
{
    if (argc >= 2 && (argv[1][0] == '-' || argv[1][0] == '/') &&
        (argv[1][1] == '?' || argv[1][1] == 'h' || argv[1][1] == 'H'))
    {
        printf("Global Environment Variable List/Set/Clear v%s\n",VERSION);
        printf("Usage: %s [varName] [varText]\n"
               "       If varName exists and varText is not provided,\n"
               "       then varName is cleared, "
               "otherwise varName is set to varText\n",
               (strrchr(argv[0],'\\') + 1));
        return (1);
    }

    gEnvPtr = (char far *) GetMasterEnvPtr();
    if (gEnvPtr == NULL)
    {
        printf("Error getting Master Env Ptr!\n");
        return (2);
    }

    if (argc == 1)
        global_put_env(NULL, NULL);
    else if (argc == 2)
        global_put_env(argv[1], NULL);
    else
        global_put_env(argv[1], argv[2]);

    dump_env();
    return (0);
}
[ RETURN TO DIRECTORY ]