/*
 * Das Environment Programm fr den ATARI ST
 *
 * Initialisieren, Ver„ndern und L”schen von Environment Variablen
 * Es wird die Systemvariable THE_ENV benutzt.
 *
 * Das Initialiseren des Environment-Bereichs l„uft leider nicht im
 * AUTO-Ordner, das sich das Desktop (bzw. das AES) den alten Environment-
 * Bereich berschreibt, daher erst nach Starten des Desktop's verwenden.
 *
 * Es werden 'multiple character constants' verwendet, d.h. 'XBRA' ist als
 * Integer-Wert zul„ssig, daher bei Lattice mit -cm compilieren.
 *
 * (C) 8.1.1989 Ralf Meister
 * Version mit Zeilen-Verl„ngerung durch ein \ am Ende der Zeile
 * (C) 1.2.1989 Ralf Meister
 * Version mit lesbarer XBRA Kennung
 * (C) 23.9.1989 Ralf Meister
 */


#include  "portab.h"
#include  "osbind.h"
#include  "stdio.h"
#include  "stdlib.h"

/* Begrenze die Speicheranforderung des Programms, Lattice spezifisch */
long _MNEED = 0x100;


#define THE_ENV 0x4be       /* Systemvariable THE_ENV */

#define MAGIC 'RMEN'        /* Flag zum Erkennen der richtigen Version */

#define MIN_SIZE 0x100      /* Minimale Gr”že des Environments, hier 1/4k */
#define DEF_SIZE 0x400      /* Default Gr”že hier 1k */


#define MAX_STR 120     /* Maximale L„nge eines Strings */
#define MAX_NAM 32      /* Maximale L„nge eines Namens  */

/* Definition der Struktur der Basepage, nur die erforderlichen Eintr„ge */
typedef struct pd PD;

struct pd {
    char    res1[36];   /* Bereich, der uns nicht interessiert  */
    PD      *p_parent;  /* Zeiger auf Basepage des Vater's      */
    long    res2;
    char    *p_env;     /* Zeiger auf Environment Bereich       */
    char    res3[80];
    char    p_cmdlin[128];
    };


/* Struktur vor dem Speicherblock des Environment Bereiches, damit man ber-
   prfen kann, ob die Environment Variablen von diesem Programm stammen */
typedef struct envblock ENVBLOCK;
struct envblock {
    int     size;       /* Gr”že des reservierten Speichers */
    int     xbra;       /* die Konstante 'XBRA' */
    int     magic;      /* Kontrollwert */
    int     old_vec;    /* Alte Adresse hier merken */
    };

/* Definition von Datenstrukturen zum Speichern der verschiedenen Aufgaben */
typedef enum { NOTHING,PRINT,DELETE,CREATE,KILL,SET,SETFILE } KIND;
typedef struct {
    KIND    kind;   /* Art der Aufgabe */
    char    *arg;   /* Eventuelle Parameter */
    }   TASK;
#define MAX_TASK    5   /* Maximale Anzahl von Aufgaben */


/* Globale Variablen */
int waitforkey=FALSE; /* Warten auf Tastendruck nach Beenden des Programms */
int help=FALSE;       /* Flag fr Hilfe */
int res=FALSE;        /* Flag fr resident halten */
int size=DEF_SIZE;    /* Default Gr”že des Bereichs, hier 2k */

/* die zu erledigenden Aufgaben */
TASK task_list[MAX_TASK];/* TRUE verschiedene Eintr„ge sollten reichen */
int task_nr=0;



/* Ausgeben einer Fehlermeldung auf stderr und ggfs. warten auf Taste */
void error(str)
char *str;
{
    fprintf(stderr,"%s\n",str);
    if(waitforkey) while(!kbhit());
    exit(1);
}



