/*
 * ps.c		- show process status
 *
 * Copyright (c) 1992 Branko Lankester
 *
 */

#define __KERNEL__
#include <sys/types.h>
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <sys/ioctl.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include "ps.h"
#include "psdata.h"

#define	PS_D	0	/* default format (short) */
#define	PS_L	1	/* long format */
#define	PS_U	2	/* user format */
#define	PS_J	3	/* jobs format */
#define	PS_S	4	/* signal format */
#define	PS_V	5	/* vm format */
#define	PS_M	6	/* mem. stuff */
#define	PS_X	7	/* regs etc., for testing */
#define	PS_Y	8	/* system call */

char *hdrs[] = {
"  PID TTY     STAT  TIME COMMAND",
"  UID   PID  PPID PRI  NI SIZE  RSS WCHAN      STAT  TTY       TIME COMMAND",
"USER       PID %CPU %MEM SIZE  RSS TTY     STAT  START   TIME COMMAND",
" PPID   PID  PGID   SID TTY     TPGID  STAT   UID   TIME COMMAND",
#ifdef OLD_SIG_FMT
"  UID   PID SIGNAL   BLOCKED  IGNORED  CAUGHT   STAT  TTY       TIME COMMAND",
#else
"  UID   PID 12345678901234567890123456789012 STAT  TTY       TIME COMMAND",
#endif
"  PID TTY     STAT   TIME PAGEIN TSIZ DSIZ  RSS   LIM %MEM COMMAND",
"  PID TTY     MAJOR MINOR  TRS  DRS SIZE SWAP  RSS SHRD  LIB  DT COMMAND",
" F   PID    STACK      ESP      EIP TMOUT ALARM STAT  TTY       TIME COMMAND",
"  PID TTY     STAT      EIP SYSTEM CALL                      TIME COMMAND",
};

typedef void format_function(char *s, struct task_struct *task, reg_t *stack);

format_function show_short, show_long, show_user, show_jobs,
	show_sig, show_vm, show_m, show_regs, show_syscall;

format_function *fmt_fnc[] = {
    show_short, show_long, show_user, show_jobs,
    show_sig, show_vm, show_m, show_regs, show_syscall,
};

char *sighdrs[] = {
#include "sighdrs.h"
};

#define	KSTK(reg,stack)	(((unsigned long *)(stack))[1007+(reg)])

long time_now;

/*
 * command line options
 */
int fmt;
int all = 0;
int kern_comm = 0;
int no_ctty = 0;
int run_only = 0;
char *ctty = NULL;
int npid = 0;
int *pids;
int show_env = 0;
int num_outp = 0;	/* numeric fields for user or wchan */
int first_task = 1;	/* don't show task 0 */
int pg_shift = 2;	/* default: show k instead of pages */
int Sum = 0;
int forest = 0;
int maxcols;

struct tree_node {
    pid_t pid;
    pid_t ppid;
    char *line;
    char *cmd;
    int children;
    int maxchildren;
    int *child;
    int have_parent;
} *node;
int nodes = 0;
int maxnodes = 0;

void add_node(char *s, char *cmd, struct task_struct *task);
void show_forest(void);

void set_maxcmd(int w_opts);
void show_procs(void);
char *sprint_time(char *s, struct task_struct *task);

