
/*
 * SUB.C
 *
 * (C)Copyright 1987 Matthew Dillon,    All rights reserved
 *
 *      Subroutines used throughout the shell (not very descriptive, am I?)
 */


#include <exec/types.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include "shell.h"

#define HM_STR 0              /* various HISTORY retrieval modes */
#define HM_REL 1
#define HM_ABS 2

extern struct FileLock *Lock(), *DupLock(), *CurrentDir();
extern struct FileLock *Clock;

seterr()
{
    char buf[32];
    int stat;

    sprintf(buf, "%ld", Lastresult);
    set_var(LEVEL_SET, V_LASTERR, buf);
    stat = atoi(get_var(LEVEL_SET, V_STAT));
    if (stat < Lastresult)
        stat = Lastresult;
    sprintf(buf, "%ld", stat);
    set_var(LEVEL_SET, V_STAT, buf);
}


char *
next_word(str)
register char *str;
{
    while (*str  &&  *str != ' '  &&  *str != 9)
        ++str;
    while (*str  && (*str == ' ' || *str == 9))
        ++str;
    return (str);
}


char *
compile_av(av, start, end)
short start, end;
char **av;
{
    char *cstr;
    short i, len;

    len = 0;
    for (i = start; i < end; ++i)
        len += strlen(av[i]) + 1;
    cstr = malloc(len + 1);
    *cstr = '\0';
    for (i = start; i < end; ++i) {
        strcat (cstr, av[i]);
        strcat (cstr, " ");
    }
    return (cstr);
}

/*
 * FREE(ptr)   --frees without actually freeing, so the data is still good
 *               immediately after the free.
 */


Free(ptr)
char *ptr;
{
    static char *old_ptr;

    if (old_ptr)
        free (old_ptr);
    old_ptr = ptr;
}

/*
 * Add new string to history (H_head, H_tail, H_len,
 *  S_histlen
 */

add_history(str)
char *str;
{
    register struct HIST *hist;

    while (H_len > S_histlen)
        del_history();
    hist = (struct HIST *)malloc (sizeof(struct HIST));
    if (H_head == NULL) {
        H_head = H_tail = hist;
        hist->next = NULL;
    } else {
        hist->next = H_head;
        H_head->prev = hist;
        H_head = hist;
    }
    hist->prev = NULL;
    hist->line = malloc (strlen(str) + 1);
    strcpy (hist->line, str);
    ++H_len;
}

del_history()
{
    if (H_tail) {
        --H_len;
        ++H_tail_base;
        free (H_tail->line);
        if (H_tail->prev) {
            H_tail = H_tail->prev;
            free (H_tail->next);
            H_tail->next = NULL;
        } else {
            free (H_tail);
            H_tail = H_head = NULL;
        }
    }
}


char *
last_history_entry()
{
    if (H_head)
        return(H_head->line);
    return("");
}


char *
get_history(ptr)
char *ptr;
{
    register struct HIST *hist;
    register short len;
    short mode = HM_REL;
    long num  = 1;
    char *str;
    char *result = NULL;

    if (ptr[1] >= '0' && ptr[1] <= '9') {
        mode = HM_ABS;
        num  = atoi(&ptr[1]);
        goto skip;
    }
    switch (ptr[1]) {
    case '!':
        break;
    case '-':
        num += atoi(&ptr[2]);
        break;
    default:
        mode = HM_STR;
        str  = ptr + 1;
        break;
    }
skip:
    switch (mode) {
    case HM_STR:
        len = strlen(str);
        for (hist = H_head; hist; hist = hist->next) {
            if (strncmp(hist->line, str, len) == 0 && *hist->line != '!') {
                result = hist->line;
                break;
            }
        }
        break;
    case HM_REL:
        for (hist = H_head; hist && num--; hist = hist->next);
        if (hist)
            result = hist->line;
        break;
    case HM_ABS:
        len = H_tail_base;
        for (hist = H_tail; hist && len != num; hist = hist->prev, ++len);
        if (hist)
            result = hist->line;
        break;
    }
    if (result) {
        Eputs (result);
        return(result);
    }
    Eputs ("History substitution failed");
    return ("");
}