/* Reserviere den Speicher am Ende des verfgbaren Speichers */
char *get_mem(size)
int size;
{
    int length;
    char *ptr,*dummy;
    if( (length=Malloc(-1)) > size )
        if( (dummy=(char *)Malloc(length-size)) > 0)
            if( (ptr=(char *)Malloc(size)) > 0 )
            {
                Mfree(dummy);
                return(ptr);
            };
    error("No more memory");
    return NULL;
}

/* Bilde einen Environment Bereich, mit/ohne šberschreiben des alten */
void create(arg,del)
char *arg;
int del;
{
    int stack;
    char *old_env;
    register char *env;
    register ENVBLOCK *block;
    register PD *basepage;
    extern PD *_pbase;  /* Die Adresse unserer Basepage */
    /* Stelle die Gr”že des zu benutzenden Speichers fest */
    if(arg!=NULL && *arg!='\0') /* Wenn kein Parameter, dann default */
    {
        size=atoi(arg);
        if(size<MIN_SIZE) size=MIN_SIZE;
    }
    /* Hole dir die Adresse des alten Wertes */
    stack=Super(0); /* In den Supervisormodus gehen */
    old_env=*(char **)THE_ENV;  /* Hole den Zeiger auf das Environment */
    Super(stack);   /* Wieder in den Usermode gehen */
    if(!del)    /* wenn nicht berschrieben werden soll */
        if(old_env!=NULL) error("THE_ENV already used");
    env=get_mem(size+sizeof(ENVBLOCK));  /* reserviere Speicher+Block */
    block=(ENVBLOCK *)env;
    block->magic=MAGIC;         /* Setze den Prfwert */
    block->size=size;           /* Und die Gr”že des Environments */
    block->xbra='XBRA';         /* Den Wert fr 'XBRA' setzen */
    block->old_vec=(int)old_env;/* und den alten Pointer setzen */
    env+=sizeof(ENVBLOCK);      /* Zeige auf Datenbereich */
    stack=Super(0);             /* In den Supervisormodus gehen */
    *(char **)THE_ENV=env;      /* Den Pointer setzen */
    Super(stack);               /* wieder in den Usermode gehen */
    *env='\0';                  /* Nock keine Variablen gespeichert */
    res=TRUE;                   /* Flag fr resident lassen setzen */

    /* Den Zeiger in allen vorhergehenden Basepage'en setzen */
    basepage=_pbase;
    while(basepage!=NULL)
    {
        basepage->p_env=env;
        basepage=basepage->p_parent;
    }

    printf("Allocate %d Bytes for environment\n",size);
}


/* Hole die Adresse, auf die THE_ENV zeigt */
char *get_env()
{
    int stack;
    register char *env;
    register ENVBLOCK *block;
    stack=Super(0);     /* In den Supervisor-Modus gehen */
    env=*(char **)THE_ENV;
    Super(stack);       /* wieder in den User-Mode gehen */
    if(env==NULL) error("No valid environment");
    block=(ENVBLOCK *)(env-sizeof(ENVBLOCK));/* Den Zeiger auf Block setzen */
    if( block->magic != MAGIC ) error("No valid environment");
    return(env);
}


/* Ausgeben der Environment Variablen, auf die THE_ENV zeigt */
void print()
{
    char *env;
    env=get_env();
    puts("List of environment variables:");
    while(*env)     /* Wenn der erste Buchstabe = 0 ende */
    {
        while(*env) /* sonst solange die Buchstaben != 0 */
            putchar(*env++);    /* ausgeben */
        putchar('\n');  /* Fr jede Variable eine neue Zeile */
        env++;          /* Hinter das Variablenende lesen */
    }
    putchar('\n');
}

/* Hole Namen einer Variablen */
void split(name,str)
char *name,*str;
{
    register char *ptr1,*ptr2;
    ptr1=str;
    ptr2=name;
    while(*ptr1 && *ptr1!='=' && ptr2-name<MAX_NAM) *ptr2++=*ptr1++;
    *ptr2='\0';
}

