[comp.lang.pascal] Environment variables in Turbo Pascal

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>>