[comp.os.minix] Upgraded ps

valke@psy.vu.nl (Peter Valkenburg) (07/04/90)

Hello there,

below a slightly modified version to my ps is included.  It is an upgrade on
the 1.5.10 version and should work on both atari's and pc's.
People seem to have trouble with the program's operation regularly, so I have
added the man page as well and extended the information in the source for
those who don't like to have manuals on-line :->.

The major change is that ps now uses the /dev entries to determine the actual
tty names, and stores these in its database as well.  This also means that ps
is independent of your particular naming convention in /dev and that it is fit
for future devices without further modification.  E.g. "/dev/ttyp1" will (some
day) show up as "p1" in the TTY column.  It does however entail that you do a
new "ps -U" if you're using the database file and change tty names in /dev.

Some comments on problems that seem to pop up now and then:
	- Ps definitely compiles better in a directory that shares its parent
	  directory with that of the kernel/mm/fs source directories, since it
	  contains lines like:
	    #include "../kernel/const.h"
	- Contrary to folk belief, ps does not depend on any system executables
	  after a "ps -U".  You can effectively throw your kernel/mm/fs away as
	  far as ps is concerned.  All it needs to know is in the database.
	- Again contrary to that belief system, ps is not religious about the
	  boot image containing kernel/mm/fs with symbol tables.  In fact, you
	  can strip the executables after a "ps -U", then build the boot image
	  and save a few K's of core upon the next reboot.
	- Ps depends heavily on up-to-date system info.  If you recompile the
	  kernel, mm or fs, you will need to do a "ps -U" (but again, only if
	  you want to use that database mechanism).  If the changes include
	  really heavy stuff, like new tasks, you should recompile ps.
	- Ps's allergic reaction to kernels with amoeba in them is caused by
	  inaccurate system parameters.  The following should work:
	    1) Compile ps with -DAM_KERNEL.  This gets NR_TASKS right (will be
	       14 instead of 9).
	    2) Update the database from the amoeba executables, as in:
		  ps -U ../amoeba/kernel/kernel ../amoeba/mm/mm ../amoeba/fs/fs
	- Ps should be suid bin or root, since it reads /dev/[k]mem, which are
	  normally not readable by normal users.
	  If you want your ps to be useable by anyone, yet disallow modification
	  of the database by anyone other than root and bin, you can arrange the
	  following access permissions (note the protected memory files and set
	  *group* id on ps):
		-rwxr-sr-x  1 bin         11916 Jul  4 15:31 /bin/ps
		-rw-r--r--  1 bin           848 Jul  4 15:39 /etc/psdatabase
		crw-r-----  1 bin        1,   1 Jan  1  1970 /dev/mem
		crw-r-----  1 bin        1,   2 Jan  1  1970 /dev/kmem
	  That hardly anybody ever comments on this proves that the program is
	  little used, or that all MINIX users log on as bin or root, or that
	  many people have a serious security problem on their system :-).
	- Finally, give ps at least 10K of stack space with chmem.

Thanks for various comments on, and modifications of the program.  Hope this
helps,

		Peter Valkenburg (valke@psy.vu.nl).