/* L”sche den String ptr durch berschreiben */
void copy(ptr)
register char *ptr;
{
    register char *ptr2;
    ptr2=ptr;
    while(*ptr2++); /* Das Ende des zu l”schenden Strings holen */
    while(*ptr2)    /* Die anderen Variablen berkopieren */
        while((*ptr++=*ptr2++));
    *ptr++='\0';    /* Die Kennung fr Ende der Variablen setzen */
}

/* L”schen der Variablen str */
void delete(str,env)
char *str,*env;
{
    register char *ptr1;
    char name[MAX_NAM];
    if(str==NULL) error("No variable to delete");
    if(*str=='\0') error("No variable to delete");
    ptr1=env;
    while(*ptr1)    /* Solange nicht Ende der Variablen */
    {
        split(name,ptr1);
        if(!strcmp(name,str))/* Diese Variable l”schen */
        {
            copy(ptr1);     /* L”sche durch berschreiben */
            return;         /* Und dann raus aus diesem Programm */
        }
        while(*ptr1++);     /* Lese bis hinter den String */
    }
}

/* Einfgen einer Variablen in den Environment Bereich, die Form des Strings
   muž <var>=<value> oder <var> sein */
void add(str,env)
char *str,*env;
{
    char name[MAX_NAM];
    ENVBLOCK *block;
    register char *ptr;
    split(name,str);    /* Hole dir den Namen */
    delete(name,env);   /* Die alte Variable l”schen */
    block=(ENVBLOCK *)(env-sizeof(ENVBLOCK));
    ptr=env;            /* Das Ende der Variablen suchen */
    while(*ptr)
        while(*ptr++);
    /* Jetzt zeigt *ptr auf das abschliežende '\0' */
    /* Prfe, ob genug Speicher da ist (+2 fr "\0\0" am Schluž) */
    if(ptr-env+strlen(str)+2>block->size)
        error("Environment space too small");
    while((*ptr++=*str++));
    *ptr++='\0';
}

/* Teste, ob ein Character in einer Liste von Buchstaben vorhanden ist */
int in(ch,str)
register char ch,*str;
{
    while(*str)
        if(ch==*str++) return TRUE;
    return FALSE;
}

/* Die Datei einlesen und die standard Variablen aus dem File setzen */
void set_file(fname)
char *fname;
{
    FILE *fh;
    char read_buffer[MAX_STR],buffer[4*MAX_STR];    /* Grožer Puffer */
    register char *ptr1,*ptr2;
    char *env;
    if( (fh=fopen(fname,"ra"))==NULL)
        error("Can't open file");
    env=get_env();
    while(fgets(read_buffer,MAX_STR,fh)!=NULL)
    {
        ptr1=read_buffer;   /* die Zeiger zum Durchsuchen setzen */
        ptr2=buffer;
        /* Kommentarzeilen berlesen */
        if(in(*ptr1,"*#;%+!&$\n")) continue;
        do
            *ptr2++=*ptr1++;
        while(!in(*ptr1," =\n\t") && *ptr1);  /* bis Tokenende lesen */
        if(*ptr1!='\n' && *ptr1)     /* šberlese Blanks und TABS */
            while(in(*ptr1," \t")) ptr1++;
        if(*ptr1=='=')                          /* Also Form var=value */
        {
            while(*ptr1!='\n' && *ptr1)  /* Kopiere den Rest der Zeile */
                *ptr2++=*ptr1++;
            /* Geht die Zeile weiter (erkennt man an '\\\n' ?? */
            while(*(ptr1-1)=='\\' && *ptr1=='\n')
            {   /* Lese neue Zeile ein un kopiere weiter */
                if(fgets(read_buffer,MAX_STR,fh)==NULL)
                    error("Unexpected end of file");
                ptr2--;             /* Lasse '\\\n' weg */
                ptr1=read_buffer;   /* Den ptr1 auf neue Zeile setzen */
                while(*ptr1!='\n' && *ptr1)
                {
                    if(ptr2-buffer>=4*MAX_STR)
                        error("Too long environment variable");
                    *ptr2++=*ptr1++;
                }
            }
        }
        /* Das Stringende - Zeichen hinzufgen */
        *ptr2++='\0';
        add(buffer,env);
    }
}

