mike@ivory.SanDiego.NCR.COM (Michael Lodman) (07/28/87)
Could someone please email and tell me how to read the DOS environment variables with Turbo Pascal? Thanks. -- Michael Lodman (619) 485-3335 Advanced Development NCR Corporation E&M San Diego mike.lodman@ivory.SanDiego.NCR.COM {sdcsvax,cbatt,dcdwest,nosc.ARPA,ihnp4}!ncr-sd!ivory!lodman
tr@wind.bellcore.com (tom reingold) (07/29/87)
In article <151@ivory.SanDiego.NCR.COM> mike@ivory.UUCP (Michael Lodman) writes: $ Could someone please email and tell me how to read the DOS environment $ variables with Turbo Pascal? $ $ Thanks. $ $ $ -- $ Michael Lodman (619) 485-3335 Mike Morearty posted this to the net a while back. I made very minor changes. Where I did so, I put my first name (Tom) on the line. The program should be pretty clear. ------------------------------------------------------------ (* Path: seismo!lll-crg!nike!ucbcad!zen!cory.Berkeley.EDU!morearty From: morearty@cory.Berkeley.EDU (Mike Morearty) Newsgroups: net.micro.pc Subject: You can get argv[0] in DOS 3.x Message-ID: <468@zen.BERKELEY.EDU> Date: Mon, 6-Oct-86 06:18:52 EDT Date-Received: Mon, 6-Oct-86 19:20:50 EDT Sender: news@zen.BERKELEY.EDU Reply-To: morearty@cory.Berkeley.EDU (Mike Morearty) Organization: University of California, Berkeley Lines: 111 I just discovered an undocumented feature of the more recent versions of DOS: it is at last possible to obtain argv[0]. I discovered this because I was surprised to read in the DOS 3.2 manual that XCOPY.EXE behaves differently if you rename it as MCOPY.EXE. So I disassembled it, and found the following method to determine the name of the program that is being run. As you probably know, in all versions of DOS from 2.0 on, there is an environment area in memory with strings that can be created with the SET command, such as "set PATH=\". The segment of this environment area is stored as a word at offset 2Ch into the Program Segment Prefix. If, for example, the word at address 2Ch is 1000, then the environment area begins at offset 1000:0000. This environment area consists of null-terminated strings of the form "NAME=value" (with NAME all upper case), followed by a null byte. If the next byte is also a null, then that marks the end of the environment area. (Thus, if the very first byte in the environment area is a null, there are no strings present.) Now, the good stuff: in DOS 3.1 and 3.2, and possibly in 3.0 (I haven't been able to test it), this environment area is followed by the complete path and filename of the program that has been invoked. Here is the exact format (#x indicates a byte with value x): NAME=value#0 ;one or more null-terminated strings #0 ;the terminating null byte #1 ;followed by a byte with value one #0 ;followed by another null byte C:\PAS\ENV.COM#0 ;argv[0], null-terminated Note the following changes from older versions of DOS: 1. There are always TWO null bytes at the end of the environment area. In older versions, there may have been only one null byte if there were no environment strings present. 2. The environment area is immediately followed by a 1 and then a 0. If this is not present, assume that argv[0] is not present either. Note that the drive specifier and/or certain subdirectory names in the path may be lower case, and there may be "." and/or ".." entries in the path, depending on the line that the user typed to invoke the program. Some examples follow, assuming that the user is in the directory C:\DIR, and there is a program in that directory called PROGRAM.COM: User typed argv[0] contains ---------- ---------------- program C:\DIR\PROGRAM.COM PROGRAM C:\DIR\PROGRAM.COM .\program C:\DIR\.\PROGRAM.COM \dir\program C:\dir\PROGRAM.COM c:program c:\DIR\PROGRAM.COM program.exe C:\DIR\PROGRAM.COM Here is a sample program written in Turbo Pascal (version 3.x), to display argv[0]. ================================================================= *) { Argv0.pas - Display argv[0], in other words, the path and filename that invoked this program. Works under DOS 3.1 and 3.2, and possibly 3.0. } program Argv0; {$p512} var env: integer absolute cseg:$2C; (* segment of environment *) cp: integer; (* pointer into environment segment *) r: record (* record of registers *) ax,bx,cx,dx,bp,si,di,ds,es,flags: integer; end; ch: char; begin r.ax := $3000; MsDos(r); (* get DOS version number *) if (lo(r.ax) <> 3) or (hi(r.ax) > $20) then begin writeln('Needs DOS 3.0, 3.1, or 3.2'); halt; end; cp := 0; (* point to beginning of segment *) (* find the end of the environment space, e.g., where two nulls are found in a row. *) while (memw[env:cp] <> 0) do begin ch := chr(memw[env:cp]); (* Tom *) if ord(ch) <> 0 then (* Tom *) write(ch) else (* Tom *) writeln; cp := cp + 1; end; cp := cp + 2; (* move past the two nulls *) (* If this version of DOS has argv[0], then the two nulls are immediately followed by an ASCII 1 and then another null. *) if (memw[env:cp] <> 1) then begin writeln('Invalid memory format'); end else begin cp := cp + 2; (* move past the 1 and the 0 *) writeln; (* Tom *) writeln; (* Tom *) while mem[env:cp] <> 0 do begin (* argv[0] is null-terminated *) write(chr(mem[env:cp])); cp := cp+1; end; writeln; (* Tom *) end; end. -- Mike Morearty Tom Reingold INTERNET: tr@bellcore.bellcore.com UUCP: {seismo,ihnp4,ucbvax,decvax}!bellcore!tr {ulysses,allegra,clyde,princeton}!bellcore!tr
jfb@vi.ri.cmu.edu.UUCP (08/01/87)
In article <1690@bellcore.bellcore.com>, tr@wind.bellcore.com (tom reingold) writes: > In article <151@ivory.SanDiego.NCR.COM> mike@ivory.UUCP (Michael Lodman) writes: > $ Could someone please email and tell me how to read the DOS environment > $ variables with Turbo Pascal? > > Mike Morearty posted this to the net a while back. I made very > minor changes. Where I did so, I put my first name (Tom) on the > line. The program should be pretty clear. > > I just discovered an undocumented feature of the more recent > versions of DOS: it is at last possible to obtain argv[0]. > The method described is documented, quite clearly, in the PC-DOS 3.0 (3.1?) manual. Maybe your documentation is missing it... I can't even guess. Supposedly, this was considered a problem in DOS 2.0. Personally, I've never had a real desire to know what argv[0] is. MS-DOS doesn't support links (hard or symbolic), so each file has a unique name. But I can see where you might want to be really friendly or something, and parse argv[0]. Anyway, keep on hacking... John Brennen jfb@vi.ri.cmu.edu Visual Inspection Lab Carnegie Mellon Univ.
madd@bucsb.bu.edu.UUCP (Jim "Jack" Frost) (08/02/87)
In article <151@ivory.SanDiego.NCR.COM> mike@ivory.UUCP (Michael Lodman) writes: >Could someone please email and tell me how to read the DOS environment >variables with Turbo Pascal? > >Thanks. This is easy. Note that MS-DOS gives you a pointer to the environment variable list at memw[cseg:$2C]. This simple program will print out the environment; I suspect that anyone with a little knowledge can expand it to a complete getenv() style function in just a few minutes. [note -- I am just making this up now. no flames if I screw it up a little] program printenv; var eseg, { segment of environment } p : integer; { pointer within segment } begin eseg:= memw[cseg:$2C]; { get pointer to env segment } p:= 0; { initialize pointer within segment } repeat { loop for all environment entries } while mem[eseg:p]<>0 do begin { loop for chars in each entry } write(chr(mem[eseg:p])); { display character in entry } p:= p+1 { go to next character in entry } end; { end environment enty loop } writeln; { next line for next entry } p:= p+1 { go to next environment entry } until mem[eseg:p]=0 { environment ends in a null byte } end. For those of you who don't know, MS-DOS defines the environment as a series of null-terminated strings that ends in a null. This means that each entry in the environment ends in a null (chr(0), #0, whatever), and the last entry has another null following the null of the last string. Pretty easy to program with that info and the address of the segment! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Jim Frost * The Madd Hacker | UUCP: ..!harvard!bu-cs!bucsb!madd H H | ARPA: madd@bucsb.bu.edu H-C-C-OH <- heehee +---------+---------------------------------- H H | "We are strangers in a world we never made"
mike@ivory.SanDiego.NCR.COM (Michael Lodman) (08/03/87)
I received requests for this information from several people by email so I am posting the response I received from John Knutson. I have not tried it as yet. From: hp-sdd!sdcsvax!husc6.harvard.edu!harvard!ut-sally!ut-ngp!auscso!knutson6 (John Knutson) { -------------------------------------------------------------------------- } { READENV.INC } { Copyright (C) 1985 24 Karat Consulting } { 4702 S. Alaska St. } { Seattle, Washington 98118 } { } { These Turbo Pascal functions can be used to search the DOS environment } { area. } { } { ENVSTR returns a Turbo string containing the DOS environment area. } { } { NEXTENVSTR returns the next string (keyword and value) in the environment, } { starting at the index passed. } { } { READENV returns a string whose value is the current environment setting } { for the keyword passed, or a null string if the keyword is not } { found. } { } { Typical environment keywords are PATH (the DOS search path) and COMSPEC } { (the name of the command processor). Remember that the environment passed } { to a program is a READ-ONLY copy of the real DOS environment...thus you } { can't put new strings in from an executing program to be read by } { another...but you can use it to communicate between two separate programs. } { The Pascal statements to use these functions are: } { } { StringVar := EnvStr; (* move environment into string *) } { StringVar := ReadEnv(KeywordName); (* read environment for a key *) } { StringVar := NextEnvStr(Env,N); (* read string in environment, *) } { (* starting at N *) } { e.g., } { Type } { Lstr = string[255]; } { Var } { Env, } { Works : lstr; } { Worki : integer; } { begin } { Env := EnvStr; } { writeln('Value for COMSPEC is ',ReadEnv('COMSPEC')); } { Worki := 1; } { writeln('Environment settings are: '); } { repeat } { Works := NextEnvStr(Env,Worki); } { if length(Works) > 0 then writeln(Works) } { until length(Works) = 0 } { end; } { } { The DOS command to put variables into the environment is: } { SET keyword=value } { Keywords are stored uppercase, but their values are stored as is. You can } { use this (undocumented) feature in .BAT files to read environment } { variables: } { } { ECHO The value of COMSPEC is %COMSPEC% } { } { that is, enclose the environment variable name in %'s to read its value. } { } { WARNING: These functions assume an environment area of not more than 255 } { bytes; that is, the size of a Turbo string. DOS sets up an initial } { environment area of 127 bytes. DOS will expand this dynamically when you } { use the SET command for more environment variables, and more space is } { needed. (This only applies to SET commands issued at the DOS prompt, not } { from within .BAT files). However, if you have installed any resident } { routines, you can't expand the environment area any further. There are } { several public domain routines to get around this limitation...however, } { this routine assumes you haven't got more than the 255 bytes that Turbo } { can handle in a string. If you do, don't use this routine. } { } { This routine has been placed in the Public Domain by the author and copies } { may be freely made for non-commercial, demonstration, or evaluation } { purposes. If you use this routine in a program for sale or for commercial } { purposes, please send money ($2 to $5 would be sufficient) to the address } { above; or if you make some wonderful correction or enhancement, please } { notify us at the same address. Thank you. } { } { Turbo Pascal is a Copyright of Borland International Inc. } { -------------------------------------------------------------------------- } type lstr = string[255]; function EnvStr: lstr; { -------------------------------------------------------------------------- } { This function returns a string containing the DOS environment. } { -------------------------------------------------------------------------- } var i : integer; ev : integer absolute cseg:$002c; { pointer to environment in PSP } evstr: lstr; begin i := 0; { find real length of environment } while memw[ev:i] <> 0 do i := i+1; { 2 null bytes ends the environment } if i > 255 then i := 255; { limit of Turbo string length } Move(mem[ev:0],evstr[1],i+1); { move used bytes to Turbo string } evstr[0] := chr(i+1); { and set length field } end; function NextEnvStr(evstr: lstr; var strt: integer): lstr; { -------------------------------------------------------------------------- } { This function gets the string starting at STRT in EVSTR, delimited by } { CHR(0). A null string returned indicates the end of the environment. } { -------------------------------------------------------------------------- } var lst : lstr; i : integer; begin i := strt; if i > 1 then delete(evstr,1,i-1); { remove unneeded part of string } i := pos(chr(0),evstr); { find the next null char } if i < 2 then EnvStr := '' { 1-end of env; 0-ERROR!! } else begin move(evstr[1],lst[1],i-1); { move string to temp string } lst[0] := chr(i-1); { set string length field } strt := strt+i; { increment next field starting pos } EnvStr := lst { return string } end; end; function ReadEnv(keyword: lstr): lstr; { -------------------------------------------------------------------------- } { This function looks in the DOS environment for KEYWORD, and returns its } { current value. If KEYWORD isn't found, a null string is returned. } { -------------------------------------------------------------------------- } var i,lp : integer; { work variables } evarea, { Turbo string for environment } estr : lstr; begin for i := 1 to length(keyword) do keyword[i] := UpCase(keyword[i]); { environment parms are uppercase} if keyword[length(keyword)] <> '=' then { environment format is: } keyword := keyword + '='; { KEYWORD=value } lp := Length(keyword); { get length of parameter } evarea := EnvStr; { get DOS environment into string} i := 1; { set index to start at 1 } estr := NextEnvStr(evarea,i); { get first environment string } while (estr <> '') and { null string means exhausted search } (Copy(estr,1,lp) <> keyword) do { if it's not our keyword, skip it, } estr := NextEnvStr(evarea,i); { and extract the next string } if estr <> '' then { we found the string requested } Delete(estr,1,lp); { remove the KEYWORD= part } ReadEnv := estr; { set return value } end; \032\032\032\032\032\032\032\032\032\032\032\032\032\032\032\032\032\032-- John Knutson {ihnp4,allegra,ut-sally}!ut-ngp!auscso!knutson6 "I see everything once!" -- Catch-22 --- John Knutson {ihnp4,allegra,ut-sally}!ut-ngp!auscso!knutson6 "I see everything once!" -- Catch-22 -- Michael Lodman (619) 485-3335 Advanced Development NCR Corporation E&M San Diego mike.lodman@ivory.SanDiego.NCR.COM {sdcsvax,cbatt,dcdwest,nosc.ARPA,ihnp4}!ncr-sd!ivory!lodman
dmt@mtunb.ATT.COM (Dave Tutelman) (08/04/87)
In article <1013@vi.ri.cmu.edu> jfb@vi.ri.cmu.edu.UUCP (John Brennan) writes: >Personally, I've never >had a real desire to know what argv[0] is. MS-DOS doesn't support links >(hard or symbolic), so each file has a unique name. But I can see where >you might want to be really friendly or something, and parse argv[0]. When I write a command-line program (non-intreractive, options given on the command line), I include lots of error messages in the code. As the programmer, I KNOW what the program is called, and SHOULD ALWAYS be called :-) But the USER can call the program anything, and invoke it by whatever filename he/she decides to store it as. I've chosen to deal with that dichotomy by parsing argv[0] into a string variable "progname", and using that in error messages wherever I report the program's name. ("The user is always right.") +---------------------------------------------------------------+ | Dave Tutelman | | Physical - AT&T - Lincroft, NJ | | Logical - ...ihnp4!mtuxo!mtunb!dmt | | Audible - (201) 576 2442 | +---------------------------------------------------------------+
allbery@ncoast.UUCP (Brandon Allbery) (08/07/87)
As quoted from <1013@vi.ri.cmu.edu> by jfb@vi.ri.cmu.edu (John Brennen): +--------------- | In article <1690@bellcore.bellcore.com>, tr@wind.bellcore.com (tom reingold) writes: | > I just discovered an undocumented feature of the more recent | > versions of DOS: it is at last possible to obtain argv[0]. | | Supposedly, this was considered a problem in DOS 2.0. Personally, I've never | had a real desire to know what argv[0] is. MS-DOS doesn't support links | (hard or symbolic), so each file has a unique name. But I can see where | you might want to be really friendly or something, and parse argv[0]. +--------------- Actually, it sometimes _is_ useful. I once tried to cobble together a "ps" utility to tell me how deeply I was nested in sub-COMMAND.COM's; I have a nasty tendency, under both UNIX and MS-DOS, to spawn sub-"shells" and then forget that I'm in a subshell. Under UNIX, I run csh, so I use an environ- ment variable and update it in cshrc; the result is a prompt that tells me where I am: % coesys sh COEsys@2 % unixstat UNIX|STAT@3 % ^D COEsys@2 % ^D % ^D login: Needless to say, this is a royal pain under DOS 2.11 (3.x's floppy access is way too slow for my tastes); so I tried to write a program to print out the commands (or, under 2.x, the PSP addresses) of nested processes. Not perfect, but at least I could check the nesting depth. BTW, I was finally stopped because, while I found two addresses in the PSP which apparently pointed back to the parent process's PSP, I couldn't find the way to stop it at the boot COMMAND.COM. I suppose it's possible that searching for the segment address of the beginning of the PSP was wrong. That or it is correct for the boot COMMAND.COM to point to itself -- except that an explicit check for this didn't stop the looping. Can anyone tell me what address in the PSP contains a pointer to the parent PSP, and what form the pointer takes (i.e. fully-qualified address of start of PSP, segment address of back-pointer in parent PSP, etc.)? Better yet, can anyone mail me a reasonably complete description of the various items in a PSP? Thanks in advance. -- Brandon S. Allbery, moderator of comp.sources.misc and comp.binaries.ibm.pc {{harvard,mit-eddie}!necntc,well!hoptoad,sun!cwruecmp!hal}!ncoast!allbery ARPA: necntc!ncoast!allbery@harvard.harvard.edu Fido: 157/502 MCI: BALLBERY <<ncoast Public Access UNIX: +1 216 781 6201 24hrs. 300/1200/2400 baud>>