
unit JRS_EnvU ;

{ ENVIRON by JRS, >=2002-11-23
  Used by NOWMINUS, ENVICALC.  Tested for DOS mode.
  Set or clear an ancestral environment variable.
  Name part of input is made UPPER CASE.
  Status values:
  0 = OK
  1 = No environment found
  2 = Empty environment found
  3 = No environment terminator found
  4 = Insufficient space for change (also: WinNT)
  5 = DEBUG problem
  Note that this affects only one specified environment, Gen deep.
  Ref: MSDOS Bible

  DOS mode (not DPMI/Windows; gave GPF there; JRS_EnvD might be worked on
  Fails harmlessly in Windows NT)
  Limited environment handling, /in lieu/ of TSUntEnv.
  The unit does NOT expand any environment.
  Written with assistance from TS and others in news:c.l.p.b.
  This seems OK for DOS, Win3, Win98;
  but direct environment access may not be allowed in Windows NT. }


interface

type TGetPut = (Get, Put) ;

const EnvironNameWillBeMadeUpperCase : boolean = true ;

procedure Environ(const Ndeep : word ; const RW : TGetPut ;
  EnvNam : string ; var EnvVal : string ; var Status : byte) ;

function GetPSPseg_Ndeep(const Ndeep : word) : word ;

procedure GetEnvDescNdeep(const Ndeep : integer ; var Segt, Size : word) ;

{$IFDEF JRS}
procedure ResizeEnvtNdeep(const Ndeep : integer ; const NewSize : word ;
  var Status : byte) (* DO NOT USE *) ;

procedure ValidateEnvtNdeep ;
{$ENDIF}


implementation

uses Dos ;

type Pword = ^word ;

function GetChar(const Seg, Off : word) : char ;
begin GetChar := Pchar(Ptr(Seg, Off))^ end {GetChar} ;

function GetWord(const Seg, Off : word) : word ;
begin GetWord := Pword(Ptr(Seg, Off))^ end {GetWord} ;

function GetEnvSeg(const PSPsg : word) : word
  { PSP word 2Ch is Seg of Env } ;
begin GetEnvSeg := GetWord(PSPsg, $2C) end {GetEnvSeg} ;

function GetParentSeg(const PSPsg : word) : word
  { PSP word 16h is Seg of Parent } ;
begin GetParentSeg := GetWord(PSPsg, $16) end {GetParentSeg} ;

function GetPSPseg_Ndeep(const Ndeep : word) : word { consider Ndeep=0 } ;
var PSPseg : word ; J : word ;
begin
  PSPseg := PrefixSeg ;
  for J := 1 to Ndeep do PSPseg := GetParentSeg(PSPseg) ;
  GetPSPseg_Ndeep := PSPseg end {GetPSPseg_Ndeep} ;


