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

    11/88

    QCAT v0.01 -- List files on disks or in directories

                         -----------------

                   Copyright (C) 1988 by Daniel Elbaum

         This software is freely redistributable provided that:
         the three files which comprise it (qcat, qcat.c, qcat.doc)
         remain intact; all copyright notices contained in any of
         the aforementioned files remain intact; and no fee beyond
         reasonable remuneration for collation and distribution be
         charged for use and/or purveyance.

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

#include <stdio.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>

/* Error descriptor codes */

#define E_NOLOCK    (-1)
#define E_NOMEM     (-2)
#define E_NOEXAM    (-3)
#define E_USAGE     (-4)
#define E_OUTPUT    (-5)
#define E_LIBRARY   (-6)
#define E_NONODE    (-100)
#define E_NOSTRING  (-101)

/* Manifest constants */

#define NAMSIZ      (108)
#define MEMFLOOR    (64000L)    /* least amt of mem to leave    */

#define IREV    (1)
#define INAM    ("intuition.library")

/* Global data, type and function declarations */
typedef struct IntuitionBase* ib_t;

ib_t IntuitionBase=NULL;
void *OpenLibrary();

FILE *ofp;

struct {
    char filenam[NAMSIZ*10];
    } fs;

struct namlist {
    char *nam;
    struct namlist *next;
    } nlist;

/*
    Acts like a state machine, the state of which
    is determined by the combination of arguments
    supplied.  The while loop sets the state, while
    the if switches select action taken according
    thereto.
*/

main(c, v)
char **v;
{
    register active;
    register err;
    char *cdrive=NULL;
    char *onam=NULL;
    char bu[NAMSIZ*10];
    char *getcd();
    char *realnam();
    void getrspinit(), freelist();


    ++v;
    while (**v == '-'){
        switch(*++*v){
            case 'o':   /* specify output file  */
                if (!*++v) {prerr(NULL, E_USAGE); exit(10);}
                else {onam=*v; ++v;}
                break;
            case 'c':   /* continuous listing of 1 drive    */
                if (!*++v) {prerr(NULL, E_USAGE); exit(10);}
                else {cdrive=*v; ++v;}
                if (!(IntuitionBase=(ib_t)OpenLibrary(INAM, IREV))){
                    prerr(NULL, E_LIBRARY); exit(10);
                }
                break;
            default:
                prerr(NULL, E_USAGE);
                exit(10);
        }
    }

    if (onam){
        if (!(ofp=fopen(onam, "w"))){
            prerr(onam, E_OUTPUT);
            exit(10);
        }
    }
    else ofp=stdout;

    if (cdrive){
        getrspinit();
        if (getresponse()!=FALSE){
            for (active=1; active; ){
                realnam(cdrive, fs.filenam);    /* seed pathname    */
                if (err=getnams(cdrive)) prerr(cdrive, err);
                if (getresponse()==FALSE) active=0;
            }
        }
    }
    else if (!*v){
        getcd(bu);
        strcpy(fs.filenam, bu);
        if (err=getnams(bu)) prerr(bu, err);
    }
    else{
        for (; *v; ++v){
            realnam(*v, fs.filenam);            /* seed pathname    */
            if (err=getnams(*v)) prerr(*v, err);
        }
    }
    prlist(ofp, &nlist);
    freelist(&nlist);
    if (ofp!=stdout) fclose(ofp);
    if (IntuitionBase) CloseLibrary(IntuitionBase);
    exit(0);
}

/*
    If nam is the name of a file, add its size to the global counter.
    If nam is the name of a directory, saunter down a level and
    grab info for its files.
    Return a coded description of any error which occurs, or zero
    if success was the order of the day.
*/

getnams(nam)
char *nam;
{
    register struct FileLock *l=NULL;
    register struct FileInfoBlock *f=NULL;
    register err=0;
    struct FileLock *Lock();
    void addtonam(), truncnam();

    if (!nam) return(E_NOEXAM);
    if (!(l=Lock(nam, ACCESS_READ))){
        return(E_NOLOCK);
    }
    if (!(f=(struct FileInfoBlock *)AllocMem((ULONG)sizeof(*f), MEMF_PUBLIC))) {
        UnLock(l);
        return(E_NOMEM);
    }
    if (!Examine(l, f)) {
        UnLock(l);
        FreeMem(f, (ULONG)sizeof(*f));
        return(E_NOEXAM);
    }
    if (f->fib_DirEntryType<0){     /* file */
        addnam(nam, &nlist);
    }
    else {
        while (ExNext(l, f)){     /* directory    */
            addtonam(fs.filenam, f->fib_FileName);
            if (err=getnams(fs.filenam)) break;
            truncnam(fs.filenam);        /* delete filename from path    */
        }
    }
    UnLock(l);
    FreeMem(f, (ULONG)sizeof(*f));
    return(err);
}

