/*
 * top.c		- control processes
 *
 * Copyright (c) 1992 Roger Binns
 * based entirely on ps which is
 * Copyright (c) 1992 Branko Lankester
 *
 */

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

extern int setpriority __P((int __which, int __who, int __prio));

struct task_struct **task_tbl;

#define RCFILE		".toprc"
#define MAXSCREENWIDTH	200
#define MAXSCREENHEIGHT	100

int newmap = 1;
extern int maxcmd;
/* some dummy variables to get cmdline.c to link */
int kern_comm = 0;
int show_env = 0;
int Idle, freemem;
char hdr[200];
char *title = "TOP   by Roger Binns    Ps (c) 1992 Branko Lankester";
char rcfile[256];

char *cm, *clrtobot, *cl, *so, *se, *clrtoeol, *mb, *md, *us, *ue;
char *vi, *ve;
char *outp;
int vals[NR_TASKS];
int ticks[NR_TASKS];
int interval;
char *outstr;
int corm=1;
int cols, lines;
int maxlines;
struct termios savetty;
struct termios rawtty;
int sleeptime = 2;
char pflags[26];
enum {
	P_PID,		P_PPID,		P_UID,		P_USER,
	P_PCPU,		P_PMEM,		P_TTY,		P_PRI,
	P_NICE,		P_PAGEIN,	P_TSIZ,		P_DSIZ,
	P_SIZE,		P_TRS,		P_SWAP,		P_SHARE,
	P_A,		P_WP,		P_D,		P_RSS,
	P_WCHAN,	P_STAT,		P_TIME,		P_COMMAND,
	P_END
};

void do_it(struct task_struct *task, reg_t *stack, int num);
void do_key(char c);
void do_help(void);
void show_procs(void);

void
show_title(void)
{
	char goodlook[100];

	memset(goodlook, ' ', sizeof goodlook);
	goodlook[(cols - strlen(title)) / 2] = '\0';
	printf("%s%s%s%s%s%s\n", cl, so, goodlook, title, goodlook, se);
}

void
redraw(void)
{
	char *on, *off;

	on = so;
	off = se;

	printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
	    tgoto(cm, 0, 0),
	    on, "q", off, "uit ", on, "k", off, "ill ", on, "n",
	    off, "ice  Sort by ", on, "c", off, "pu or ", on, "m",
	    off, "emory use  ", on, "A-X", off, " Fields ",
	    on, "?", off, " Menu  ", on, "0-9", off, " delay  ^Write",
	    clrtoeol);
	printf("%s%s", tgoto(cm, 0, 1), clrtoeol);
	printf("%s%s%s", tgoto(cm, 0, 2), hdr, clrtoeol);
}

void
sort_vals(void)
{
	int it, highest, i, done = 0;

	while (done < maxlines) {
		it = -1;
		highest = 0;
		for (i = 0; i < NR_TASKS; i++) {
			if (vals[i] < highest)
				continue;
			it = i;
			highest = vals[i];
		}
		if (it < 0)
			return;
		strcat(outstr, outp + it * MAXSCREENWIDTH);
		done++;
		vals[it] = -1;
	}
	outstr[strlen(outstr) - 1] = '\0';
}

char *headers[] = {
	"  PID ",		" PPID ",		" UID ",
	"USER     ",		"%CPU ",		"%MEM ",
	"TTY     ",		"PRI ",			" NI ",
	"PAGEIN ",		"TSIZE ",		"DSIZE ",
	" SIZE ",		" TRS ",		"SWAP ",
	"SHARE ",		"  A ",			" WP ",
	"  D ",			" RSS ",		"WCHAN     ",
	"STAT ",		"  TIME ",		"COMMAND"
};

char *headers2[] = {
	"Process Id",		"Parent Process Id",	"User Id",
	"User Name",		"CPU Usage",		"Memory Usage",
	"Controlling tty",	"Priority",		"Nice Value",
	"Page Fault Count",	"Code Size (kb)",	"Data+Stack Size (kb)",
	"Virtual Image Size (kb)","Resident Text Size (kb)","Swapped kb",
	"Shared Pages (kb)",	"Accessed Page count",	"Write Protected Pages",
	"Dirty Pages",		"Resident Set Size (kb)","Sleeping in Function",
	"Process Status",	"CPU Time",		"Command"
};