replace_head(str)
char *str;
{
    if (str == NULL)
        str = "";
    if (H_head) {
        free (H_head->line);
        H_head->line = malloc (strlen(str)+1);
        strcpy (H_head->line, str);
    }
}

perror(str)
char *str;
{
    ierror(str, IoErr());
}

ierror(str, err)
register char *str;
short err;
{
    register struct PERROR *per = Perror;

    if (err) {
        for (; per->errstr; ++per) {
            if (per->errnum == err) {
                fhprintf (Cerr, "%s%s%s\n",
                    per->errstr,
                    (str) ? ": " : "",
                    (str) ? str : ""
                );
                return(err);
            }
        }
        fhprintf (Cerr, "Unknown DOS error %ld %s\n", err, (str) ? str : "");
   }
   return (err);
}

/*
 * Disk directory routines
 *
 * dptr = dopen(name, stat)
 *    struct DPTR *dptr;
 *    char *name;
 *    int *stat;
 *
 * dnext(dptr, name, stat)
 *    struct DPTR *dptr;
 *    char **name;
 *    int  *stat;
 *
 * dclose(dptr)                  -may be called with NULL without harm
 *
 * dopen() returns a struct DPTR, or NULL if the given file does not
 * exist.  stat will be set to 1 if the file is a directory.  If the
 * name is "", then the current directory is openned.
 *
 * dnext() returns 1 until there are no more entries.  The **name and
 * *stat are set.  *stat = 1 if the file is a directory.
 *
 * dclose() closes a directory channel.
 *
 */

struct DPTR *
dopen(name, stat)
char *name;
register int *stat;
{
    register struct DPTR *dp;

    *stat = 0;
    dp = (struct DPTR *)malloc(sizeof(struct DPTR));
    if (*name == '\0')
        dp->lock = DupLock (Clock);
    else
        dp->lock = Lock (name, ACCESS_READ);
    if (dp->lock == NULL) {
        free (dp);
        return (NULL);
    }
    dp->fib = (struct FileInfoBlock *)
            AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC);
    if (!Examine (dp->lock, dp->fib)) {
        perror (name);
        dclose (dp);
        return (NULL);
    }
    if (dp->fib->fib_DirEntryType >= 0)
        *stat = 1;
    return (dp);
}

dnext(dp, pname, stat)
register struct DPTR *dp;
char **pname;
int *stat;
{
    if (dp == NULL)
        return (0);
    if (ExNext (dp->lock, dp->fib)) {
        *stat = (dp->fib->fib_DirEntryType < 0) ? 0 : 1;
        *pname = dp->fib->fib_FileName;
        return (1);
    }
    return (0);
}


dclose(dp)
register struct DPTR *dp;
{
    if (dp == NULL)
        return (1);
    if (dp->fib)
        FreeMem (dp->fib, sizeof(*dp->fib));
    if (dp->lock)
        UnLock (dp->lock);
    free (dp);
    return (1);
}


isdir(file)
char *file;
{
    register struct DPTR *dp;
    int stat;

    stat = 0;
    if (dp = dopen (file, &stat))
        dclose(dp);
    return (stat == 1);
}


free_expand(av)
register char **av;
{
    register char **base = av;

    if (av) {
        while (*av) {
            free (*av);
            ++av;
        }
        free (base);
    }
}

/*
 * EXPAND(wild_name, pac)
 *    wild_name      - char * (example: "df0:*.c")
 *    pac            - int  *  will be set to # of arguments.
 *
 * Standalone, except in requires Clock to point to the Current-Directory
 * lock.
 */


