/* 
MSTENVP.C 
test bed for different methods of finding the master environment

cl -qc mstenvp.c
*/

#include <stdlib.h>
#include <dos.h>

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long ULONG;
typedef void far *FP;

#ifndef MK_FP
#define MK_FP(seg,ofs)  ((FP)(((ULONG)(seg) << 16) | (ofs)))
#endif

#ifdef __TURBOC__
#define ASM asm
#else
#define ASM _asm
#endif

/**********************************************************************/

#pragma pack(1)

typedef struct {
    BYTE type;          /* 'M'=in chain; 'Z'=at end */
    WORD owner;         /* PSP of the owner */
    WORD size;          /* in 16-byte paragraphs */
    BYTE unused[3];
    BYTE dos4[8];
    } MCB;

MCB far *get_mcb(void)
{
    ASM mov ah, 52h
    ASM int 21h
    ASM mov dx, es:[bx-2]
    ASM xor ax, ax
    /* in both Microsoft C and Turbo C, far* returned in DX:AX */
}

#define MCB_FM_SEG(seg)     ((seg) - 1)
#define IS_PSP(mcb)         (FP_SEG(mcb) + 1 == (mcb)->owner)
#define ENV_FM_PSP(psp_seg) (*((WORD far *) MK_FP(psp_seg, 0x2c)))
#define PARENT(psp_seg)     (*((WORD far *) MK_FP(psp_seg, 0x16)))

char far *env(MCB far *mcb)
{
    char far *e;
    unsigned env_mcb;
    unsigned env_owner;
    
    if (! IS_PSP(mcb))
        return (char far *) 0;
    
    e = MK_FP(ENV_FM_PSP(mcb->owner), 0);
    env_mcb = MCB_FM_SEG(FP_SEG(e));
    env_owner = ((MCB far *) MK_FP(env_mcb, 0))->owner;
    return (env_owner == mcb->owner) ? e : (char far *) 0;
}

typedef enum { FALSE, TRUE } BOOL;
typedef BOOL (*WALKFUNC)(MCB far *mcb);

/*
    General-purpose MCB walker.
    The second parameter to walk() is a function that expects an 
    MCB pointer, and that returns TRUE to indicate that walk()
    should keep going, and FALSE to indicate that walk() should
    stop.
*/
BOOL walk(MCB far *mcb, WALKFUNC walker)
{
    for (;;)
        switch (mcb->type)
        {
            case 'M':
                if (! walker(mcb))
                    return FALSE;
                mcb = MK_FP(FP_SEG(mcb) + mcb->size + 1, 0);
                break;
            case 'Z':
                return walker(mcb);
                break;
            default:
                return FALSE;   /* error in MCB chain! */
        }
}

/**********************************************************************/
    
/* using the GETVECT 2Eh technique (ENVPKG.ASM) */
void far *mstenvp1(void)
{
    ASM mov ax, 352eh       /* get INT 2Eh vector */
    ASM int 21h
    ASM mov dx, es:[002Ch]  /* environment segment */
    ASM xor ax, ax          /* return far ptr in DX:AX */
}

/**********************************************************************/

/* walk MCB chain, looking for very first environment */
void far *env2 = (void far *) 0;

BOOL walk2(MCB far *mcb)
{
    if (env2 = env(mcb))
    {
        unsigned psp = FP_SEG(mcb)+1;
        return (PARENT(psp) == psp) ? FALSE /*found it!*/ : TRUE;
    }
    else
        return TRUE;    /* keep going */
}

void far *mstenvp2(void)
{
    walk(get_mcb(), walk2);
    return env2;
}

/**********************************************************************/

/* walk MCB chain, looking for very LAST env addr > PSP addr */
void far *env3 = (void far *) 0;

#define NORMALIZE(fp)   (FP_SEG(fp) + (FP_OFF(fp) >> 4))

BOOL walk3(MCB far *mcb)
{
    void far *fp;
    /* if env seg at greater address than PSP, then
       candidate for master env -- we'll take last */
    if (fp = env(mcb))
        if (NORMALIZE(fp) > (FP_SEG(mcb)+1))
            env3 = fp;
    return TRUE;
}

void far *mstenvp3(void)
{
    walk(get_mcb(), walk3);
    return env3;
}

/**********************************************************************/

/* walk MCB chain, looking for very first environment belonging
   to PSP which is its own parent */
void far *env4 = (void far *) 0;

#define PARENT(psp_seg)     (*((WORD far *) MK_FP(psp_seg, 0x16)))

BOOL walk4(MCB far *mcb)
{
    if (env4 = env(mcb))
    {
        unsigned psp = FP_SEG(mcb) + 1;
        return (PARENT(psp) == psp) ? FALSE /*found it!*/ : TRUE;
    }
    else
        return TRUE;    /* keep going */
}

void far *mstenvp4(void)
{
    walk(get_mcb(), walk4);
    return env4;
}

/**********************************************************************/

main()
{
    printf("GETVECT 2Eh method; mstenvp1   = %Fp\n", mstenvp1()); 
    printf("WALK MCB method; mstenvp2      = %Fp\n", mstenvp2());
    printf("WALK MCB/LAST method; mstenvp3 = %Fp\n", mstenvp3());
    printf("WALK MCB/OWN PARENT; mstenvp4  = %Fp\n", mstenvp4());
}