void
usage(FILE *ofp, int exitval)
{
    fprintf(ofp, "\
usage: ps 0acefhHjlmnprsStuUvwxXy [t tty] [pid[,pid]...]\n\
\n\
j -- job control format, l -- long format, m -- memory format,\n\
s -- signal format, u -- user format, v -- virtual memory format,\n\
X -- register format, y -- syscall format, default -- short format\n\
\n\
0 -- include task zero, a -- include processes for all users\n\
c -- print only command name, e -- show env vars, f -- print forest of procs\n\
h -- suppress the header, n -- print the user and wchan fields numerically\n\
p -- print counts in pages, r -- restrict listing to runnable processes\n\
S -- summarize usage for children in CPU time and page fault fields\n\
t tty -- show all proccesses with TTY as their controlling terminal\n\
w -- wide: 132, ww -- 264, www -- unlimited, default -- screen width or 80\n\
x -- include processes without a controlling terminal\n\
U [SYSTEM-PATH [SWAP-PATH]] -- update ps database\n\
\n\
STATUS key: R -- running, S -- sleeping, I -- idle, D -- uninterruptible,\n\
Z -- zombie, T -- stopped, P -- paging, W -- swapped out, < -- high priority,\n\
N -- niced, X -- debugged, x -- traced, s -- session leader, + -- foreground\n\
\n\
SIGNAL key: P -- pending, B -- blocked, I -- ignored, C -- caught\n\
");
    exit(exitval);
}

int
main(int argc, char *argv[])
{
    char *p;
    int no_header = 0;
    int fopt = 0;
    int width = 0;
    int Update = 0;
    int uid = geteuid();
    int i;

    /* Root may need his/her output faster than the average bear. */
    if (uid == 0)
	nice(-40);

repeat:
    if (argc > 1) {
	for (p = argv[1]; *p; ++p) {
	    switch (*p) {
		case 'l': fmt = PS_L; ++fopt; break;
		case 'u': fmt = PS_U; ++fopt; break;
		case 'j': fmt = PS_J; ++fopt; break;
		case 's': fmt = PS_S; ++fopt; break;
		case 'v': fmt = PS_V; ++fopt; break;
		case 'm': fmt = PS_M; ++fopt; break;
		case 'X': fmt = PS_X; ++fopt; break;
		case 'y': fmt = PS_Y; ++fopt; break;
		case 'a': all = 1; break;
		case 'c': kern_comm = 1; break;
		case 'x': no_ctty = 1; break;
		case 't': ctty = p + 1; break;
		case 'r': run_only = 1; break;
		case 'e': show_env = 1; break;
		case 'w': ++width; break;
		case 'h': no_header = 1; break;
		case 'n': num_outp = 1; break;
		case 'S': Sum = 1; break;
		case 'p': pg_shift = 0; break;
		case 'U': Update = 1; break;
		case 'f': forest = 1; break;
		case '0':
		    first_task = 0;
		    no_ctty = 1;
		    if (uid != 0)
			all = 1;
		    break;
#ifdef DEBUG
		case 'd': ++Debug; break;
#endif
		case 'g':	/* old flag, ignore */
		case ',':
		case '-': break;
		case '?':
		case 'H':
		    usage(stdout, 0);
		    break;
		default:
		    if (npid)
			usage(stderr, 1);
		    /* expect comma separated list of pids */
		    pids = (int *) xmalloc(strlen(p)*2+2);
		    while (isdigit(*p)) {
			pids[npid++] = atoi(p);
			while (isdigit(*p))
			    ++p;
			if (*p != ',')
			    break;
			++p;
		    }
		    if (*p)
			usage(stderr, 1);
		    break;
	    }
	    if (ctty || npid)
		break;		/* pid and tty always last */
	}
	if (fopt > 1) {
	    fprintf(stderr, "ps: specify only one of j,l,s,u,v,X,y\n");
	    exit(1);
	}
    }
    if (argc > 2 && argv[2][0] != '/') {
	++argv;
	--argc;
	goto repeat;
    }

    /*
     * only allow different namelist if already read access to /dev/kmem
     */
    if (argc > 2 && access(kmem_path, R_OK)) {
	perror(kmem_path);
	exit(1);
    }

    if (argc > 3)
	swappath[0] = argv[3];

    if (open_sys(argc > 2 ? argv[2] : NULL, Update) == -1) {
	perror(argc > 2 ? argv[2] : "cannot open psdatabase");
	exit(1);
    }

    if (Update)
	exit(0);

#ifdef USE_MMAP
    mmap_init();
#endif
    set_maxcmd(width);
    read_globals();
    time_now = time(0L);
    if (!no_header) {
	if (fmt == PS_S) {
		for (i = 0; i < sizeof(sighdrs)/sizeof(sighdrs[0]); i++)
			printf("%12s%s\n", "", sighdrs[i]);
	}
	puts(hdrs[fmt]);
    }
    show_procs();
    exit(0);
}