char **
expand(base, pac)
char *base;
int *pac;
{
    register char *ptr;
    char **eav = (char **)malloc (sizeof(char *));
    short eleft, eac;
    char *name;
    char *bname, *ename, *tail;
    int stat, scr;
    register struct DPTR *dp;

    *pac = eleft = eac = 0;

    base = strcpy(malloc(strlen(base)+1), base);
    for (ptr = base; *ptr && *ptr != '?' && *ptr != '*'; ++ptr);
    for (; ptr >= base && !(*ptr == '/' || *ptr == ':'); --ptr);
    if (ptr < base) {
        bname = strcpy (malloc(1), "");
    } else {
        scr = ptr[1];
        ptr[1] = '\0';
        bname = strcpy (malloc(strlen(base)+1), base);
        ptr[1] = scr;
    }
    ename = ptr + 1;
    for (ptr = ename; *ptr && *ptr != '/'; ++ptr);
    scr = *ptr;
    *ptr = '\0';
    tail = (scr) ? ptr + 1 : NULL;

    if ((dp = dopen (bname, &stat)) == NULL  ||  stat == 0) {
        free (bname);
        free (base);
        free (eav);
        Eputs ("Could not open directory");
        return (NULL);
    }
    while (dnext (dp, &name, &stat)) {
        if (compare_ok(ename, name)) {
            if (tail) {
                int alt_ac;
                char *search, **alt_av, **scrav;
                struct FileLock *lock;

                if (!stat)           /* expect more dirs, but this not a dir */
                    continue;
                lock = CurrentDir (Clock = dp->lock);
                search = malloc(strlen(name)+strlen(tail)+2);
                strcpy (search, name);
                strcat (search, "/");
                strcat (search, tail);
                scrav = alt_av = expand (search, &alt_ac);
                CurrentDir (Clock = lock);
                if (scrav) {
                    while (*scrav) {
                        if (eleft < 2) {
                            char **scrav = (char **)malloc(sizeof(char *) * (eac + 10));
                            bmov (eav, scrav, (eac + 1) << 2);
                            free (eav);
                            eav = scrav;
                            eleft = 10;
                        }
                        eav[eac] = malloc(strlen(bname)+strlen(*scrav)+1);
                        strcpy(eav[eac], bname);
                        strcat(eav[eac], *scrav);
                        free (*scrav);
                        ++scrav;
                        --eleft, ++eac;
                    }
                    free (alt_av);
                }
            } else {
                if (eleft < 2) {
                    char **scrav = (char **)malloc(sizeof(char *) * (eac + 10));
                    bmov (eav, scrav, (eac + 1) << 2);
                    free (eav);
                    eav = scrav;
                    eleft = 10;
                }
                eav[eac] = malloc (strlen(bname)+strlen(name)+1);
                eav[eac] = strcpy(eav[eac], bname);
                strcat(eav[eac], name);
                --eleft, ++eac;
            }
        }
    }
    dclose (dp);
    *pac = eac;
    eav[eac] = NULL;
    free (bname);
    free (base);
    if (eac)
        return (eav);
    free (eav);
    return (NULL);
}

/*
 * Compare a wild card name with a normal name
 */

#define MAXB   8

compare_ok(wild, name)
char *wild, *name;
{
    register char *w = wild;
    register char *n = name;
    char *back[MAXB][2];
    register char s1, s2;
    register short bi = 0;

    while (*n || *w) {
        switch (*w) {
        case '*':
            if (bi == MAXB) {
                Eputs ("Too many levels of '*'");
                return (0);
            }
            back[bi][0] = w;
            back[bi][1] = n;
            ++bi;
            ++w;
            continue;
goback:
            --bi;
            while (bi >= 0 && *back[bi][1] == '\0')
                --bi;
            if (bi < 0)
                return (0);
            w = back[bi][0] + 1;
            n = ++back[bi][1];
            ++bi;
            continue;
        case '?':
            if (!*n) {
                if (bi)
                    goto goback;
                return (0);
            }
            break;
        default:
            s1 = (*n >= 'A' && *n <= 'Z') ? *n - 'A' + 'a' : *n;
            s2 = (*w >= 'A' && *w <= 'Z') ? *w - 'A' + 'a' : *w;
            if (s1 != s2) {
                if (bi)
                    goto goback;
                return (0);
            }
            break;
        }
        if (*n)  ++n;
        if (*w)  ++w;
    }
    return (1);
}


Oputs(str)
char *str;
{
    Write (Cout, str, strlen(str));
    Write (Cout, "\n", 1);
}

Eputs(str)
char *str;
{
    Write (Cerr, str, strlen(str));
    Write (Cerr, "\n", 1);
}

char *
Ogets(str)
char *str;
{
    register int i = 0;

    while (Read(Cin, str + i, 1) == 1) {
        if (str[i] == '\n') {
            str[i] = 0;
            return (str);
        }
        if (++i == 255) {
            str[i] = 0;
            return (str);
        }
    }
    return (NULL);
}