/* Eine kleine Help-Meldung auf dem Bildschirm zeigen */
void usage()
{
    puts("ENVIRON [-w][-h][-p][-f <file>][-s <var>][-d <var>]\
[-c <size>][-k <size>] <arg>");
    puts(" h print this tiny help message");
    puts(" w wait for key before terminating");
    puts(" p print the environment variables");
    puts(" f set variables from <file>");
    puts(" s set variable <var>");
    puts(" d delete variable <var>");
    puts(" c create environment space without overwriting exiting one");
    puts(" k kill old environment space and create new");
    puts("\n <arg> set variable <arg>");
    puts(" no <arg>   same as -p");
    help=TRUE;
}

/* Das Hauptprogramm : untersuche Parameterzeile und verteile die Arbeit */
void main(argc,argv)
int  argc;
char *argv[];
{
    int next;
    char *odata,option;
    char *env;
    int i;
    puts("ENVIRON  environment utility  V1.4\n(C) Ralf Meister 1989\n");
    for(next=1;(odata=argopt(argc,argv,"sSfFcCkKDD",&next,&option))!=NULL;)
    {
        if(task_nr==MAX_TASK) error("Too many different tasks");
        switch(option)
        {
        case 'h':
        case 'H': usage();
                  break;
        case 'w':
        case 'W': waitforkey=TRUE;
                  break;
        case 'd':
        case 'D': task_list[task_nr].kind=DELETE;
                  task_list[task_nr++].arg=odata;
                  break;
        case 's':
        case 'S': task_list[task_nr].kind=SET;
                  task_list[task_nr++].arg=odata;
                  break;
        case 'f':
        case 'F': task_list[task_nr].kind=SETFILE;
                  task_list[task_nr++].arg=odata;
                  break;
        case 'p':
        case 'P': task_list[task_nr].kind=PRINT;
                  task_list[task_nr++].arg=NULL;
                  break;
        case 'c':
        case 'C': task_list[task_nr].kind=CREATE;
                  task_list[task_nr++].arg=odata;
                  break;
        case 'k':
        case 'K': task_list[task_nr].kind=KILL;
                  task_list[task_nr++].arg=odata;
                  break;
        default : fprintf(stderr,"Invalid option %c\n",option);
                  usage();
                  error("");
        }
    }
    /* Wenn keine Hilfe notwendig war */
    if(!help)
    {
        /* Die Aufgaben der Reihe nach ausfhren */
        for(i=0;i<task_nr;i++)
            switch(task_list[i].kind)
            {
            case PRINT: print();
                        break;
            case DELETE : env=get_env();
                        delete(task_list[i].arg,env);
                        break;
            case CREATE : create(task_list[i].arg,FALSE); /* ohne l”schen */
                        break;
            case KILL : create(task_list[i].arg,TRUE); /* mit l”schen */
                        break;
            case SET :  env=get_env();
                        add(task_list[i].arg,env);
                        break;
            case SETFILE: set_file(task_list[i].arg);
                        break;
            default:error("Should not occur");
            }
        /* Keine Aufgaben und keine Variablen zu setzen -> Ausdrucken */
        if(next==argc && task_nr==0)
            print();
        /* Ansonsten die Variablen der Parameterzeile setzen */
        if(next<argc)
        {
            env=get_env();
            for(;next<argc;next++)
                add(argv[next],env);
        }
    }
    if(waitforkey) while (!kbhit());

    /* Ggfs. Programm freigeben,aber nicht reservierten Speicher */
    if(res) Ptermres(0,0);
}

