#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 */
}