void
setp(void)
{				/* set up which fields are printed */
	int i, c = 0;
	for (i = 0; i < P_END; i++)
		c += pflags[i];
	if (!c) {		/* read from toprc */
		int handle, a;
		char c[27];

		handle = open(rcfile, O_RDONLY);
		if (handle < 0)
			for (i = 0; i < P_END; i++)
				pflags[i] = 1;
		else {
			read(handle, c, 27);
			sleeptime = c[0] - '0';
			if (sleeptime < 0)
				sleeptime = 0;
			for (a = 1; a < 27; a++) {
				pflags[a - 1] = c[a] - ('a' + a - 1);
				if (pflags[a - 1])
					pflags[a - 1] = 1;
			}
			close(handle);
		}

	}
	hdr[0] = 0;
	for (i = 0; i < P_END; i++) {
		if (!pflags[i])
			continue;
		strcat(hdr, headers[i]);
	}
	maxcmd = cols - strlen(hdr) + 8;
	if (maxcmd < 5)
		maxcmd = 5;	/*will be ignored */
	hdr[cols] = 0;
}

void 
sigcatch(int i)
{
	printf("%s%s%s\r", tgoto(cm, 0, lines - 1), clrtoeol, ve);
	ioctl(0, TCSETSF, &savetty);
	exit(0);
}

void 
window_init(int i)
{
	struct winsize ws;
	struct sigaction sa;

	cols = lines = 0;
	if (ioctl(1, TIOCGWINSZ, &ws) >= 0) {
		cols = ws.ws_col;
		lines = ws.ws_row;
	}
	if (cols == 0)
		cols = tgetnum("co");
	if (lines == 0)
		lines = tgetnum("li");
	if (cols > MAXSCREENWIDTH - 2)
		cols = MAXSCREENWIDTH - 2;	/* leave space for \n and \0 */
	if (lines > MAXSCREENHEIGHT)
		lines = MAXSCREENHEIGHT;
	maxlines = lines - 3;
	setp();
	redraw();
	sa.sa_handler = window_init;
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGWINCH, &sa, NULL);
}

void 
main(int argc, char *argv[])
{
	char *buffer = 0;
	char *termtype = getenv("TERM");
	int i;
	char c;
	struct termios newtty;
	int _jiffies, jiffies;
	unsigned long av[3], _aver;
	unsigned long _nr_free_pages, _nr_buffers;
	struct timeval tv;
	struct sigaction sa;
	fd_set in;

	if (getenv("HOME")) {
		strcpy(rcfile, getenv("HOME"));
		strcat(rcfile, "/");
	}
	strcat(rcfile, RCFILE);
	if (!termtype) {
		printf("TERM not set\n");
		exit(1);
	}
	close(0);
	if (open("/dev/tty", O_RDONLY))
		printf("stdin is not there\n");
	if (ioctl(0, TCGETS, &savetty) == -1) {
		printf("stdin must be a tty\n");
		exit(1);
	}
	sa.sa_handler = sigcatch;
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGHUP, &sa, NULL);
	sigaction(SIGINT, &sa, NULL);
	newtty = savetty;
	newtty.c_lflag &= ~ICANON;
	newtty.c_lflag &= ~ECHO;
	newtty.c_cc[VMIN] = 1;
	newtty.c_cc[VTIME] = 0;
	if (ioctl(0, TCSETSF, &newtty) == -1) {
		printf("cannot put tty into raw mode\n");
		exit(1);
	}
	ioctl(0, TCGETS, &rawtty);
	outp = (char *) calloc(NR_TASKS * MAXSCREENWIDTH, 1);
	outstr = (char *) calloc(MAXSCREENWIDTH * MAXSCREENHEIGHT, 1);
	tgetent(buffer, termtype);
	cm = tgetstr("cm", 0);
	clrtobot = tgetstr("cd", 0);
	cl = tgetstr("cl", 0);
	so = tgetstr("so", 0);
	se = tgetstr("se", 0);
	mb = tgetstr("mb", 0);
	md = tgetstr("md", 0);
	us = tgetstr("us", 0);
	ue = tgetstr("ue", 0);
	vi = tgetstr("vi", 0);
	ve = tgetstr("ve", 0);
	clrtoeol = tgetstr("ce", 0);
	if (vi == NULL)
		vi = ve = "";
	open_psdb();
