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

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <fcntl.h>
#include <ctype.h>
#include <linux/sched.h>
#include <termcap.h>
#include <termio.h>
#include "ps.h"

extern int errno;

struct task_struct **task_tbl;

#define RCFILE		".toprc"
#define MAXSCREENWIDTH	160
#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];

void do_it(struct task_struct *task, reg_t *stack, int num);
void do_key(char c);
char *cm, *clrtobot, *cl, *so, *se, *clrtoeol, *mb, *md, *us, *ue;
char *outp;
int vals[NR_TASKS];
int ticks[NR_TASKS];
int interval;
char *outstr;
int corm=1;
int cols, lines;
int maxlines;
struct termio savetty;
struct termio 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 show_title()
{
	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;

	show_title();
	printf("%s", tgoto(cm, 0, 1));	
	printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%c%s%s%s%s%s%s%s%s%s%s", 
		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-",'A'+P_END-1, off, " Fields ",
		on, "?", off, " Menu  ", on, "0-9", off, " delay  ^Write");

	printf("%s%s", tgoto(cm, 0, 4), hdr);	
}

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;
	}
}

char *headers[]={
	"  PID ",		" PPID ",		" UID ",
	"USER     ",		"%CPU ",		"%MEM ",
	"TT ",			"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]);
	}
	strcat(hdr, "\n");
	maxcmd=cols-strlen(hdr)+8;
	if(maxcmd<5) maxcmd=5; /* will be ignored, dunno how cmdline will like it */
	hdr[cols]=0;
}

void sigcatch(int i)
{
	ioctl(0, TCSETAF, &savetty);
	printf(tgoto(cm, 0,lines-1));
	exit(1);
}

void window_init(int i)
{
    struct winsize ws;

    if (ioctl(1, TIOCGWINSZ, &ws) != -1) {
	cols = ws.ws_col;
	lines = ws.ws_row;
    } else {
	cols=tgetnum("co");
	lines=tgetnum("li");
    }
    if(cols>MAXSCREENWIDTH-2)
	cols=MAXSCREENWIDTH-2; /* leave space for \n and \0 */
    if(lines>MAXSCREENHEIGHT)
	lines=MAXSCREENHEIGHT;
    maxlines=lines-6;
    setp();
    redraw();
    signal(SIGWINCH, window_init);
}

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

	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, TCGETA, &savetty) == -1)
		{
			printf("stdin must be a tty\n");
			exit(1);
		}
	signal(SIGHUP, sigcatch);
	signal(SIGINT, sigcatch);
	newtty=savetty;
	newtty.c_lflag&=~ICANON;
	newtty.c_lflag&=~ECHO;
	newtty.c_cc[VMIN]=1;
	newtty.c_cc[VTIME]=0;
	if(ioctl(0, TCSETAF, &newtty)==-1)
		{
			printf("cannot put tty into raw mode\n");
			exit(1);
		}
	ioctl(0, TCGETA, &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);
	clrtoeol=tgetstr("ce", 0);
	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);
	while(1) {
	    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;
	    printf("%s%2d%% Idle, %2d%% free core - sorted by %s",
		tgoto(cm,0,3), Idle/10, freemem/10,
		corm?"CPU use    ":"Memory use "); /* can't have two tgoto's - they use a static buffer */
	    kmemread(av, _aver, sizeof av);
	    for (i=0; i<3; ++i) {
		av[i] *= 100;
		av[i] >>= 11;
	    }
	    printf("%s", tgoto(cm, cols-27, 3));
	    i = printf("load avg: %d.%02d, %d.%02d, %d.%02d%s",
		    av[0] / 100, av[0] % 100,
		    av[1] / 100, av[1] % 100,
		    av[2] / 100, av[2] % 100,
		    clrtoeol);
	    printf("%s%s%s", tgoto(cm,0,5), outstr, clrtobot);
	    
	    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)
		    do_key(c);
#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, TCSETA, &savetty);
	c=toupper(c);
	if(c=='N' || c=='K')
	{
		printf(tgoto(cm, 0, 3));
		printf(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(tgoto(cm, 0, 3));
		printf(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(tgoto(cm, 0, 3));
		printf(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(tgoto(cm, 0,3));
			printf(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(tgoto(cm,0,3));
			printf(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,3), clrtoeol);
	ioctl(0, TCSETA, &rawtty);
}

do_help()
{
    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]);
    }
    while (1) {
	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();
}
			

show_procs()
{
    struct task_struct *taskp;
    int tty, i, uid;
#ifndef USE_MMAP
    struct task_struct task_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, 4);
	if (taskp) {
	    if (kmemread(&task_buf, 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;
	    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;
    	time_t now, start;
    	int pcpu, pmem, p_ticks;
	long ppid;
	struct mem_info *mi;
	char tmp[200];
	
    	if(task->pid==0) return ; 
	kmemread(&ppid, &task->p_pptr->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->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, "%5d ", 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, "%-2.2s ", dev_to_tty(task->tty)); break;
		case P_PRI: sprintf(tmp, "%3d ", 2*PZERO-task->counter); break;
		case P_NICE: sprintf(tmp, "%3d ", PZERO-task->priority); break;
		case P_PAGEIN: sprintf(tmp, "%6d ", task->maj_flt); break;
		case P_TSIZ: sprintf(tmp, "%5d ", task->end_code/1024); break;
		case P_DSIZ: sprintf(tmp, "%5d ", SIZE(task,stack)); break;
		case P_SIZE: sprintf(tmp, "%5d ", 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, "%4d ", task->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");
}
