/* ------------ [ 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 #include #include #include #include #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); }