#ifdef USE_MMAP
	if (mmap_init() < 0)
		exit(1);
	task_tbl = KPTR(k_addr("_task"));
#endif
	_jiffies = k_addr("_jiffies");
	_aver = k_addr("_avenrun");
	_nr_free_pages = k_addr("_nr_free_pages");
	_nr_buffers = k_addr("_nr_buffers");
	read_globals();
	window_init(0);
	printf("%s", vi);
	jiffies = get_kword(_jiffies);
	for (;;) {
		interval = -jiffies;
		jiffies = get_kword(_jiffies);
		interval += jiffies;
		Idle = freemem = 1000;
		show_procs();
		outstr[0] = 0;
		sort_vals();
		if (Idle < 0)
			Idle = 0;
		freemem = (get_kword(_nr_free_pages) + get_kword(_nr_buffers) * 4)
		    * 100 / nr_pages;
		if (freemem < 0)
			freemem = 0;
		/* can't have two tgoto's - they use a static buffer */
		printf("%s%3d%% Idle,%3d%% free core - sorted by %s",
		    tgoto(cm, 0, 1), Idle / 10, freemem / 10,
		    corm ? "CPU use    " : "Memory use ");
		kmemread(av, _aver, sizeof av);
		for (i = 0; i < 3; ++i) {
			av[i] *= 100;
			av[i] >>= 11;
		}
		printf("%s", tgoto(cm, cols - 30, 1));
		printf("load avg: %2ld.%02ld, %2ld.%02ld, %2ld.%02ld",
		    av[0] / 100, av[0] % 100,
		    av[1] / 100, av[1] % 100,
		    av[2] / 100, av[2] % 100);
		printf("%s%s%s", tgoto(cm, 0, 3), outstr, clrtobot);
		printf("%s", tgoto(cm, 0, lines - 1));
		fflush(stdout);

		tv.tv_sec = sleeptime;
		tv.tv_usec = 0;
		FD_ZERO(&in);
		FD_SET(0, &in);
		if (select(16, &in, 0, 0, &tv) > 0)
			if (read(0, &c, 1) == 1) {
				printf("%s", ve);
				do_key(c);
				printf("%s", vi);
			}
#ifdef USE_MMAP
		upd_cnt += sleeptime;
		if (upd_cnt >= 15)
			mmap_cleanup();
#endif
	}
	close_psdb();
}