/*
 * set maximum chars displayed on a line
 */
void
set_maxcmd(int w_opts)
{
    struct winsize win;

    maxcols = 80;
    if (ioctl(1, TIOCGWINSZ, &win) != -1 && win.ws_col > 0)
	maxcols = win.ws_col;
    switch (w_opts) {
	case 0: break;
	case 1: maxcols += 52; break;	/* 80 -> 132 */
	case 2: maxcols *= 2; break;
	default: maxcols = MAXCMD;
    }
    maxcmd = maxcols - strlen(hdrs[fmt]) + 6;
}

void
show_procs(void)
{
    struct task_struct *taskp;
    struct task_struct task_buf;
    struct tty_struct tty_buf;
    char stack_buf[PAGE_SIZE];
    char s[80];
    char *cmd;
    int tty, i, uid, j;
    off_t _task = k_addr("_task");

    uid = getuid();

    tty = ctty ? tty_to_dev(ctty) : -1;

    for (i = first_task; i < NR_TASKS; ++i) {
	kmemread(&taskp, _task + 4*i, 4);
	if (taskp) {
	    kmemread(&task_buf, (unsigned long) taskp, sizeof(task_buf));

	    /* check if valid, proc may have exited */
	    if ((unsigned) task_buf.state > 5 ||
		    (task_buf.pid <= 0 && i != 0) ||
		    !task_buf.kernel_stack_page)
		continue;

	    /* dereference task sub-structures into our address space */
	    if (task_buf.tty) {
		kmemread(&tty_buf, (unsigned long) task_buf.tty,
			 sizeof(tty_buf));
		task_buf.tty = &tty_buf;
	    }

	    if (task_buf.pid == 0)
		task_buf.mm->rss = 0;

	    if (npid) {
		for (j = 0; j < npid; ++j)
		    if (task_buf.pid == pids[j])
			break;
		if (j == npid)
		    continue;
	    } else if (ctty) {
		if (TASK_TTY(&task_buf) != tty)
		    continue;
	    } else
		if ((!all && task_buf.uid != uid) ||
		    (!no_ctty && TASK_TTY(&task_buf) == -1) ||
		    (run_only && task_buf.state != TASK_RUNNING &&
			    task_buf.state != TASK_UNINTERRUPTIBLE))
			continue;

	    kmemread(&stack_buf, task_buf.kernel_stack_page, PAGE_SIZE);
	    (fmt_fnc[fmt])(s, &task_buf, (reg_t *) &stack_buf);
	    if (fmt != PS_V && fmt != PS_M)
		sprint_time(s + strlen(s), &task_buf);
	    cmd = cmd_args(&task_buf);
	    if (forest)
		add_node(s, cmd, &task_buf);
	    else
		printf("%s %s\n", s, cmd);
	}
    }
    if (forest)
	show_forest();
}

void
add_node(char *s, char *cmd, struct task_struct *task)
{
    if (maxnodes == 0) {
    	maxnodes = 64;
	node = (struct tree_node *)
	    malloc(sizeof(struct tree_node) * maxnodes);
    }
    if (nodes > maxnodes) {
    	maxnodes *= 2;
	node = (struct tree_node *)
	    realloc(node, sizeof(struct tree_node) * maxnodes);
    }
    node[nodes].pid = task->pid;
    node[nodes].ppid = get_kword((unsigned long) &task->p_pptr->pid);
    node[nodes].line = strdup(s);
    node[nodes].cmd = strdup(cmd);
    node[nodes].children = 0;
    node[nodes].have_parent = 0;
    nodes++;
}

int
node_cmp(const void *s1, const void *s2)
{
    struct tree_node *n1 = (struct tree_node *) s1;
    struct tree_node *n2 = (struct tree_node *) s2;

    return n1->pid - n2->pid;
}