/*
    Assuming p points to a valid pathname and s to a valid filename,
    append the filename to the pathname.
*/

void
addtonam(p, s)
register char *p, *s;
{
    register char *op=p;

    while (*p)
        p++;
    if (p!=op){
        if (*--p!=':')
            *++p='/';
        p++;
    }
    while (*s)
        *p++=*s++;
    *p='\0';
}

/*
    Assuming p points to a valid pathname, clip the filename from it.
*/

void
truncnam(p)
register char *p;
{
    register char *pp;

    if (!p) return;
    for (pp=p; *pp; pp++)
        ;
    while (*pp!='/'&&*pp!=':'&&pp>p)
        --pp;
    if (*pp==':') ++pp;
    *pp='\0';
}

/*
    Build a real pathname from the filename,
    dirname, or drivename specifiecd in nam
    into bu.  Return a pointer to bu on success,
    or NULL if the chore is beyond doing.
*/

char *
realnam(nam, bu)
char *nam, *bu;
{
    register addcol;
    register struct FileLock *l=NULL;
    struct FileLock *Lock();

    if (!nam||!bu) return(NULL);
    if (nam[strlen(nam)-1]==':') addcol=1;
    else addcol=0;
    if (!(l=Lock(nam, ACCESS_READ))){
        return(NULL);
    }
    fullnam(l, bu);
    UnLock(l);
    return(bu);
}


/*
    Put the full name of the current
    directory into the supplied buffer
    and return a pointer to it.
*/

char *
getcd(bu)
char *bu;
{
    struct FileLock *ol;

    if (!bu) return(NULL);
    fullnam(ol=CurrentDir(0L), bu);
    CurrentDir(ol);
    return(bu);
}

/*
    Put the full pathname of a
    locked file in the given buffer
    and return a pointer to it.
*/

char *
fullnam(l, bu)
register struct FileLock *l;
char *bu;
{
    register len;
    struct FileLock *nl, *ParentDir();
    char bu2[NAMSIZ];
    char ebu[NAMSIZ*10];

    if (!l||!bu) return(NULL);
    bu2[0]=0;
    locknam(l, bu);
    while (l=ParentDir(l)){
        if (locknam(l, bu2)){
            sprintf(ebu, "%s/%s", bu2, bu);
            strcpy(bu, ebu);
        }
        else break;
    }
    if (bu2[0]) bu[strlen(bu2)]=':';
    else {bu[len=strlen(bu)]=':'; bu[len+1]='\0';}
    return(bu);
}

/*
    Put the name of the file or directory
    associated with the given lock into
    the given buffer and return a pointer
    to it or NULL if it couldn't be done.
*/

char *
locknam(l, bu)
register struct FileLock *l;
char *bu;
{
    register struct FileInfoBlock *f=NULL;

    if (!l||!bu) return(NULL);
    if (!(f=(struct FileInfoBlock *)AllocMem((ULONG)sizeof(*f), MEMF_PUBLIC))) {
        return(NULL);
    }
    if (!Examine(l, f)) {
        FreeMem(f, (ULONG)sizeof(*f));
        return(NULL);
    }
    strcpy(bu, f->fib_FileName);
    FreeMem(f, (ULONG)sizeof(*f));
    return(bu);
}

/*
    Add a name to the end of the filename list.
    Could be more efficient, but the bottleneck
    is disk speed.  Return a descriptive error
    value on failure.
*/

addnam(s, lp)
char *s;
struct namlist *lp;
{
    struct namlist *newnode();
    char *strsave();
    void freelist(), prlist();

    if (!s||!lp) return(-1);
    if (amtfree()<MEMFLOOR){        /* dump if mem full */
        prlist(ofp, lp);
        freelist(lp);
    }
    while (lp->next)
        lp=lp->next;
    if (!(lp->next=newnode()))
        return(E_NONODE);    /* won't happen.  Better not.   */
    if (!(lp->next->nam=strsave(s)))
        return(E_NOSTRING);
    return(0);
}