void 
do_key(char c)
{
	int npid, nval;
	char spid[100];
	int gid;

	if (c == '?') {
		do_help();
		return;
	}
	if (c >= 'A' && c <= 'Z') {
		pflags[c - 'A'] = 1 - pflags[c - 'A'];
		setp();
		redraw();
		return;
	}
	ioctl(0, TCSETS, &savetty);
	c = toupper(c);
	if (c == 'N' || c == 'K') {
		printf("%s%s", tgoto(cm, 0, 1), clrtoeol);
		if (c == 'K')
			printf("Kill pid:");
		else
			printf("Nice pid:");
		if (fgets(spid, sizeof spid, stdin) == NULL || *spid == '\n')
			goto ex;
		npid = atoi(spid);

		printf("%s%s", tgoto(cm, 0, 1), clrtoeol);
		printf((c == 'N') ? "Nice pid: %d Value:" : "Kill pid: %d signal:", npid);
		if (fgets(spid, sizeof spid, stdin) == NULL)
			goto ex;
		if (*spid == '\n')
			nval = (c == 'N' ? 5 : 9);
		else
			nval = atoi(spid);
		printf("%s%s", tgoto(cm, 0, 1), clrtoeol);
		printf((c == 'N') ? "Nice pid: %d Value: %d" : "Kill pid: %d signal: %d", npid, nval);
		errno = 0;
		if (c == 'N')
			setpriority(PRIO_PROCESS, npid, nval);
		else
			kill(npid, nval);
		if (errno) {
			printf("%s%s", tgoto(cm, 0, 1), clrtoeol);
			printf("\007%s: %s", c == 'N' ? "nice" : "kill",
			    strerror(errno));
			fflush(stdout);
			sleep(3);
		}
	}
	else if (c == 'L' - 'A' + 1)
		redraw();	/* ctrl L */
	else if (c == 'W' - 'A' + 1) {	/* ctrl W */
		int handle, a;
		char c[27];

		gid = getegid();
		setgid(getgid());
		handle = open(rcfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
		setgid(gid);
		if (handle < 0) {
			perror("top");
			exit(1);
		}
		c[0] = '0' + sleeptime;
		for (a = 0; a < 26; a++)
			c[a + 1] = (pflags[a]) ? ('A' + a) : ('a' + a);
		if (write(handle, c, 27) != 27) {
			perror("top");
			exit(1);
		}
		close(handle);
	}
	else if (c == 'M')
		corm = 0;
	else if (c == 'C')
		corm = 1;
	else if (c == 'F')
		kern_comm ^= 1;
	else if (c == 'Q')
		kill(getpid(), 2);
	else if (c >= '0' && c <= '9') {
		sleeptime = c - '0';
		printf("%s%s", tgoto(cm, 0, 1), clrtoeol);
		printf(sleeptime ? "Update now done every %d seconds\n"
		    : "No delays in updates now\n", sleeptime);
		sleep(2);
	}
	else
		printf("%c", (char) 7);
      ex:
	printf("%s%s", tgoto(cm, 0, 1), clrtoeol);
	ioctl(0, TCSETS, &rawtty);
}

void
do_help(void)
{
	int i, changed = 0;
	int row, col;
	char *p, c;

	show_title();
	for (i = 0; i < sizeof headers / sizeof headers[0]; ++i) {
		row = i % (lines - 3) + 3;
		col = i / (lines - 3) * 40;
		printf("%s", tgoto(cm, col, row));
		for (p = headers[i]; *p == ' '; ++p);
		printf("%c %c: %-10s = %s", pflags[i] ? '*' : ' ', i + 'A',
		    p, headers2[i]);
	}
	for (;;) {
		printf("%sToggle fields with %sa-x%s, any other key to return: ",
		    tgoto(cm, 0, 1), so, se);
		fflush(stdout);
		read(0, &c, 1);
		i = toupper(c) - 'A';
		if (i >= 0 && i < sizeof headers / sizeof headers[0]) {
			row = i % (lines - 3) + 3;
			col = i / (lines - 3) * 40;
			printf("%s", tgoto(cm, col, row));
			if (pflags[i] ^= 1)
				putchar('*');
			else
				putchar(' ');
			changed = 1;
		}
		else
			break;
	}
	if (changed)
		setp();
	redraw();
}

void
show_procs(void)
{
	struct task_struct *taskp;
	int i;

#ifndef USE_MMAP
	struct task_struct task_buf;
	struct tty_struct tty_buf;
	char stack_buf[PAGE_SIZE];
	off_t _task = k_addr("_task");
#endif

	newmap = 1;
	for (i = 1; i < NR_TASKS; ++i) {
		vals[i] = -1;
		outp[MAXSCREENWIDTH * i] = 0;
#ifdef USE_MMAP
		taskp = task_tbl[i];
		if (taskp) {
			mmap_page(taskp);
			taskp = KPTR(taskp);
			/* check if valid, proc may have exited */
			if ((unsigned) taskp->state > 4 ||
			    taskp->pid <= 0 && i != 0 ||
			    !taskp->kernel_stack_page)
				continue;
			mmap_page(taskp->kernel_stack_page);
			do_it(taskp, KPTR(taskp->kernel_stack_page), i);
		}
#else
		kmemread(&taskp, _task + 4 * i, sizeof(taskp));
		if (taskp) {
			if (kmemread(&task_buf, (unsigned long) taskp,
				     sizeof(task_buf)) < 0)
				continue;
			/* check if valid, proc may have exited */
			if ((unsigned) task_buf.state > 4 ||
			    (task_buf.pid <= 0 && i != 0) ||
			    !task_buf.kernel_stack_page)
				continue;
			if (kmemread(&stack_buf, task_buf.kernel_stack_page, PAGE_SIZE) < 0)
				continue;
			if (task_buf.tty) {
				if (kmemread(&tty_buf, (unsigned long) task_buf.tty, sizeof(tty_buf)) < 0)
					continue;
				task_buf.tty = &tty_buf;
			}
			do_it(&task_buf, (reg_t *) &stack_buf, i);
		}
#endif
	}
}

void 
do_it(struct task_struct *task, reg_t *stack, int num)
{
	unsigned t, i;
	int pcpu, pmem, p_ticks;
	long ppid;
	struct mem_info *mi = NULL;
	char tmp[200];

	if (task->pid == 0)
		return;
	kmemread(&ppid, (unsigned long) &task->p_opptr->pid, 4);
	t = task->utime + task->stime;
	if ((p_ticks = t - ticks[num]) < 0)
		p_ticks = t;	/* new process */
	ticks[num] = t;
	t /= HZ;

	if (interval)
		pcpu = p_ticks * 1000 / interval;
	else
		pcpu = p_ticks;

	if (pflags[P_TRS] || pflags[P_SWAP] || pflags[P_SHARE] ||
	    pflags[P_A] || pflags[P_WP] || pflags[P_D]) {
		if (newmap) {
			get_memmap();
			newmap = 0;
		}
		mi = get_mem_info(task);
		pmem = mi->pmem;
	}
	else
		pmem = task->mm->rss * 1000 / nr_pages;
	if (pcpu > 999)
		pcpu = 999;
	Idle -= pcpu;
	freemem -= pmem;
	if (corm)
		vals[num] = (pcpu << 8) + 256 - task->counter;
	else
		vals[num] = pmem;

	for (i = 0; i < P_END; i++) {
		if (!pflags[i])
			continue;
		tmp[0] = 0;
		switch (i) {
		case P_PID:
			sprintf(tmp, "%5d ", task->pid);
			break;
		case P_PPID:
			sprintf(tmp, "%5ld ", ppid);
			break;
		case P_UID:
			sprintf(tmp, "%4d ", task->euid);
			break;
		case P_USER:
			sprintf(tmp, "%-8.8s ", user_from_uid(task->euid));
			break;
		case P_PCPU:
			sprintf(tmp, "%2d.%1d ", pcpu / 10, pcpu % 10);
			break;
		case P_PMEM:
			sprintf(tmp, "%2d.%1d ", pmem / 10, pmem % 10);
			break;
		case P_TTY:
			sprintf(tmp, "%-7.7s ", dev_to_tty(TASK_TTY(task)));
			break;
		case P_PRI:
			sprintf(tmp, "%3ld ", 2 * PZERO - task->counter);
			break;
		case P_NICE:
			sprintf(tmp, "%3ld ", PZERO - task->priority);
			break;
		case P_PAGEIN:
			sprintf(tmp, "%6ld ", task->mm->maj_flt);
			break;
		case P_TSIZ:
			sprintf(tmp, "%5ld ", task->mm->end_code / 1024);
			break;
		case P_DSIZ:
			sprintf(tmp, "%5ld ", SIZE(task, stack));
			break;
		case P_SIZE:
			sprintf(tmp, "%5ld ", VSIZE(task, stack));
			break;
		case P_TRS:
			sprintf(tmp, "%4d ", mi->trs << 2);
			break;
		case P_SWAP:
			sprintf(tmp, "%4d ", mi->swap << 2);
			break;
		case P_SHARE:
			sprintf(tmp, "%5d ", mi->share << 2);
			break;
		case P_A:
			sprintf(tmp, "%3d ", mi->acc);
			break;
		case P_WP:
			sprintf(tmp, "%3d ", mi->wp);
			break;
		case P_D:
			sprintf(tmp, "%3d ", mi->dirt);
			break;
		case P_RSS:
			sprintf(tmp, "%4ld ", task->mm->rss * 4);
			break;
		case P_WCHAN:
			sprintf(tmp, "%-9.9s ",
			    (task->state == TASK_INTERRUPTIBLE ||
				task->state == TASK_UNINTERRUPTIBLE ?
				wchan(task->tss.ebp, stack, 0) : ""));
			break;
		case P_STAT:
			sprintf(tmp, "%-4.4s ", status(task));
			break;
		case P_TIME:
			sprintf(tmp, "%3d:%02d ", t / 60, t % 60);
			break;
		case P_COMMAND:
			strcpy(tmp, cmd_args(task));
			break;
		}
		strcat(outp + num * MAXSCREENWIDTH, tmp);
	}
	outp[num * MAXSCREENWIDTH + cols] = 0;
	strcat(outp + num * MAXSCREENWIDTH, clrtoeol);
	strcat(outp + num * MAXSCREENWIDTH, "\n");
}