void
show_tree(int n, int depth, char *continued)
{
    int i;
    int cols;

    cols = printf("%s ", node[n].line);
    for (i = 0; i < depth; i++) {
	if (cols + 4 >= maxcols - 1)
	    break;
	if (i == depth - 1)
	    printf(" \\_ ");
	else if (continued[i])
	    printf(" |  ");
	else
	    printf("    ");
	cols += 4;
    }
    printf("%-.*s\n", maxcols - cols - 1, node[n].cmd);
    for (i = 0; i < node[n].children; i++) {
	continued[depth] = i != node[n].children - 1;
	show_tree(node[n].child[i], depth + 1, continued);
    }
}

void
show_forest(void)
{
    int i;
    int parent;
    struct tree_node n;
    struct tree_node *p;
    char continued[1024];

    qsort((void *) node, nodes, sizeof(struct tree_node), node_cmp);
    for (i = 0; i < nodes; i++) {
	if (node[i].ppid > 1 && node[i].pid != node[i].ppid) {
	    n.pid = node[i].ppid;
	    p = (struct tree_node *)
		bsearch((void *) &n, (void *) node, nodes,
		sizeof(struct tree_node), node_cmp);
	    parent = p ? p - node : -1;
	}
	else
	    parent = -1;
	if (parent >= 0) {
	    node[i].have_parent++;
	    if (node[parent].children == 0) {
		node[parent].child = (int *) malloc(16 * sizeof(int *));
		node[parent].maxchildren = 16;
	    }
	    else if (node[parent].children == node[parent].maxchildren) {
		node[parent].maxchildren *= 2;
		node[parent].child = (int *) realloc(node[parent].child,
						     node[parent].maxchildren
						     * sizeof(int *));
	    }
	    node[parent].child[node[parent].children++] = i;
	}
    }
    for (i = 0; i < nodes; i++) {
	if (!node[i].have_parent)
	    show_tree(i, 0, continued);
    }
}

void
show_short(char *s, struct task_struct *task, reg_t *stack)
{
    sprintf(s, "%5d %-7s %-4s",
	task->pid,
	dev_to_tty(TASK_TTY(task)),
	status(task));
}

void
show_long(char *s, struct task_struct *task, reg_t *stack)
{
    sprintf(s, "%5d %5d %5ld %3ld %3ld %4ld %4ld %-10.10s %-5s %-7s ",
	task->euid,
	task->pid,
	get_kword((unsigned long) &task->p_pptr->pid),
#if 0
	task->counter,
	task->priority,
#else
	2 * PZERO - task->counter,	/* sort of priority */
	PZERO - task->priority,		/* nice value */
#endif
	VSIZE(task,stack),
	task->mm->rss * 4,
	(task->state == TASK_INTERRUPTIBLE ||
	 task->state == TASK_UNINTERRUPTIBLE ||
	 (Debug > 1 && task->state == TASK_STOPPED) ? 
	    wchan(task->tss.ebp, stack, num_outp) : ""),
	status(task),
	dev_to_tty(TASK_TTY(task)));
}

void
show_jobs(char *s, struct task_struct *task, reg_t *stack)
{
    sprintf(s, "%5ld %5d %5d %5d %-7s %5d  %-4s %5d ",
	get_kword((unsigned long) &task->p_pptr->pid),
	task->pid,
	task->pgrp,
	task->session,
	dev_to_tty(TASK_TTY(task)),
	TASK_TTY_PGRP(task),
	status(task),
	task->euid);
}

