#include        <stdio.h>
#include        <libraries/dos.h>
#include        <libraries/dosextens.h>
#include        <exec/memory.h>
#include        <exec/tasks.h>

/*
 * P S :        Like the UN*X command of the same name, this reports on
 *              running processes in the system. The current version only
 *              reports CLI processes, so you don't see the filesystem
 *              tasks. Written because I got tired of seeing the question
 *              marks that status spits out at every opportunity.
 *              This will build OK with 16 bit integers.
 *
 * Usage:       ps [-f]
 *
 * Author:      Dewi Williams ..!ihnp4!druca!dewi
 * Status:      Public domain.
 */
     
/* Defines */

/* Change typeless BCPL BPTR to typed C (for struct pointers). Don't
 * use this define on an APTR, that's only a badly disguised void *.
 */
#define BPTR_TO_C(strtag, var)  ((struct strtag *)(BADDR( (ULONG) var)))

/* Use AmigaDOS i/o to keep executable size down */
#define WSTR(s)         (void)Write(OutLock, s, (long)strlen(s))
#define WCHR(s)         (void)Write(OutLock, s, 1L)     /* char w/ d. quotes */

#define TO_ASC(n)       ((n) + '0')             /* make it printable! */

/* Casting conveniences */
#define PROC(task)              ((struct Process *)task)
#define ROOTNODE                ((struct RootNode *)DOSBase->dl_Root)
#define CLI(proc)               (BPTR_TO_C(CommandLineInterface, proc->pr_CLI))

/* Externs */
extern struct DosLibrary *DOSBase;      /* dos library base pointer */
extern struct FileLock   *Output();     /* get output file handle */

/* Globals */
static struct FileLock   *OutLock;      /* used by WSTR define */
static int               fullopt = 0;   /* set by -f command line flag */

main(argc, argv)
int     argc;
char    **argv;
{
        register ULONG   *tt;           /* References TaskArray         */
        register int     count;         /* loop variable                */
        register UBYTE   *port;         /* msgport & ptr arith          */
        register struct Task *task;     /* EXEC descriptor              */
        char             strbuf[256];   /* scratch for btocstr()        */
        char             *btocstr();    /* BCPL BSTR to ASCIIZ          */
        void             disp_hdr();    /* display ps header            */
        void             display();     /* display data for one process */

        OutLock = Output();             /* initialize output handle     */

        if (argc > 1 && strcmp(argv[1], "-f") == 0) fullopt = 1;

        tt = (unsigned long *)(BADDR(ROOTNODE->rn_TaskArray));

        if (fullopt) disp_hdr();

        Forbid();               /* need linked list consistency */
     
        /* Loop through the data for the CLI processes. */
        for (count = 1; count <= (int)tt[0] ; count++) {/* or just assume 20?*/
                if (tt[count] == 0) continue;           /* nobody home */

                /* Start by pulling out MsgPort addresses from the TaskArray
                 * area. By making unwarranted assumptions about the layout
                 * of Process and Task structures, we can derive these
                 * descriptors. Every task has an associated process, since
                 * this loop drives off a CLI data area.
                 */

                port = (UBYTE *)tt[count];
                task = (struct Task *)(port - sizeof(struct Task));

                /* Sanity check just in case */
                if (PROC(task)->pr_TaskNum == 0 || PROC(task)->pr_CLI == NULL)
                        continue;               /* or complain? */

                /* Pass the C string version of the command name to
                 * the display routine.
                 */
                display(count, task->tc_Node.ln_Name,
                        btocstr(CLI(PROC(task))->cli_CommandName, strbuf),
                        task->tc_Node.ln_Pri);
        }
        Permit();               /* outside critical region */
        exit(0);
}

/*
 * Convert a BCPL string to a C string. To avoid scrogging in-memory
 * stuff, it copies it first. Your data area better be big enough!
 */

char *
btocstr(b, buf)
ULONG   b;
char    *buf;
{
        register char   *s;

        s = (char *)BADDR(b);   /* Shift & get length-prefixed str */
        (void)movmem(s +1, buf, s[0]);  /* a.k.a memcpy */
        buf[s[0]] = '\0';
        return buf;
}

/*
 * Display the header for the output.
 */

void
disp_hdr()
{
        WSTR("Process #  CLI Type         Command Name         Priority\n");
}

/*
 * Display the information for a particular CLI process. Keep the format
 * string in sync with that of disp_hdr().
 */

void
display(tnum, type, name, pri)
int     tnum;           /* CLI process number */
char    *type;          /* Initial, Background or New CLI */
char    *name;          /* Command name if CLI's running one */
int     pri;            /* priority of CLI & command */
{
        char            *psitoa();
        char            buf[80];
        char            *ext_len();
        register char   *p = buf;

        if (*name == '\0') name = "<Cli>";      /* Null, nothing loaded */

        if (fullopt) {
                p = ext_len(psitoa(tnum, p), 11);
                strcpy(p, type);
                p = ext_len(p, 17);
                strcpy(p, name);
                p = ext_len(p, 21);
                (void)psitoa(pri, p);
        } else {
                p = ext_len(psitoa(tnum, p), 2);
                *p++ = ':';
                *p++ = ' ';
                strcpy(p, name);
        }

        WSTR(buf);
        WCHR("\n");
}

/*
 * Everything after here is a hack to avoid dragging in printf.
 * ------------------------------------------------------------
 */
     
/*
 * Limited itoa style function. Don't use it anywhere else! Used to keep
 * things small & avoid printf. Has a very limited range (3 digits +/- sign).
 * This hack avoids having to do the recursion & reverse of K&R itoa.
 */
     
char *
psitoa(num, into)
int     num;                            /* number to convert  */
char    *into;                          /* write it into here */
{
        register char   *p = into;
     
        if (num < 0) {
                *p++ = '-';
                num = -num;
        }
        if (num > 99) {
                *p++ = TO_ASC(num/100);
                *p++ = TO_ASC((num%100)/10);
                *p++ = TO_ASC(num%10);
        } else if (num > 9) {
                *p++ = TO_ASC(num/10);
                *p++ = TO_ASC(num%10);
        } else
                *p++ = TO_ASC(num);

        *p = '\0';                      /* end of the string */
        return into;
}

/*
 * Extend to length. Assumes strlen(s) never greater than len.
 */

char *
ext_len(s, len)
char    *s;                     /* must be large enough for extension to len */
int     len;
{
        register int    leftover;
        register int    slen = strlen(s);
        register char   *p = s + slen;
     
        for(leftover = len - slen; leftover > 0; leftover--)
                *p++ = ' ';

        *p = '\0';              /* null terminate */
        return p;               /* return loc after string */
}