---------cut here--------- ---------cut here--------- ---------cut here---------
echo x - ps.c
sed '/^X/s///' > ps.c << '/'
X/* ps - print status			Author: Peter Valkenburg */
X
X/* ps.c, Peter Valkenburg (valke@psy.vu.nl), january 1990.
X *
X * This is a V7 ps(1) look-alike for MINIX 1.5.0.  It can use a database with
X * information on system addresses and terminal names as an extra, and has
X * some additional fields.
X * It does not support the 'k' option (i.e. cannot read memory from core file).
X * If you want to compile this for non-IBM PC architectures, the header files
X * require that you have your CHIP, MACHINE etc. defined.
X * Full syntax:
X *	ps [-][alxU] [kernel mm fs]
X * Option `a' gives all processes, `l' for detailed info, `x' includes even
X * processes without a terminal, `U' updates /etc/psdatabase where ps keeps
X * system information and terminal names.  Additional arguments are the paths
X * of system binaries from which system addresses are taken.
X *
X * VERY IMPORTANT NOTE:
X *	To compile ps, the kernel/, fs/ and mm/ source directories must be in
X *	../ relative to the directory where ps is compiled (normally the
X *	command source directory).
X *	To run ps the kernel, mm and fs executables must contain symbol tables.
X *	This can be arranged using the -s flag and the ast program.  For
X *	example in fs, one would have
X *
X *	asld -s -i -o fs $l/head.s $(obj) $l/libc.a $l/end.s >symbol.out
X *	ast -X fs		# include symbol.out in fs
X *
X *	Ps does *NOT* need the boot image to contain symbol tables.  Nor does
X *	it need kernel, fs or mm binaries after a `ps -U'.  The paths of these
X *	executables can be changed by passing them in the command line, e.g.,
X *	a "ps /kernel /mm /fs" runs ps with namelists extracted from the
X *	system executables found in /.
X *	If you want your ps to be useable by anyone, yet disallow modification
X *	of the database by anyone other than root and bin, you can arrange the
X *	following access permissions (note the protected memory files and set
X *	*group* id on ps):
X *	-rwxr-sr-x  1 bin         11916 Jul  4 15:31 /bin/ps
X *	-rw-r--r--  1 bin           848 Jul  4 15:39 /etc/psdatabase
X *	crw-r-----  1 bin        1,   1 Jan  1  1970 /dev/mem
X *	crw-r-----  1 bin        1,   2 Jan  1  1970 /dev/kmem
X *
X *	Finally, this is what you have to do if you have amoeba in your kernel:
X *	  1) compile ps including -DAM_KERNEL as an argument to cc and
X *	  2) pass the amoeba kernel, mm and fs binaries to a ps U, as in:
X *		ps -U ../amoeba/kernel/kernel ../amoeba/mm/mm ../amoeba/fs/fs
X */
X
X/*
X * Some technical comments on this implementation:
X *
X * Most fields are similar to V7 ps(1), except for CPU, NICE, PRI which are
X * absent, RECV which replaces WCHAN, and PGRP that is an extra.
X * The info is obtained from the following fields of proc, mproc and fproc:
X * F	- kernel status field, p_flags
X * S	- kernel status field, p_flags; mm status field, mp_flags (R if p_flags
X * 	  is 0; Z if mp_flags == HANGING; T if mp_flags == STOPPED; else W).
X * UID	- mm eff uid field, mp_effuid
X * PID	- mm pid field, mp_pid
X * PPID	- mm parent process index field, mp_parent (used as index in proc).
X * PGRP - mm process group field, mp_procgrp
X * ADDR	- kernel physical text address, p_map[T].mem_phys
X * SZ	- kernel physical stack address + stack size - physical text address,
X * 	  p_map[S].mem_phys + p_map[S].mem_len - p_map[T].mem_phys
X * RECV	- kernel process index field for message receiving, p_getfrom
X *	  If sleeping, mm's mp_flags, or fs's fp_task are used for more info.
X * TTY	- fs controlling tty device field, fs_tty.
X * TIME	- kernel user + system times fields, user_time + sys_time
X * CMD	- system process index (converted to mnemonic name obtained by reading
X *	  tasktab array from kmem), or user process argument list (obtained by
X *	  reading the stack frame; the resulting address is used to get
X *	  the argument vector from user space and converted into a concatenated
X *	  argument list).
X */  	  
X 
X#include <minix/config.h>
X#include <limits.h>
X#include <sys/types.h>
X
X#include <minix/const.h>
X#undef EXTERN				/* <minix/const.h> defined this */
X#define EXTERN				/* so we get proc, mproc and fproc */
X#include <minix/type.h>
X
X#include "../kernel/const.h"
X#include "../kernel/type.h"
X#include "../kernel/proc.h"
X#undef printf				/* kernel's const.h defined this */
X
X#include "../mm/mproc.h"
X#include "../fs/fproc.h"
X#include "../fs/const.h"
X#undef printf				/* fs's const.h defined this */
X
X
X/*----- ps's local stuff below this line ------*/
X
X#include <minix/com.h>
X#include <fcntl.h>
X#include <a.out.h>
X#include <stdio.h>
X#include <dirent.h>
X#include <sys/stat.h>
X
X#define mindev(dev)	(((dev)>>MINOR) & 0377)	/* yield minor device */
X#define majdev(dev)	(((dev)>>MAJOR) & 0377)	/* yield major device */
X
X#define	TTY_MAJ		4			/* major device of console */
X
X/* macro to convert memory offsets to rounded kilo-units */
X#define	off_to_k(off)	((unsigned) (((off) + 512) / 1024))
X
X/* what we think the relevant identifiers in the namelists are */
X#define	ID_PROC		"_proc"		/* from kernel namelist */
X#define	ID_MPROC	"_mproc"	/* from mm namelist */
X#define	ID_FPROC	"_fproc"	/* from fs namelist */
X#define	ID_TASKTAB	"_tasktab"	/* from kernel namelist */
X
X/*
X * Structure for system address info (also part of ps's database).
X */
Xtypedef struct {
X	struct nlist ke_proc[2], ke_tasktab[2];
X	struct nlist mm_mproc[2];
X	struct nlist fs_fproc[2];
X} sysinfo_t;
X
Xsysinfo_t sysinfo;			/* sysinfo holds actual system info */
X
X/*
X * Structure for tty name info (also part of ps's database).
X */
Xtypedef struct {
X	char tty_name[NAME_MAX + 1];	/* file name in /dev */
X	dev_t tty_dev;			/* major/minor pair */
X} ttyinfo_t;
X
X#define	N_TTYINFO	40		/* # of ttyinfo_t structs */
X
Xttyinfo_t ttyinfo[N_TTYINFO];		/* ttyinfo holds actual tty info */
X
X#define	NAME_SIZ	(sizeof(sysinfo.ke_proc[0].n_name))	/* 8 chars */
X
X/* what we think the identfiers of the imported variables in this program are */
X#define	PROC	proc
X#define	MPROC	mproc
X#define	FPROC	fproc
X#define	TASKTAB	tasktab
X
X/* default paths for system binaries */
X#if (CHIP == M68000)
X#define KERNEL_PATH	"/usr/src/kernel/kernel.mix"
X#define MM_PATH		"/usr/src/mm/mm.mix"
X#define FS_PATH		"/usr/src/fs/fs.mix"
X#else
X#define KERNEL_PATH	"/usr/src/kernel/kernel"
X#define MM_PATH		"/usr/src/mm/mm"
X#define FS_PATH		"/usr/src/fs/fs"
X# endif
X
X#define	KMEM_PATH	"/dev/kmem"	/* opened for kernel proc table */
X#define	MEM_PATH	"/dev/mem"	/* opened for mm/fs + user processes */
X
Xint kmemfd, memfd;			/* file descriptors of [k]mem */
X
X#define DBASE_PATH	"/etc/psdatabase"	/* path of ps's database */
X#define DBASE_MODE	0644			/* mode for ps's database */
X
X/* paths for system binaries (not relevant if database is used) */
Xchar *kpath = KERNEL_PATH;
Xchar *mpath = MM_PATH;
Xchar *fpath = FS_PATH;
X
Xstruct tasktab tasktab[NR_TASKS + INIT_PROC_NR + 1];	/* task table */
X
X/*
X * Short and long listing formats:
X *
X *   PID TTY  TIME CMD
X * ppppp tttmmm:ss ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
X * 
X *   F S UID   PID  PPID  PGRP ADDR  SZ       RECV TTY  TIME CMD
X * fff s uuu ppppp ppppp ppppp aaaa sss rrrrrrrrrr tttmmm:ss ccccccccccccccccccccc
X */
X#define S_HEADER "  PID TTY  TIME CMD\n"
X#define S_FORMAT "%5d %3s%3ld:%02ld %.63s\n"
X#define L_HEADER "  F S UID   PID  PPID  PGRP ADDR  SZ       RECV TTY  TIME CMD\n"
X#define L_FORMAT "%3o %c %3d %5d %5d %5d %4d %3d %10s %3s%3ld:%02ld %.21s\n"
X
Xstruct pstat {				/* structure filled by pstat() */
X	dev_t ps_dev;			/* major/minor of controlling tty */
X	uid_t ps_ruid;			/* real uid */
X	uid_t ps_euid;			/* effective uid */
X	pid_t ps_pid;			/* process id */
X	pid_t ps_ppid;			/* parent process id */
X	int ps_pgrp;			/* process group id */
X	int ps_flags;			/* kernel flags */
X	int ps_mflags;			/* mm flags */
X	int ps_ftask;			/* (possibly pseudo) fs suspend task */
X	char ps_state;			/* process state */
X	size_t ps_tsize;		/* text size (in bytes) */
X	size_t ps_dsize;		/* data size (in bytes) */
X	size_t ps_ssize;		/* stack size (in bytes) */
X	off_t ps_text;			/* physical text offset */
X	off_t ps_data;			/* physical data offset */
X	off_t ps_stack;			/* physical stack offset */
X	int ps_recv;			/* process number to receive from */
X	time_t ps_utime;		/* accumulated user time */
X	time_t ps_stime;		/* accumulated system time */
X	char *ps_args;			/* concatenated argument string */
X};
X
X/* ps_state field values in pstat struct above */
X#define	Z_STATE		'Z'		/* Zombie */
X#define	W_STATE		'W'		/* Waiting */
X#define	S_STATE		'S'		/* Sleeping */
X#define	R_STATE		'R'		/* Runnable */
X#define	T_STATE		'T'		/* stopped (Trace) */
X
X/*
X * Tname returns mnemonic string for dev_nr.  This is "?" for maj/min pairs that
X * are not found.  It uses the ttyinfo array (prepared by gettynames).
X * Tname assumes that the first three letters of the tty's name can be omitted
X * and returns the rest (except for the console, which yields "co").
X * Thus, tname normally returns "co" for tty0, "1" for tty1, "01" for tty01,
X * "p0" for ptyp0, "i00" for ttyi00, etc.
X * One snag: If some non-standard name appears in /dev before the regular one,
X * ps will print the non-standard's suffix.
X */
Xchar *tname(dev_nr)
Xdev_t dev_nr;
X{
X	int i;
X	
X	if (majdev(dev_nr) == TTY_MAJ && mindev(dev_nr) == 0)
X		return "co";
X
X	for (i = 0; i < N_TTYINFO && ttyinfo[i].tty_name[0] != '\0'; i++)
X		if (ttyinfo[i].tty_dev == dev_nr)
X			if (strlen(ttyinfo[i].tty_name) <= 3)
X				return ttyinfo[i].tty_name;
X			else
X				return &(ttyinfo[i].tty_name[3]);
X
X	return "?";
X}
X
X/* return canonical task name of task p_nr; overwritten on each call (yucch) */
Xchar *taskname(p_nr)
X{
X	char *cp;
X	
X	if (p_nr < -NR_TASKS || p_nr > INIT_PROC_NR)
X		return "?";
X	
X	/* strip trailing blanks for right-adjusted output */
X	for (cp = tasktab[p_nr + NR_TASKS].name; *cp != '\0'; cp++)
X		if (*cp == ' ')
X			break;
X	*cp = '\0';		
X	
X	return tasktab[p_nr + NR_TASKS].name;
X}
X
X/*
X * Prrecv prints the RECV field for process with pstat buffer pointer bufp.
X * This is either "ANY", "taskname", or "(blockreason) taskname".
X */
Xchar *prrecv(bufp)
Xstruct pstat *bufp;
X{
X	char *blkstr, *task;		/* reason for blocking and task */
X	static char recvstr[20];
X
X	if (bufp->ps_recv == ANY)
X		return "ANY";
X
X	task = taskname(bufp->ps_recv);
X	if (bufp->ps_state != S_STATE)
X		return task;
X	
X	blkstr = "?";
X	if (bufp->ps_recv == MM_PROC_NR) {
X		if (bufp->ps_mflags & PAUSED)
X			blkstr = "pause";
X		else if (bufp->ps_mflags & WAITING)
X			blkstr = "wait";
X	}
X	else if (bufp->ps_recv == FS_PROC_NR) {
X		if (-bufp->ps_ftask == XOPEN)
X			blkstr = "xopen";
X		else if (-bufp->ps_ftask == XPIPE)
X			blkstr = "xpipe";
X		else
X			blkstr = taskname(-bufp->ps_ftask);	
X	}
X	
X	(void) sprintf(recvstr, "(%s) %s", blkstr, task);
X	return recvstr;
X}
X
X/*
X * Main interprets arguments, gets system addresses, opens [k]mem, reads in
X * process tables from kernel/mm/fs and calls pstat() for relevant entries.
X */
Xmain(argc, argv)
Xchar *argv[];
X{
X	int i;
X	struct pstat buf;
X	int db_fd;
X	int uid = getuid();		/* real uid of caller */
X	int opt_all = FALSE;		/* -a */
X	int opt_long = FALSE;		/* -l */
X	int opt_notty = FALSE;		/* -x */
X	int opt_update = FALSE;		/* -U */
X	int opt_path = FALSE;		/* alternative system executables */
X
X	/* parse arguments; a '-' need not be present (V7/BSD compatability) */
X	switch (argc) {
X	case 1:		/* plain ps */
X		break;
X	case 2:		/* ps <[-][alxU]> */
X	case 5:		/* ps <[-][alxU]> <kernel mm fs> */
X		for (i = (argv[1][0] == '-' ? 1 : 0); argv[1][i] != '\0'; i++)
X			switch (argv[1][i]) {
X			case 'a':
X				opt_all = TRUE;
X				break;
X			case 'l':
X				opt_long = TRUE;
X				break;
X			case 'x':
X				opt_notty = TRUE;
X				break;
X			case 'U':
X				opt_update = TRUE;
X				break;
X			default:
X				usage(argv[0]);
X			}	
X		break;
X	case 4:		/* ps <kernel mm fs> */
X		if (argv[1][0] != '-')
X			break;
X	default:
X		usage(argv[0]);
X	}
X	
X	if (argc >= 4) {	/* ps [-][alxU] <kernel mm fs> */
X		opt_path = TRUE;
X		kpath = argv[argc - 3];
X		mpath = argv[argc - 2];
X		fpath = argv[argc - 1];
X	}
X	
X	/* fill the sysinfo and ttyinfo structs */
X	if (opt_update || opt_path ||
X	    (db_fd = open(DBASE_PATH, O_RDONLY)) == -1) {
X		strncpy(sysinfo.ke_proc[0].n_name, ID_PROC, NAME_SIZ);
X		strncpy(sysinfo.ke_tasktab[0].n_name, ID_TASKTAB, NAME_SIZ);
X		if (nlist(kpath, sysinfo.ke_proc) != 0 ||
X		    nlist(kpath, sysinfo.ke_tasktab) != 0)
X			err("Can't read kernel namelist");
X		strncpy(sysinfo.mm_mproc[0].n_name, ID_MPROC, NAME_SIZ);
X		if (nlist(mpath, sysinfo.mm_mproc) != 0)
X			err("Can't read mm namelist");
X		strncpy(sysinfo.fs_fproc[0].n_name, ID_FPROC, NAME_SIZ);
X		if (nlist(fpath, sysinfo.fs_fproc) != 0)
X			err("Can't read fs namelist");
X
X		if (gettynames(ttyinfo) == -1)
X			err("Can't get tty names");
X
X		if (opt_update) {
X			if ((db_fd = creat(DBASE_PATH, DBASE_MODE)) == -1)
X				err("Can't creat psdatabase");
X			if (write(db_fd, (char *) &sysinfo,
X				  sizeof(sysinfo_t)) != sizeof(sysinfo_t) ||
X			    write(db_fd, (char *) ttyinfo,
X				  sizeof(ttyinfo)) != sizeof(ttyinfo))
X				err("Can't write psdatabase");
X		}		
X	}
X	else {
X		if (read(db_fd, (char *) &sysinfo,
X			 sizeof(sysinfo_t)) != sizeof(sysinfo_t) ||
X		    read(db_fd, (char *) ttyinfo,
X			 sizeof(ttyinfo)) != sizeof(ttyinfo))
X			err("Can't read info from psdatabase");	 
X	}
X	(void) close (db_fd);
X	    	
X	/* get kernel tables */    	
X	if ((kmemfd = open(KMEM_PATH, O_RDONLY)) == -1)
X		err(KMEM_PATH);
X	if (addrread(kmemfd, (phys_clicks) 0,
X		     (vir_bytes) sysinfo.ke_proc[0].n_value,
X		     (char *) PROC, sizeof(PROC)) != sizeof(PROC))
X		err("Can't get kernel proc table from /dev/kmem");
X	if (addrread(kmemfd, (phys_clicks) 0,
X		     (vir_bytes) sysinfo.ke_tasktab[0].n_value,
X		     (char *) TASKTAB, sizeof(TASKTAB)) != sizeof(TASKTAB))
X		err("Can't get kernel task table from /dev/kmem");
X
X	/* get mm/fs tables */
X	if ((memfd = open(MEM_PATH, O_RDONLY)) == -1)
X		err(MEM_PATH);
X	if (addrread(memfd, PROC[NR_TASKS + MM_PROC_NR].p_map[D].mem_phys,
X		     (vir_bytes) sysinfo.mm_mproc[0].n_value,
X		     (char *) MPROC, sizeof(MPROC)) != sizeof(MPROC))
X		err("Can't get mm proc table from /dev/mem");
X	if (addrread(memfd, PROC[NR_TASKS + FS_PROC_NR].p_map[D].mem_phys,
X		     (vir_bytes) sysinfo.fs_fproc[0].n_value,
X		     (char *) FPROC, sizeof(FPROC)) != sizeof(FPROC))
X		err("Can't get fs proc table from /dev/mem");
X		
X	/* now loop through process table and handle each entry */
X	printf("%s", opt_long ? L_HEADER : S_HEADER);
X	for (i = -NR_TASKS; i < NR_PROCS; i++) {
X		if (pstat(i, &buf) != -1 &&
X		    (opt_all || buf.ps_euid == uid || buf.ps_ruid == uid) &&
X		    (opt_notty || majdev(buf.ps_dev) == TTY_MAJ))
X			if (opt_long)
X				printf(L_FORMAT,
X				       buf.ps_flags, buf.ps_state,
X				       buf.ps_euid, buf.ps_pid, buf.ps_ppid,
X				       buf.ps_pgrp,
X				       off_to_k(buf.ps_text),
X				       off_to_k((buf.ps_stack + buf.ps_ssize
X				       			- buf.ps_text)),
X				       (buf.ps_flags & RECEIVING ?
X						prrecv(&buf) :
X				       		""),
X				       tname(buf.ps_dev),
X				       (buf.ps_utime + buf.ps_stime) / HZ / 60,
X				       (buf.ps_utime + buf.ps_stime) / HZ % 60,
X				       i <= INIT_PROC_NR ? taskname(i) :
X						(buf.ps_args == NULL ? "" :
X					   		buf.ps_args));
X			else
X				printf(S_FORMAT,
X				       buf.ps_pid, tname(buf.ps_dev),
X				       (buf.ps_utime + buf.ps_stime) / HZ / 60,
X				       (buf.ps_utime + buf.ps_stime) / HZ % 60,
X				       i <= INIT_PROC_NR ? taskname(i) :
X						(buf.ps_args == NULL ? "" :
X					   		buf.ps_args));
X	}
X}
X
X/*
X * Get_args inspects /dev/mem, using bufp, and tries to locate the initial
X * stack frame pointer, i.e. the place where the stack started at exec time.
X * It is assumed that the end of the stack frame looks as follows:
X *	argc	<-- initial stack frame starts here
X *	argv[0]
X *	...
X *	NULL	(*)
X *	envp[0]
X *	...
X *	NULL	(**)
X *	argv[0][0] ... '\0'
X *	...
X *	argv[argc - 1][0] ... '\0'
X *	envp[0][0] ... '\0'
X *	...
X *	[trailing '\0']
X * Where the total space occupied by this original stack frame <= ARG_MAX.
X * Get_args reads in the last ARG_MAX bytes of the process' data, and
X * searches back for two NULL ptrs (hopefully the (*) & (**) above).
X * If it finds such a portion, it continues backwards, counting ptrs until:
X * a) either a word is found that has as its value the count (supposedly argc),
X * b) another NULL word is found, in which case the algorithm is reiterated, or
X * c) we wind up before the start of the buffer and fail.
X * Upon success, get_args returns a pointer to the conactenated arg list.
X * Warning: this routine is inherently unreliable and probably doesn't work if
X * ptrs and ints have different sizes.
X */
Xchar *get_args(bufp)
Xstruct pstat *bufp;
X{
X	union {
X#if (CHIP == M68000)
X		long stk_i;
X#else
X		int stk_i;
X#endif
X		char *stk_cp;
X		char stk_c;
X	} stk[ARG_MAX / sizeof(char *)], *sp;
X	enum {INITIAL, DONE, FAIL, NULL1, NULL2} state;
X	int nargv;		/* guessed # of (non-NULL) argv pointers seen */
X	int cnt;		/* # of bytes read from stack frame */
X	int neos;		/* # of '\0's seen in argv string space */
X	off_t l;
X	char *cp, *args;
X	
X	
X	if (bufp->ps_ssize < ARG_MAX)
X		cnt = bufp->ps_ssize;
X	else
X		cnt = ARG_MAX;
X	/* get last cnt bytes from user stack */
X	if (addrread(memfd, (phys_clicks) (bufp->ps_stack >> CLICK_SHIFT),
X		     (vir_bytes) (bufp->ps_ssize - cnt),
X		     (char *) stk, cnt) != cnt)
X		return NULL;
X	
X	state = INITIAL;
X	sp = &stk[cnt / sizeof(char *)];
X	while (state != DONE && state != FAIL) {
X		if (--sp < &stk[0])
X			state = FAIL;	/* wound up before start of buffer */
X		switch(state) {
X		case INITIAL:	/* no NULL seen yet */
X			if (sp[0].stk_cp == NULL)
X				state = NULL1;
X			break;
X		case NULL1:	/* one NULL seen */	
X			if (sp[0].stk_cp == NULL) {
X				nargv = 0;	/* start counting argv ptrs */
X				state = NULL2;
X			}	
X			/*
X			 * What follows is a dirty patch to recognize sh's 
X			 * stack frame when it has assigned argv[0] to argv[1],
X			 * and has thus blown away its NULL pointer there.
X			 */
X			else if (sp > &stk[0] && sp[0].stk_cp == sp[-1].stk_cp){
X				nargv = 0;
X				state = NULL2;
X			}
X			break;
X		case NULL2:	/* two NULLs seen */
X			if (sp[0].stk_cp == NULL)
X				nargv = 0;	/* restart counting */
X			else if (sp[0].stk_i == nargv)
X				state = DONE;	/* think i got it */
X			/* next is same ugly patch as above */	
X			else if (sp > &stk[0] && sp[0].stk_cp == sp[-1].stk_cp)
X				nargv = 0;
X			else
X				nargv++;	/* ? some argv pointer ? */
X			break;
X		default:	/* FAIL or DONE */
X			break;
X		}	
X	}
X	
X	if (state != DONE)
X		return NULL;
X
X	/* get a local version of argv[0]; l is offset back from end of stack */
X	l = bufp->ps_stack + bufp->ps_ssize -
X#if (CHIP == INTEL)
X		bufp->ps_data - 
X#endif
X		(vir_bytes) sp[1].stk_cp;
X	if (l < 0 || l > cnt)
X		return NULL;
X	args = &((char *) stk)[cnt - (int) l];
X	neos = 0;
X	for (cp = args; cp < &((char *) stk)[cnt]; cp++)
X		if (*cp == '\0')
X			if (++neos >= sp[0].stk_i)
X				break;
X			else
X				*cp = ' ';	
X	if (neos != sp[0].stk_i)
X		return NULL;			
X
X	return args;
X}
X
X/*
X * Pstat collects info on process number p_nr and returns it in buf.
X * It is assumed that tasks do not have entries in fproc/mproc.
X */
Xint pstat(p_nr, bufp)
Xstruct pstat *bufp;
X{
X	int p_ki = p_nr + NR_TASKS;	/* kernel proc index */
X	
X	if (p_nr < -NR_TASKS || p_nr >= NR_PROCS)
X		return -1;
X	
X	if ((PROC[p_ki].p_flags & P_SLOT_FREE) &&
X	    !(MPROC[p_nr].mp_flags & IN_USE))
X		return -1;
X
X	bufp->ps_flags = PROC[p_ki].p_flags;
X	
X	if (p_nr >= 0) {
X		bufp->ps_dev = FPROC[p_nr].fs_tty;
X		bufp->ps_ftask = FPROC[p_nr].fp_task;
X	}	
X	else {
X		bufp->ps_dev = 0;
X		bufp->ps_ftask = 0;
X	}
X
X	if (p_nr >= 0) {
X		bufp->ps_ruid = MPROC[p_nr].mp_realuid;	
X		bufp->ps_euid = MPROC[p_nr].mp_effuid;	
X		bufp->ps_pid = MPROC[p_nr].mp_pid;
X		bufp->ps_ppid = MPROC[MPROC[p_nr].mp_parent].mp_pid;
X		bufp->ps_pgrp = MPROC[p_nr].mp_procgrp;
X		bufp->ps_mflags = MPROC[p_nr].mp_flags;
X	}
X	else {
X		bufp->ps_pid = bufp->ps_ppid = 0;
X		bufp->ps_ruid = bufp->ps_euid = 0;
X		bufp->ps_pgrp = 0;
X		bufp->ps_mflags = 0;
X	}	
X	
X	/* state is interpretation of combined kernel/mm flags for non-tasks */
X	if (p_nr >= 0) {				/* non-tasks */
X		if (MPROC[p_nr].mp_flags & HANGING)
X			bufp->ps_state = Z_STATE;	/* zombie */
X		else if (MPROC[p_nr].mp_flags & STOPPED)
X			bufp->ps_state = T_STATE;	/* stopped (traced) */
X		else if (PROC[p_ki].p_flags == 0)
X			bufp->ps_state = R_STATE;	/* in run-queue */
X		else if (MPROC[p_nr].mp_flags & (WAITING | PAUSED) ||
X			 FPROC[p_nr].fp_suspended == SUSPENDED)
X			bufp->ps_state = S_STATE;	/* sleeping */
X		else	
X			bufp->ps_state = W_STATE;	/* a short wait */
X	}
X	else {						/* tasks are simple */
X		if (PROC[p_ki].p_flags == 0)
X			bufp->ps_state = R_STATE;	/* in run-queue */
X		else
X			bufp->ps_state = W_STATE;	/* other i.e. waiting */
X	}		
X		
X	bufp->ps_tsize = (size_t) PROC[p_ki].p_map[T].mem_len << CLICK_SHIFT;
X	bufp->ps_dsize = (size_t) PROC[p_ki].p_map[D].mem_len << CLICK_SHIFT;
X	bufp->ps_ssize = (size_t) PROC[p_ki].p_map[S].mem_len << CLICK_SHIFT;
X	bufp->ps_text = (off_t) PROC[p_ki].p_map[T].mem_phys << CLICK_SHIFT;
X	bufp->ps_data = (off_t) PROC[p_ki].p_map[D].mem_phys << CLICK_SHIFT;
X	bufp->ps_stack = (off_t) PROC[p_ki].p_map[S].mem_phys << CLICK_SHIFT;
X	
X	bufp->ps_recv = PROC[p_ki].p_getfrom;
X	
X	bufp->ps_utime = PROC[p_ki].user_time;
X	bufp->ps_stime = PROC[p_ki].sys_time;
X	
X	if (bufp->ps_state == Z_STATE)
X		bufp->ps_args = "<defunct>";
X	else if (p_nr > INIT_PROC_NR)
X		bufp->ps_args = get_args(bufp);
X	
X	return 0;
X}
X
X/*
X * Addrread reads nbytes from offset addr to click base of fd into buf.
X */
Xint addrread(fd, base, addr, buf, nbytes)
Xphys_clicks base;
Xvir_bytes addr;
Xchar *buf;
X{
X	extern long lseek();
X    
X	if (lseek(fd, ((long) base << CLICK_SHIFT) + (long) addr, 0) < 0)
X		return -1;
X
X	return read(fd, buf, nbytes);
X}
X
Xusage(pname)
Xchar *pname;
X{
X	fprintf(stderr, "Usage: %s [-][alxU] [kernel mm fs]\n", pname);
X	exit(1);
X}
X
Xerr(s)
Xchar *s;
X{
X	extern int errno;
X
X	if (errno == 0)
X		fprintf(stderr, "%s\n", s);
X	else
X		perror(s);
X
X	exit(2);
X}	
X
X/*
X * Fill ttyinfo by fstatting character specials in /dev.
X */
Xint gettynames(ttyinfo)
Xttyinfo_t ttyinfo[];
X{
X	static char dev_path[] = "/dev/";
X	DIR *dev_dir;
X	struct dirent *ent;
X	struct stat statbuf;
X	static char path[sizeof(dev_path) + NAME_MAX];
X	int index;
X
X	if ((dev_dir = opendir(dev_path)) == NULL)
X		return -1;
X
X	index = 0;
X	while ((ent = readdir(dev_dir)) != NULL) {
X		strcpy(path, dev_path);
X		strcat(path, ent->d_name);
X		if (stat(path, &statbuf) == -1 || !S_ISCHR(statbuf.st_mode))
X			continue;
X		if (index >= N_TTYINFO)
X			err("Can't keep info on all ttys in /dev");
X		ttyinfo[index].tty_dev = statbuf.st_rdev;
X		strcpy(ttyinfo[index].tty_name, ent->d_name);
X		index++;
X	}
X
X	return 0;
X}
/
echo x - ps.man1
sed '/^X/s///' > ps.man1 << '/'
X#ps
XCommand:   ps - process status (MINIX 1.5.0)
XSyntax:    ps [-][alxU] [kernel mm fs]
XFlags:     -a  Print all processes with controlling terminals
X	   -l  Give long listing
X	   -x  Include processes without a terminal
X	   -U  Update ps's database from the system executables
XExample:  ps -axl	   	 # print all processes and tasks in long format
X	  ps -U /kernel /fs /mm  # update ps's database from system executables
X   Ps prints the status of active processes.  Normally only the caller's own
Xprocesses are listed in short format (the PID, TTY, TIME and CMD fields as
Xexplained below).  The long listing contains:
X  F	  Kernel flags.  001: free slot; 002: no memory map; 004: sending;
X  	  010: receiving; 020: inform on pending signals; 040: pending signals;
X  	  100: being traced.
X  S	  State.  R: runnable; W: waiting (on a message); S: sleeping (i.e.
X  	  suspended on MM or FS); Z: zombie; T: stopped.
X  UID, PID, PPID, PGRP
X	  The user, process, parent process and process group ID's.
X  ADDR, SZ
X	  Decimal address and size of the process in kilobytes.
X  RECV    Process/task on which a receiving process is waiting or sleeping.  If
X  	  sleeping, the suspending call to MM or (possibly pseudo) task in FS
X  	  is also given, e.g., "(xpipe) FS" means process is blocked on a pipe.
X  TTY	  Controlling tty for the process.
X  TIME    Process' cumulative (user + system) execution time.
X  CMD	  Command line arguments of the process.  System processes and tasks
X	  have a mnemonic name.
X   If extra arguments (the kernel, mm and fs non-stripped executables) are
Xgiven, these are used to obtain the system addresses from (instead of the
Xdefault system executables).  This applies to the -U option also.
X   The default system executables are /usr/src/{kernel/kernel,mm/mm,fs/fs};
Xthe database for system addresses is kept in /etc/psdatabase; /dev/{mem,kmem}
Xare used to read the system tables and command line arguments from.
X
/