GEMENV v1.1 Environment Variable Manager for the GEM Desktop Public Domain Program by Ian Lepore Overview Environment variables (referred to as "env vars" in much of this document) are a facility for storing information in character-string format for a duration that exceeds the run of a single program. (My, that sounds stuffy.) Many popular operating systems support env vars, among them MSDOS, Unix, and GEMDOS. Unfortunately, while GEMDOS supports env vars, the GEM desktop doesn't, so it's a feature that goes largely unused on the ST. This program was written to bring to GEM desktop users all the power of environment variables that command-shell users have been enjoying for years. With some software, such as the public domain Sozobon C compiler, env vars are required for the software to work correctly. About Environment Variables Environment variables are stored in memory. They are character strings with a format of "VARIABLE=VALUE". Every program inherits a copy of the env vars owned by its parent (the program that started it). DOS is responsible for making a copy of the parent's env data area at the time that it starts a new program. It places a pointer to this copy in the program's basepage, so that the program can access the env var data through the pointer. When programming in C, there are two functions for accessing env vars, getenv() and putenv(). These functions retrieve or store a value in the env data area. However, since each program receives a copy of its parent's env vars, using putenv() won't make permanent changes to the system's env data area; it can only affect the program's local copy. (GEMENV.PRG uses the ST cookie jar to work around this problem.) Therefore, the biggest use of env vars for most programs involves reading the values that are already there. Programs such as compilers often read env vars to get device and path information such as where to search for #include files or runtime libraries. When using GEM, the desktop is the parent program to all programs that are started by clicking on a file. These programs, therefore, inherit a copy of the desktop's env data area. Unfortunately, the desktop doesn't keep much useful information in its env data area. In fact, it's only a few bytes long, and contains the string "PATH=C:\" (or "PATH=A:\"). GEM itself uses this env var (PATH=) to locate RSC files when they are not found in the current default directory. Using GEMENV The GEMENV program has two different modes, depending on whether it is run from the AUTO folder or from the desktop. When run from AUTO, it prepares the system to support env vars, and loads the initial env vars from a file called ROOT.ENV, which must be in the root directory of the boot drive. This process is described in greater detail in the section labeled "How GEMENV Works." When run from the desktop, the program does a check to see if it was installed from the AUTO folder at bootup time. If not, the program will complain that a root environment doesn't exist. In this case, you can still use the program to create an initial set of env vars and save them into a ROOT.ENV file, and then reboot to install them into the system. (If you obtained this program as part of Ian's Sozobon C release, the Sozobon installation program will have created your initial ROOT.ENV file for you.) The main interface to the program is a large dialog box that lists all the current in-memory env vars and their values. At the bottom of the dialog is a series of buttons providing access to the major functions of the program. The displayed variables are also active buttons: clicking on a variable in the scolling area of the dialog will bring up the edit dialog which allows you to edit or delete a variable. Most of the buttons are self-explanatory, but several need further explanation. The CANCEL button will allow you to exit the program without saving any changes you've done into the system env data area. The OK button saves all changes INTO MEMORY ONLY, and then exits. The OPTIONS button provides access to the LOAD FILE and SAVE FILE options. It is possible to start the program, use the LOAD option to load a set of variables from a file, make changes to the variables, use the SAVE option to store them, and then exit via CANCEL so that the changes don't affect the current in-memory variables even though they've been stored to a file. It can be handy to keep several different configurations stored in different files, especially if you use more than one C compiler. Remember that at bootup time, GEMENV will only install itself if a ROOT.ENV file exists, so this file should contain your most common configuration. The OPTIONS dialog also provides a choice between PC FORMAT and TOS FORMAT environment variables. This choice affects the way the variables are formatted in memory. TOS FORMAT variables treat the variable's name and value as two separate strings, each with its own null terminator character (eg, "VARIABLE=\0VALUE\0"). The more common PC FORMAT stores the variable name and value as a single string (eg, "VARIABLE=VALUE\0"). It is recommended that you use PC FORMAT unless your software specifically requires TOS FORMAT. (You may need to use TOS FORMAT if you're using TOS 1.0 -- I'm not sure about this yet.) GEMENV places few restrictions on the names or values stored in env vars. The only hardcore restrictions are that variable names can't contain an '=' character (since that delimits the name from the value), and you can't store binary data in the name or value string. (Mainly because GEMENV doesn't have a hex editor, but also because a binary zero in any position would be mistaken for the end of the value string.) In general, the programs which use env vars dictate what the names of env vars must be and what types of data should appear in the value string. Most programs require uppercase names, and some require uppercase values, but these are not universal standards. The GEM desktop really likes to see a PATH= variable in the set somewhere, and the value string must be a list of valid pathnames separated with semicolons between each name, and with the trailing backslash on each pathname. Most ST software that uses a PATH= variable is also happy with this format. (And, if you design your own software, you should stay compatible with this if you access the PATH= variable.) As mentioned previously, GEM will search the paths listed in PATH= for .RSC files, If you want, you can put all your resource files in one place and put that pathname in the PATH= list. This can also be handy when you have installed applications. By putting all your installed aps and their RSC files into one path, and listing that path in the PATH= var, you'll never run into the "cannot load RSC file" glitches that installed aps are prone to. There will always be two special variables in your configuration which you cannot delete: ENV$ROOTSIZE and ENV$OPTIONS. ENV$OPTIONS stores the state of the buttons in the options dialog, so that whenever you use the program, the options will be as you last set them. The ENV$ROOTSIZE variable tells the program how much memory to allocate for env data area at bootup time. Since you might add to the env vars at any time by running GEMENV, a little extra memory is always allocated when the ROOT.ENV file is loaded. The program, by default, uses the size of the current env data, rounded up to the next 1k boundry. Typically, ENV$ROOTSIZE will always equal 1024 unless you have a lot of vars. You can manually change this value upwards if you need to, but it can never be made smaller than the current size rounded up to the next 1k (if you try, the program overrides you when you save.) The format of the ROOT.ENV file (and other .ENV files) is compatible with a normal text editor. Each line contains a single entry in the format NAME=VALUE, and the lines are terminated with a CRLF sequence (or simply LF). An installation utility program might want to create or modify the ROOT.ENV file, and that's fine, given that it follows a couple simple rules: - There MUST be an ENV$ROOTSIZE variable, and it MUST be an even multiple of 1024 bytes, and the value MUST be larger than the total size of all the env data in the file. - There MUST be an ENV$OPTIONS variable. If one exists already, don't monkey with the values in it. If you are creating a .ENV file from scratch, create this as ENV$OPTIONS=TYY. If you use any other value beside TYY, don't come crying to me if the GEMENV fails to work correctly! If you are modifying an existing ROOT.ENV file programmatically, please be aware that the user may be fond of the values s/he currently has stored in the variables. To add your software path, for example, don't just create a new PATH= variable, append your path to what's already there. If for some reason you just can't take the time to code up a fancy modification program to handle the data that's already there, then at the very least create a ROOT.BAK for the user which contains the current data before you modify it. How GEMENV Works As mentioned above, whenever a program starts, DOS provides that program a copy of its parent's env data area. This applies to the process that TOS uses to start the GEM desktop as well. After all the AUTO programs have finished running, TOS creates a basepage for the desktop using the standard DOS Pexec() call. However, rather than loading a file from disk to execute, it jumps through the exec_os vector at $4FE in low memory after the basepage is created. When the GEMENV program is run from the AUTO folder it detects that fact by trying to do an appl_init() call and getting a bad status because GEM isn't available yet. When this happens, it bypasses the normal dialog stuff it does when run from the desktop. Instead, it loads the env vars from the ROOT.ENV file, and checks the value of the ENV$ROOTSIZE variable. It allocates that much memory and stores the env vars (in TOS or PC format, as indicated by the ENV$OPTIONS var.) After the env data area is set up, GEMENV installs a cookie in the system cookie jar so that it can locate the installed data area later, when run from the desktop. (If a cookie jar doesn't exist yet in the system, one is installed in the standard Atari-recommended fashion.) The cookie used is "ENV$", which isn't likely to conflict with any existing cookies (and GEMENV will work even if there is a conflict, although it's quite likely that the any other user of an "ENV$" cookie would die.) Next, the program saves the current contents of the exec_os system vector, and places a 'hook' into that vector, so that when TOS jumps through the vector to start the desktop, a routine in the resident portion of GEMENV will get control instead. Once this is done, GEMENV issues a Ptermres() call to remain resident, keeping only a small program stub (about 300 bytes) and the env data itself. The stub and the data are adjacent; GEMENV's installation will not fragment your free memory area like some TSRs do. When all the other AUTO programs are done, and TOS starts the desktop, the stub left resident by GEMENV gets control. All this stub does is replace the environment data pointer in the current program basepage with a pointer to the data area loaded from the ROOT.ENV file. It then restores the old value of the exec_os vector, then jumps through that vector. Because the env data pointer in the desktop's basepage was changed, the desktop starts with the full contents of the ROOT.ENV data area instead of the minimal PATH=C:\ data area that TOS created for it. When the desktop starts other programs, it will pass copies of this full data area along to everyone else. Note that GEMENV does not leave any hooks in any system vectors once the desktop has started. After replacing the env pointer and passing control along to the desktop, the resident portion of GEMENV never executes anything again; it just sits there providing a data area for everyone else. In this sense, GEMENV is a passive TSR that is unlikely to interfere with anything else in the system. Also note that there are other programs in the world that use the exec_os vector. Foremost among these are the replacement desktop programs that install themselves in place of the GEM desktop. Because a replacement desktop truly steals the exec_os vector (rather than borrowing it and then jumping through it as GEMENV does), it may be necessary to play with the order of the files in your AUTO folder a bit to ensure that GEMENV can briefly gain control of the system. In general, if GEMENV reports successfull installation while AUTO stuff is running, and then complains about no root env data when you run it from the desktop, try rearranging your AUTO folder. Programming Notes It was mentioned earlier that your programs can't modify the system env data because they are really only modifying their own local copy of the env data when the putenv() function is used. Your programs can, however, use the same technique that GEMENV uses to modify the main system env data area. This techique involves locating the root data area via the ENV$ cookie, then accessing that memory directly. I'm not going to go into a long discussion of using the cookie jar; see the appropriate Atari docs for info on how to locate the ENV$ cookie. The value of the ENV$ cookie is a 32-bit pointer to the following data structure: struct gemenv_control { long magic; short version; char *pdata; long sdata; long reserved[4]; }; The 'magic' field must contain the value 0x04021959. If it does not, someone else is also using ENV$ as a cookie, and you found them, not GEMENV. In this case, just keep searching, you may find another ENV$ cookie that points to the right magic value. The version word is the installed GEMENV version, encoded as 0xVVRR, where VV is the major version and RR is the release. The current v1.1 value is 0x0101. The pdata field is the pointer to the root env data area. It is this pointer that you would use to access the env vars directly. (But, NEVER EVER modify this pointer! (Or, for that matter, anything else in this structure.) Doing so will break GEMENV, and it will NOT cause the desktop to see a new set of env vars; that can only be done via the exec_os hook.) The sdata field is the size of the root env data area. If you modify the root area, you must ensure that the modifications don't exceed this size. The reserved array is just that: reserved. Right now, this will be four longwords of zeroes. Someday, I'm going to provide a programmatic interface so that you don't have to manipulate the root data directly but can call gputenv() and ggetenv() to access the global data instead of your program's local copy. When I do that, the vectors for the functions will go here. Credits and Disclaimers I'd like to thank Mike Dorman for helping me test this program, and Bob Goff for both testing, and for obtaining the Atari docs on the cookie jar for me. I'd like to thank David Stabb for inspiring the program, although I'm sure that by now he's quite forgotten how or why he was inspirational in this project. :-) This program, the executable code, documents, and source code are all in the public domain, and the author specifically releases any and all rights associated with it. You are free to modify and/or use this work in any way you see fit. You may include or distribute this work as a part of your own product or program, be it public domain, shareware, or commercial. If you incorporate this into your own product, there is no need to include any reference to me or my work on it unless you feel like it. I just want to see it distributed widely, and I want to see ST applications start to make better use of environment variables. This software is delivered on an as-is basis, and no warranties are made, including warranty of suitability for a particular purpose. The author assumes no responsibility for the consequences of using this software, even if the consequences result from defects in this program. Besides, I don't have enough money to make suing me worth your while. :-) Ian Lepore moderator, BIX atari.st and c.language conferences 07/19/91 -------------------------------------------------------- < OK TO PORT > This information comes from the atari.st conference on BIX (r), the BYTE Information Exchange. For additional information about BIX, call 800-227-2983 or 603-924-7681. ---------------------------------------------------------