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