/*
    Allocate a new node for the filename list.
    Return a pointer to it or NULL if there's
    not enough memory.
*/

struct namlist *
newnode()
{
    register struct namlist *mp;

    if (mp=(struct namlist *)malloc(sizeof(struct namlist))){
        mp->nam=NULL;
        mp->next=NULL;
    }
    return(mp);
}

/*
    Free the filename list.
*/

void
freelist(lp)
struct namlist *lp;
{
    struct namlist *p;

    if (!lp) return;
    lp=lp->next;    /*  don't free 1st item */
    while (lp){
        p=lp->next;
        free(lp->nam);
        free(lp);
        lp=p;
    }
}

/*
    Print out the filename list.
*/

void
prlist(fp, lp)
FILE *fp;
struct namlist *lp;
{
    if (!lp||!fp) return;
    lp=lp->next;    /* don't print 1st item */
    while (lp){
        fprintf(fp, "%s\n", lp->nam);
        lp=lp->next;
    }
}

/*
    An old favorite.
*/

char *
strsave(s)
char *s;
{
    register len;
    register char *mp;

    if (!s) return(NULL);
    if (mp=malloc(len=strlen(s)+1))
        memcpy(mp, s, len);
    return(mp);
}

/*
    Report amount of free memory in the system.
    Errors not allowed here.
*/

ULONG
amtfree()
{
    ULONG c, f;

    Forbid();
    c=AvailMem(MEMF_CHIP);
    f=AvailMem(MEMF_FAST);
    Permit();
    return(c+f);
}

/*
    Print a message corresponding to the error given,
    prompting with s, if nonnull.
    Return the error given.
*/

prerr(s, err)
char *s;
{
    register char *errp;

    switch (err){
        case E_NOMEM:
            errp="Out of memory"; break;
        case E_NOEXAM:
            errp="Couldn't examine"; break;
        case E_NOLOCK:
            errp="File not found"; break;
        case E_OUTPUT:
            errp="Can't open output file"; break;
        case E_USAGE:
            errp="Usage: qcat [-o outputfile] [-c drivename] or \n\
qcat [-o outputfile] dirname";
            break;
        case E_LIBRARY:
            errp="Couldn't open intuition.library";
            break;
        default:    /* NOTREACHED   */
            errp="grievous error"; break;
    }
    if (s) printf("%s: %s\n", s, errp);
    else printf("%s\n", errp);
    return(err);
}

/*
    Set up the AutoRequest stuff, and use the
    automatic requestor to tell the user when
    to change disks, let them quit, and return
    when they do so.
*/

char *rsp_bod0="Insert a disk";
char *rsp_bod1="Next disk";
char *rsp_no="Quit";
char *rsp_yes="Redo";

struct response {
    struct IntuiText bod;
    struct IntuiText no;
    ULONG yflg;
    struct Window *w;
    } rsp;

/*
    Initialize the autorequestor's data and
    find a window to put it in. Return nonzero
    on failure, zero on success.
*/

getrspinit()
{
    rsp.bod.FrontPen=0;
    rsp.bod.BackPen=1;
    rsp.bod.DrawMode=JAM1;
    rsp.bod.LeftEdge=6;
    rsp.bod.TopEdge=3;
    rsp.bod.ITextFont=NULL;
    rsp.bod.IText=rsp_bod0;
    rsp.bod.NextText=NULL;

    rsp.no=rsp.bod;
    rsp.no.IText=rsp_no;

    rsp.yflg=DISKINSERTED;

    return(!(rsp.w=getCLIwin()));
}

/*
    Return a pointer to a window for
    the requestor.  If there be none,
    return NULL.
*/

struct Window *
getCLIwin()
{
    register struct Screen *sp;
    register struct Window *wp;

    return(IntuitionBase->ActiveWindow);
}

/*
    Post a requestor asking for a new
    disk.  The first time called, just
    ask for the first disk; after that,
    ask for the next.  Return the boolean
    value of the user's response.
*/

getresponse()
{
    register rv=0;

    rv=AutoRequest(rsp.w, &rsp.bod, NULL, &rsp.no, rsp.yflg, 0L, 200, 50);
    rsp.bod.IText=rsp_bod1;
    return(rv);
}