void
show_user(char *s, struct task_struct *task, reg_t *stack)
{
    time_t start;
    int pcpu, pmem;
    unsigned long proc_jiffies;

    if (num_outp)
	s += sprintf(s, "%5d    ", task->euid);
    else
	s += sprintf(s, "%-8s ", user_from_uid(task->euid));

    jiffies = get_kword(k_addr("_jiffies"));
    proc_jiffies = jiffies - task->start_time;
    start = time_now - proc_jiffies / HZ;
    if (proc_jiffies == 0)
	pcpu = 500;
    else
	pcpu = (double) (task->utime + task->stime) * 1000 / proc_jiffies;
    if (pcpu >= 1000)
	pcpu = 999;
    pmem = task->mm->rss * 1000 / nr_pages;

    sprintf(s, "%5d %2d.%d %2d.%d %4ld %4ld %-7s %-5s%.6s ",
	task->pid,
	pcpu / 10, pcpu % 10,
	pmem / 10, pmem % 10,
	VSIZE(task,stack),
	task->mm->rss * 4,
	dev_to_tty(TASK_TTY(task)),
	status(task),
 	ctime(&start) + (time_now - start > 3600*24 ? 4 : 10));
}


char *
sigstring(long unsigned int pending, long unsigned int blocked, long unsigned int ignored, long unsigned int caught)
{
	static char buf[33];
	int i;
	unsigned long mask;
	char *s;

	for (i = 0, mask = 1, s = buf; i < 32; i++, mask <<= 1, s++) {
		if (pending & mask)
			*s = 'P';
		else if (blocked & mask)
			*s = 'B';
		else if (ignored & mask)
			*s = 'I';
		else if (caught & mask)
			*s = 'C';
		else if (i % 2 == 0)
			*s = '.';
		else
			*s = ' ';
	}
	*s = '\0';
	return buf;
}

void
show_sig(char *s, struct task_struct *task, reg_t *stack)
{
    unsigned long sigignore=0, sigcatch=0, bit=1;
    int i;

    for (i=0; i<32; ++i) {
	switch((int) task->sigaction[i].sa_handler) {
	    case 1: sigignore |= bit; break;
	    case 0: break;
	    default: sigcatch |= bit;
	}
	bit <<= 1;
    }
#ifdef OLD_SIG_FMT
    sprintf(s, "%5d %5d %08x %08x %08x %08x %-5s %-7s ",
	task->euid,
	task->pid,
	task->signal,
	task->blocked,
	sigignore,
	sigcatch,
	status(task),
	dev_to_tty(TASK_TTY(task)));
#else
    sprintf(s, "%5d %5d %s %-5s %-7s ",
	task->euid,
	task->pid,
	sigstring(task->signal, task->blocked, sigignore, sigcatch),
	status(task),
	dev_to_tty(TASK_TTY(task)));
#endif
}

void
show_vm(char *s, struct task_struct *task, reg_t *stack)
{
    int pmem;

    s += sprintf(s, "%5d %-7s %-5s",
	task->pid,
	dev_to_tty(TASK_TTY(task)),
	status(task));
    sprint_time(s, task);
    s += strlen(s);
    s += sprintf(s, " %6ld %4ld %4ld %4ld ",
	task->mm->maj_flt + (Sum ? task->mm->cmaj_flt : 0),
	task->mm->end_code / 1024,
	SIZE(task, stack), task->mm->rss*4);
    if (task->rlim[RLIMIT_RSS].rlim_cur == RLIM_INFINITY)
	s += sprintf(s, "   xx ");
    else
	s += sprintf(s, "%5d ", task->rlim[RLIMIT_RSS].rlim_cur / 1024);
    pmem = task->mm->rss * 1000 / nr_pages;
    sprintf(s, "%2d.%d", pmem / 10, pmem % 10);
}

void
show_m(char *s, struct task_struct *task, reg_t *stack)
{
    struct mem_info *mi;

    mi = get_mem_info(task);
    sprintf(s, "%5d %-7s %5ld %5ld %4d %4d %4d %4d %4d %4d %4d %3d", 
	task->pid,
	dev_to_tty(TASK_TTY(task)),
	task->mm->maj_flt + (Sum ? task->mm->cmaj_flt : 0),
	task->mm->min_flt + (Sum ? task->mm->cmin_flt : 0),
	/*task->end_code / 4096 << pg_shift,*/
	mi->trs << pg_shift,
	mi->drs << pg_shift,
	mi->size << pg_shift, 
	mi->swap << pg_shift,
	mi->rss << pg_shift,
	mi->share << pg_shift,
	mi->lrs << pg_shift, 
	mi->dt << pg_shift);
}