{ It appears that Ndeep=8 is considered sufficient to reach the deepest.
  With a depth of 0, it sets the program's own environment, given room. }


{ MCB : type-char, owner-word, paragraphs-word, other-11-bytes }

procedure GetEnvDescNdeep(const Ndeep : integer ; var Segt, Size : word) ;
var DosVer, PSPseg : word ;
begin Size := 0;

  PSPseg := GetPSPseg_Ndeep(Ndeep) ;

  if PSPseg=0 then EXIT ;

  Segt := GetEnvSeg(PSPseg) ;

  DosVer := Swap(DosVersion) ;
  if (Segt = 0) or ((DosVer >= $0320) and (DosVer < $0330)) then
    Segt := Succ(PSPseg + GetWord(Pred(PSPseg), $3)) ;

  if GetWord(Pred(Segt), $1) <> PSPseg then EXIT ;
  Size := $10 * GetWord(Pred(Segt), $3) ;
  end {GetEnvDescNdeep} ;


procedure ResizeEnvtNdeep(const Ndeep : integer ; const NewSize : word ;
  var Status : byte) (* DO NOT USE *) ;
var EnvSeg, EnvSize, NewSeg, Paras : word ; Stts : byte ;
begin Stts := 0 ;
  GetEnvDescNdeep(Ndeep, EnvSeg, EnvSize) ;

  { Get new block }
  Paras := Succ(Pred(NewSize) div $10) ;
  asm  mov BX,[Paras] ; mov AH,$48 ; int $21 ; jb @1 {jump if bad }
      mov [NewSeg],AX ; jmp @2 ;
    @1: mov [Stts],AL ;
    @2:
      end ;
  Status := Stts ; if Status>0 then EXIT ;
  Writeln('NewSeg=', NewSeg) ;

  { Move data }
  Move(Ptr(EnvSeg, 0)^, Ptr(NewSeg, 0)^, NewSize) ;

  { Change official pointer }
  PWord(Ptr(GetPSPseg_Ndeep(Ndeep), $2C))^ := NewSeg ;

  { Return old block int 21/49 }
  asm  mov ES,[EnvSeg] ; mov AH,$49 ; int $21 ; jb @1 {jump if bad }
      jmp @2 ;
    @1: mov [Stts],AL ;
    @2:
      end ;
  Status := Stts ;
  end { ResizeEnvtNDeep} ;


procedure ValidateEnvtNdeep ; begin end ;

{ Use RW=Put with an empty EnvVal to delete EnvNam from Environment }

procedure Environ(const Ndeep : word ; const RW : TGetPut ;
  EnvNam : string ; var EnvVal : string ; var Status : byte) ;
var EnvSeg, EnvOff, EnvSize, EnvUsed,
  FoundBeg, { will be offset of first char of entry }
  FoundEnd, { will be offset of terminator of entry }
  FoundLen, { will be length of the completed entry }
  OldBeg, ValBeg : word ;
Found, Maybe, AddNew : boolean ;
Ch, xCh : char ; J : byte ;
{} {$IFDEF DEBUG}
{} W : word ;
{} Dummy : Pchar ;
{} const SDum : word = 0 ;
{} {$ENDIF}
begin
  Status := 0 ;
  GetEnvDescNdeep(Ndeep, EnvSeg, EnvSize) ;
  if EnvSeg = 0 then begin Status := 1 ; EXIT end ;
  if EnvSize = 0 then begin
    if RW=Get then EnvVal := '$0' ;
    Status := 2 ; EXIT end ;

  {} {$IFDEF DEBUG Use dummy environment for test ; assume real one < 24K }
  {} if SDum = 0 then begin
    {} GetMem(Dummy, 32768) { Offset expected small but not 0 } ;
    {} SDum := Seg(Dummy) ; EnvOff := Ofs(Dummy) ;
    {} if EnvOff+EnvSize>32750 then begin Status := 5 ; EXIT end ;
    {} Dec(SDum, (EnvOff div 16)+1) { minor bodge } ;
    {} Move(Ptr(EnvSeg, 0)^, Ptr(SDum, 0)^, EnvSize) ;
    {} end ;
  {} EnvSeg := SDum ;
  {} {$ENDIF}

  if EnvironNameWillBeMadeUpperCase then
    for J := 1 to Length(EnvNam) do EnvNam[J] := UpCase(EnvNam[J]) ;

  EnvNam := EnvNam + '=' ;

  EnvOff := 0 ;
  Maybe := true ;
  Found := false ;
  FoundBeg := 0 ;
  FoundEnd := 0 ;
  ValBeg := 0 ;
  Ch := #255 ;
  J := 0 ;
  OldBeg := 0 ;
  repeat { Scan Environment for EnvVar entry location }
    if EnvOff = EnvSize then begin Status := 3 ; EXIT end ;
    xCh := Ch ; Ch := GetChar(EnvSeg, EnvOff) ; Inc(J) ;
    {} {$IFDEF DEBUG Show } if Ch=#0 then Writeln else Write(Ch) ; {$ENDIF}
    if Ch<>EnvNam[J] then Maybe := false ;
    if Maybe and (Ch='=') then begin ValBeg := EnvOff+1 ; Found := true end ;
    if Ch=#0 then begin
      if Found then begin FoundBeg := OldBeg ; FoundEnd := EnvOff end ;
      Found := false ; Maybe := true ; J := 0 ; OldBeg := Succ(EnvOff) end ;
    Inc(EnvOff) until (Ch=#0) and (xCh=#0) ;

  EnvUsed := EnvOff ;

  (*
    Writeln('@@@') ;
    for W := 0 to 60 do Write(GetChar(EnvSeg, EnvOff+W)) ;
    Writeln(' @@@') ;
    *)

  {} {$IFDEF DEBUG Report }
  {} Writeln('*** EnvUsed = ', EnvUsed,
    {} '  FoundBeg = ', FoundBeg, '  FoundEnd = ', FoundEnd ) ;
  {} if FoundEnd > 0 then begin
    {} Write('*** Located "') ;
    {} for J := FoundBeg to FoundEnd-1 do Write(GetChar(EnvSeg, J)) ;
    {} Writeln('"') end ;
  {} {$ENDIF}

  if RW=Get then begin
    if FoundEnd=0 then EnvVal := '' else begin
      EnvVal[0] := char(FoundEnd-ValBeg) ;
      Move(Ptr(EnvSeg, ValBeg)^, EnvVal[1], byte(EnvVal[0])) ;
      end ;
    {} {$IFDEF DEBUG}
    {} Writeln('*** Get: Returning "', EnvVal, '" for "', EnvNam, '"') ;
    {} {$ENDIF}
    EXIT end {Get} ;


  AddNew := EnvVal>'' ;

  if FoundEnd = 0 then FoundLen := 0 else FoundLen := FoundEnd+1 - FoundBeg ;

  if AddNew then { ADD : check for fit }
    if EnvUsed - FoundLen + Length(EnvNam) + Length(EnvVal) + 2 > EnvSize then
    begin Status := 4 ; EXIT end ;

  if FoundEnd > 0 then { Slide entries down over one to be removed/replaced }
    Move(Ptr(EnvSeg, FoundEnd+1)^, Ptr(EnvSeg, FoundEnd+1-FoundLen)^,
    EnvUsed-FoundEnd) ;

  if AddNew then begin { Add new entry }
    Move(EnvNam[1],
      Ptr(EnvSeg, EnvUsed-FoundLen-1)^, Length(EnvNam)) ;
    Move(EnvVal[1],
      Ptr(EnvSeg, Length(EnvNam)+EnvUsed-FoundLen-1)^, Length(EnvVal)) ;
    Pword(Ptr(EnvSeg,
      Length(EnvNam)+Length(EnvVal)+EnvUsed-FoundLen-1))^ := 00 ;
    end ;

  {} {$IFDEF DEBUG Show Environment }
  {} EnvOff := 0 ;
  {} Ch := #255 ;
  {} repeat
    {} xCh := Ch ; Ch := GetChar(EnvSeg, EnvOff) ;
    {} if Ch=#0 then Writeln('#0') else Write(Ch) ;
    {} Inc(EnvOff) until (Ch=#0) and (xCh=#0) ;
  {} {$ENDIF}

  end {Environ} ;



BEGIN ;
END.