char *
prtime(char *s, long unsigned int t, long unsigned int rel)
{
    if (t == 0) {
	sprintf(s, "     ");
	return s;
    }
    if ((long) t == -1) {
	sprintf(s, "   xx");
	return s;
    }
    if ((long) (t -= rel) < 0)
	t = 0;
    
    if (t > 9999)
	sprintf(s, "%5ld", t / 100);
    else
	sprintf(s, "%2ld.%02ld", t / 100, t % 100);
    return s;
}

void
show_regs(char *s, struct task_struct *task, reg_t *stack)
{
    char time1[16];
    char time2[16];

    s += sprintf(s, "%2lx %5d %8lx %8x %8x %s %s %-5s %-7s ",
	task->flags,
	task->pid,
	/**
	task->mm->start_code >> 16,
	**/
	task->mm->start_stack,
	KSTK_ESP(stack),
	KSTK_EIP(stack),
	prtime(time1, task->timeout, jiffies),
	prtime(time2, task->it_real_value, 0),
	status(task),
	dev_to_tty(TASK_TTY(task)));
}

struct {
    int n;
    int fmt_mask;	/* bit mask for output fmt, bit set means decimal */
    char *name;
} syscall_ent[] = {
#include "syscallent.h"
};

char *
syscall_str(int nr, long int *args, int ret_code)
{
    static char buf[1024];
    int i, m;
    char *p;

    if (nr >= sizeof(syscall_ent)/sizeof(syscall_ent[0])) {
	sprintf(buf, "call%d", nr);
	i = 5;
	m = 0;
    } else {
	strcpy(buf, syscall_ent[nr].name);
	i = syscall_ent[nr].n;
	m = syscall_ent[nr].fmt_mask;
    }
    p = buf + strlen(buf);
    *p++ = '(';
    while (--i >= 0) {
	sprintf(p, (m&1) ? "%d" : "%#x", *args++);
	p += strlen(p);
	if (i) {
	    *p++ = ',';
	    *p++ = ' ';
	}
	m >>= 1;
    }
    if (ret_code == -ENOSYS)
	sprintf(p, ")");	/* in systemcall */
    else
	if (nr == ret_code)
	    sprintf(p, ") (= %d)", ret_code);	/* can be restart */
	else
	    sprintf(p, ") = %d", ret_code);	/* leaving syscall */
    return buf;
}

void
show_syscall(char *s, struct task_struct *task, reg_t *stack)
{
    int orig_eax;

    s += sprintf(s, "%5d %-7s %-4.4s %8lx ",
	task->pid, dev_to_tty(TASK_TTY(task)), status(task), KSTK(EIP, stack));
    orig_eax = KSTK(ORIG_EAX, stack);
    if (task->state != TASK_ZOMBIE && orig_eax > 0)
	sprintf(s, "%-30s ",
	    syscall_str(orig_eax, &KSTK(EBX, stack), KSTK(EAX, stack)));
    else if (task->state == TASK_ZOMBIE || Debug <= 1)
	sprintf(s, "%-30s ", "");
    else
	sprintf(s, "%ld  ax:%lx, bx:%lx, cx:%lx, dx:%lx, si:%lx, di:%lx, bp:%lx ",
	    KSTK(ORIG_EAX, stack), KSTK(EAX, stack), KSTK(EBX, stack),
	    KSTK(ECX, stack), KSTK(EDX, stack), KSTK(ESI, stack),
	    KSTK(EDI, stack), KSTK(EBP, stack));
}

char *
sprint_time(char *s, struct task_struct *task)
{
    unsigned t;

    t = (task->utime + task->stime) / HZ;
    if (Sum)
	t += (task->cutime + task->cstime) / HZ;

    sprintf(s, "%3d:%02d", t / 60, t % 60);
    return s;
}
