/*
    Process viewer/killer utility
    Copyright (c) Tudor Hulubei & Andrei Pitis, May 1994

This file is part of UIT (UNIX Interactive Tools)

UIT is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2, or (at your option) any later version.

UIT is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details .

You should have received a copy of the GNU General Public License along with
UIT; see the file COPYING.  If not, write to the Free Software Foundation,
675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <termios.h>

#include "tty.h"
#include "termcap.h"
#include "window.h"
#include "config.h"


#define max(a, b) ((a) >= (b) ? (a) : (b))
#define min(a, b) ((a) <= (b) ? (a) : (b))


int SCREEN_X, SCREEN_Y;

#define MAX_KEYS        2048
#define MAX_PROCESS	1024
#define KILLER_FIELDS	  12


static char PSFields[KILLER_FIELDS][40] =
{
    "TitleForeground",
    "TitleBackground",
    "TitleBrightness",
    "HeaderForeground",
    "HeaderBackground",
    "HeaderBrightness",
    "ScreenForeground",
    "ScreenBackground",
    "ScreenBrightness",
    "StatusForeground",
    "StatusBackground",
    "StatusBrightness"
};

#ifdef HAVE_LINUX
static int PSColors[KILLER_FIELDS] = 
{
    CYAN, BLUE, ON, CYAN, RED, ON, BLACK, CYAN, OFF, CYAN, BLUE, ON
};
#else
static int PSColors[KILLER_FIELDS] = 
{
    BLACK, WHITE, OFF, WHITE, BLACK, ON, WHITE, BLACK, OFF, BLACK, WHITE, OFF
};
#endif

#define TitleForeground			PSColors[0]
#define TitleBackground			PSColors[1]
#define TitleBrightness			PSColors[2]
#define HeaderForeground		PSColors[3]
#define HeaderBackground		PSColors[4]
#define HeaderBrightness		PSColors[5]
#define ScreenForeground		PSColors[6]
#define ScreenBackground		PSColors[7]
#define ScreenBrightness		PSColors[8]
#define StatusForeground		PSColors[9]
#define StatusBackground		PSColors[10]
#define StatusBrightness		PSColors[11]



#ifdef HAVE_LINUX
int LinuxConsole = 1;
int ColorMonitor = 1;
#else
int LinuxConsole = 0;
int ColorMonitor = 0;
#endif


extern char tty_type[];


char cSection[]  = "[UITPS-Color]";
char bwSection[] = "[UITPS-Monochrome]";
int processes;
int PID_index;
int signal_type = 11;	/* index of SIGTERM in signals table */

char homedirectory[PATH_MAX];
char configfile[PATH_MAX] = "";
char TempDirectory[PATH_MAX];
char *header_text;
configuration *config;
char *ps_vect[MAX_PROCESS];
int first_on_screen, current_process;
window *title_win, *header_win, *screen_win, *status_win;
static char title_text[]  =
    " UNIX Interactive Tools 4.2b - Process Viewer/Killer";
static char UitPsModeHelp[256];
static char no_perm[]     = "not owner !";
static char no_proc[]     = "no such process ! (REFRESH recommended)";

struct SIGNAL
{
    char signame[16];
    int  signal;
};

static struct SIGNAL signals[] =
{
    { "SIGHUP ", SIGHUP  },	/*  0 */
    { "SIGINT ", SIGINT  },	/*  1 */
    { "SIGQUIT", SIGQUIT },	/*  2 */
    { "SIGILL ", SIGILL  },	/*  3 */
    { "SIGFPE ", SIGFPE  },	/*  4 */
    { "SIGKILL", SIGKILL },	/*  5 */
    { "SIGUSR1", SIGUSR1 },	/*  6 */
    { "SIGSEGV", SIGSEGV },	/*  7 */
    { "SIGUSR2", SIGUSR2 },	/*  8 */
    { "SIGPIPE", SIGPIPE },	/*  9 */
    { "SIGALRM", SIGALRM },	/* 10 */
    { "SIGTERM", SIGTERM },	/* 11 */
    { "SIGCHLD", SIGCHLD },	/* 12 */
    { "SIGCONT", SIGCONT },	/* 13 */
    { "SIGSTOP", SIGSTOP },	/* 14 */
    { "SIGTSTP", SIGTSTP },	/* 15 */
    { "SIGABRT", SIGABRT },	/* 16 */
    { "SIGTRAP", SIGTRAP }	/* 17 */
};


#define BUILTIN_OPERATIONS	24


#define BUILTIN_CursorUp	0
#define BUILTIN_CursorDown	1
#define BUILTIN_PageUp		2
#define BUILTIN_PageDown        3
#define BUILTIN_Home		4
#define BUILTIN_End		5
#define BUILTIN_NextSignal	6
#define BUILTIN_SIGHUP		7
#define BUILTIN_SIGINT		8
#define BUILTIN_SIGQUIT		9
#define BUILTIN_SIGILL		10
#define BUILTIN_SIGFPE		11
#define BUILTIN_SIGKILL		12
#define BUILTIN_SIGUSR1		13
#define BUILTIN_SIGSEGV		14
#define BUILTIN_SIGUSR2		15
#define BUILTIN_SIGPIPE		16
#define BUILTIN_SIGALRM		17
#define BUILTIN_SIGTERM		18
#define BUILTIN_SIGCHLD		19
#define BUILTIN_SIGCONT		20
#define BUILTIN_KillProcess	21
#define BUILTIN_Refresh		22
#define BUILTIN_Exit		23


#define MAX_BUILTIN_NAME	15

char built_in[BUILTIN_OPERATIONS][MAX_BUILTIN_NAME] =
{
    "<CursorUp>",
    "<CursorDown>",
    "<PageUp>",
    "<PageDown>",
    "<Home>",
    "<End>",
    "<NextSignal>",
    "<SIGHUP>",
    "<SIGINT>",
    "<SIGQUIT>",
    "<SIGILL>",
    "<SIGFPE>",
    "<SIGKILL>",
    "<SIGUSR1>",
    "<SIGSEGV>",
    "<SIGUSR2>",
    "<SIGPIPE>",
    "<SIGALRM>",
    "<SIGTERM>",
    "<SIGCHLD>",
    "<SIGCONT>",
    "<KillProcess>",
    "<Refresh>",
    "<Exit>"
};


void removelog(void)
{
    char name[PATH_MAX];

    sprintf(name, "%s/uitps.1.%d", TempDirectory, getpid());
    remove(name);
    sprintf(name, "%s/uitps.2.%d", TempDirectory, getpid());
    remove(name);
}


void settitle(void)
{
    char temp[256];

    memset(temp, ' ', SCREEN_X);
    memcpy(temp, title_text, strlen(title_text));

    tty_bright(TitleBrightness);
    tty_foreground(TitleForeground);
    tty_background(TitleBackground);

    window_cursormove(title_win, 0, 0);
    window_write(temp, SCREEN_X);
}


void setheader(void)
{
    char temp[256];

    memset(temp, ' ', SCREEN_X);
    memcpy(temp, header_text, strlen(header_text));

    tty_bright(HeaderBrightness);
    tty_foreground(HeaderForeground);
    tty_background(HeaderBackground);

    window_cursormove(header_win, 0, 0);
    window_write(temp, SCREEN_X);
}


void setstatus(char *what)
{
    char temp[256];

    memset(temp, ' ', SCREEN_X);
    if (what)
	memcpy(temp, what, strlen(what));
    else
	memcpy(temp, UitPsModeHelp, strlen(UitPsModeHelp));

    tty_bright(StatusBrightness);
    tty_foreground(StatusForeground);
    tty_background(StatusBackground);

    window_cursormove(status_win, 0, 0);
    window_write(temp, SCREEN_X);	
}


void setsignal(void)
{
    int len = strlen(signals[signal_type].signame);

    tty_bright(StatusBrightness);
    tty_foreground(StatusForeground);
    tty_background(StatusBackground);

    window_cursormove(status_win, 0, SCREEN_X - len - 1);
    window_write(signals[signal_type].signame, len);
}


void free_ps_list(void)
{
    int i;

    for (i = 0; i < MAX_PROCESS; i++)
        if (ps_vect[i])
        {
	    free(ps_vect[i]);
	    ps_vect[i] = NULL;
	}
}


char *read_ps_line(FILE *ps_output, char *line)
{
    char *ok, c;
    int lastchar;

    ok = fgets(line, SCREEN_X + 1, ps_output);
    if (line[lastchar = strlen(line) - 1] == '\n')
        line[lastchar] = 0;
    else
	while ((c = fgetc(ps_output)) != '\n' && c != EOF);
    return ok;
}


int get_PID_index(FILE *ps_output)
{
    int i;
    char *h = header_text;

    if (read_ps_line(ps_output, header_text) == NULL) return -1;
    if (strstr(header_text, "PID") == NULL) return -1;

    for(i = 0; ; i++)
    {
	while (isspace(*h)) h++;
	if (memcmp(h, "PID", 3) == 0) return i;
	while (!isspace(*h)) h++;
    }
}


int kill_process(int process_index)
{
    int i;
    char pidstr[32];
    char *p = ps_vect[process_index];

    if (p == NULL) return 0;

    for (i = 0; i < PID_index; i++)
    {
	while (isspace(*p)) p++;
	if (memcmp(p, "PID", 3) == 0) return i;
	while (!isspace(*p)) p++;
    }

    i = 0;
    while (isspace(*p)) p++;
    while (!isspace(*p)) pidstr[i++] = *p++;
    pidstr[i] = 0;
    return !kill(atoi(pidstr), signals[signal_type].signal);
}


void build_ps_list(FILE *ps_output)
{
    int i = 0;

    do
	ps_vect[i] = (char *)malloc(SCREEN_X + 1);
    while (read_ps_line(ps_output, ps_vect[i++]));

    free(ps_vect[--i]);
    ps_vect[i] = NULL;
    processes = i;
}


void update_process(int process, int update_color)
{
    char temp[256];

    memset(temp, ' ', SCREEN_X);
    memcpy(temp, ps_vect[process], strlen(ps_vect[process]));

    if (update_color)
    {
	if (process == current_process)
	{
	    tty_foreground(ScreenBackground);
	    tty_background(ScreenForeground);
	}
	else
	{
	    tty_foreground(ScreenForeground);
	    tty_background(ScreenBackground);
	}
	tty_bright(ScreenBrightness);
    }

    window_cursormove(screen_win, process - first_on_screen, 0);
    window_write(temp, SCREEN_X);
}


void update_all(void)
{
    int i;

    tty_foreground(ScreenForeground);
    tty_background(ScreenBackground);
    tty_bright(ScreenBrightness);
    window_cursormove(screen_win, 0, 0);

    for (i = first_on_screen;
         i < processes && (i - first_on_screen < SCREEN_Y - 3); i++)
	    if (i != current_process)
		update_process(i, OFF);
	    else
		window_cursormove(screen_win, i - first_on_screen + 1, 0);

    update_process(current_process, ON);
}


void clean_up(void)
{
    free_ps_list();

    if (header_text)
    {
	free(header_text);
	header_text = NULL;
    }

    tty_end();
    tty_clrscr();
    removelog();
}


void fatal(char *postmsg)
{
    clean_up();
    fprintf(stderr, "Fatal error : %s !\n", postmsg);
    removelog();
    exit(1);    
}


int ps(char *args)
{
    FILE *stdout_log, *stderr_log;
    char ps_cmd[PATH_MAX], fname[PATH_MAX], *tty_name = ttyname(1);

    close(1);
    close(2);
    sprintf(fname, "%s/uitps.1.%d", TempDirectory, getpid());
    stdout_log = fopen(fname, "w");
    sprintf(fname, "%s/uitps.2.%d", TempDirectory, getpid());
    stderr_log = fopen(fname, "w");

    if (args)
	sprintf(ps_cmd, "ps %s", args);
    else
	sprintf(ps_cmd, "ps");

    if (system(ps_cmd))
    {
	fclose(stderr_log);
	fclose(stdout_log);
	open(tty_name, O_RDWR);
	open(tty_name, O_RDWR);
	fprintf(stderr, "Invalid command line !\n");
	return 0;
    }

    fclose(stderr_log);
    fclose(stdout_log);
    open(tty_name, O_RDWR);
    open(tty_name, O_RDWR);
    return 1;
}


void panic(int signum)
{
    tcflush(0, TCIOFLUSH);

    switch (signum)
    {
	case SIGHUP:

	    clean_up();
	    /* :-) */
	    fprintf(stderr, "Got SIGHUP !\n");
	    break;

	case SIGINT:

	    clean_up();
	    break;

	case SIGTERM:
	case SIGQUIT:

	    clean_up();
	    fprintf(stderr, "Got %s !\nbye\n",
	    	   (signum == SIGTERM) ? "SIGTERM" : "SIGQUIT");
	    break;

	case SIGSEGV:

	    tty_end();
	    tty_clrscr();
	    fprintf(stderr, "Got SIGSEGV !\n");
	    fprintf(stderr, "Please report to tudor@ulise.cs.pub.ro\n");
	    break;
    }

    removelog();
    exit(1);
}


int main(int argc, char *argv[])
{
    FILE *stdout_log;
    struct key_struct *ks;
    int key, repeat_count;
    char tmp[256], buf[256];
    int sectionptr = 0, i, j, index;
    char *home, *data = NULL, fname[PATH_MAX], *section;
    int need_update, need_update_all, old_current_process;


    termcap_init();
    tty_kbdinit(1);

    signal(SIGTERM, panic);
    signal(SIGINT , panic);
    signal(SIGQUIT, panic);
    signal(SIGSEGV, panic);
    signal(SIGHUP,  panic);

    signal(SIGILL,  SIG_IGN);
    signal(SIGTRAP, SIG_IGN);
    signal(SIGABRT, SIG_IGN);
    signal(SIGUSR1, SIG_IGN);
    signal(SIGUSR2, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
    signal(SIGCONT, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTTOU, SIG_IGN);
    signal(SIGALRM, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGFPE,  SIG_IGN);

    if (home = getenv("HOME"))
    {
        strcpy(homedirectory, home);
        strcpy(configfile, home);
        if (configfile[strlen(configfile) - 1] != '/') strcat(configfile, "/");
    }
    strcat(configfile, ".uitrc.");
    strcat(configfile, tty_type);

    config = configuration_init(configfile);

    if (configuration_getstatus(config) == STATUS_OK)
    {
	sectionptr = configuration_getsectionptr(config,"[Setup]");
        if (sectionptr == -1)
	    puts("[Setup] section missing.");
        else
        {
	    configuration_getfielddata(config, sectionptr, "TempDirectory",
				       &data, 1, DO_SEEK);
	    strncpy(TempDirectory, data ? data : homedirectory, PATH_MAX);
#ifdef HAVE_LINUX
	    configuration_getfielddata(config, sectionptr, "LinuxConsole",
				       &data, 1, DO_SEEK);
	    LinuxConsole = data && strcmp(data, "ON") == 0;
	    configuration_getfielddata(config, sectionptr, "ColorMonitor",
				       &data, 1, DO_SEEK);
	    ColorMonitor = data && strcmp(data, "ON") == 0;
#endif
	}

	sectionptr = configuration_getsectionptr(config, "[UITPS-Setup]");
        if (sectionptr == -1)
	    fprintf(stderr, "[UITPS-Setup] section missing.");
	else
	{
	    configuration_getfielddata(config, sectionptr,
				       "UitPsModeHelp",
				       &data, 1, DO_SEEK);
	    if (data) strncpy(UitPsModeHelp, data, 256);
	}

	section = (LinuxConsole && ColorMonitor) ? cSection : bwSection;

	sectionptr = configuration_getsectionptr(config, section);
	if (sectionptr == -1)
	    fprintf(stderr, "%s section missing.", section);
	else
	    for (i = 0; i < KILLER_FIELDS; i++)
	    {
		configuration_getfielddata(config, sectionptr, PSFields[i],
					   &data, 1, DO_SEEK);
        	if (!data || (index  = tty_getcolorindex(data))== -1)
                    fprintf(stderr, "Invalid %s (%s).\n", PSFields[i], data);
		else
		    PSColors[i] = index;
	    }

        sectionptr = configuration_getsectionptr(config, "[UITPS-Keys]");
        if (sectionptr == -1)
	    fprintf(stderr, "[UITPS-Keys] section missing.");
	else
	{
	    char key_seq[16];
	    char *contents;

	    for (i = 0; i < MAX_KEYS; i++)
	    {
	        configuration_getfielddata(config, sectionptr, key_seq,
					   &contents, 1, NO_SEEK);

		if (*key_seq == 0) break;

		if (contents == NULL) continue;

	        for (j = 0; j < BUILTIN_OPERATIONS; j++)
	            if (strcmp(contents, built_in[j]) == 0)
	                break;

		if (j < BUILTIN_OPERATIONS)
		{
		    if (tty_key_convert(key_seq))
		        tty_key_list_insert(key_seq, (void *)j);
		}
		else
		    fprintf(stderr, "Invalid builtin operation: %s\n",
			    contents);
	    }

	    if (i == MAX_KEYS)
	        fprintf(stderr,
			"Too many key sequences. Only %d are allowed.\n",
			MAX_KEYS);
	}
	configuration_end(config);
    }
    else
    {
        fprintf(stderr, "\nCannot open configuration file %s\n", configfile);
	fprintf(stderr, "See the manual page for details.\n");
	fprintf(stderr, "If your TERM environment variable is, let's say,\n");
	fprintf(stderr, "vt102, your configuration file name should be\n");
	fprintf(stderr, ".uitrc.vt102 . Try renaming .uitrc.console ...\n");
	exit(1);
    }

    tty_getsize(&SCREEN_X, &SCREEN_Y);

    header_text = (char *)malloc(SCREEN_X + 1);

    title_win  = window_init(0, 0, 	      1,	    SCREEN_X);
    header_win = window_init(0, 1, 	      1,	    SCREEN_X);
    screen_win = window_init(0, 2,            SCREEN_Y - 2, SCREEN_X);
    status_win = window_init(0, SCREEN_Y - 1, 1,	    SCREEN_X);

    tty_init();

    first_on_screen = current_process = 0;

restart:

    if (ps(argc > 1 ? argv[1] : NULL) == 0)
    {
	removelog();
	goto end;
    }

    sprintf(fname, "%s/uitps.1.%d", TempDirectory, getpid());
    stdout_log = fopen(fname, "r");
    removelog();

    if ((PID_index = get_PID_index(stdout_log)) == -1)
    {
	fclose(stdout_log);
	goto end;
    }

    free_ps_list();
    build_ps_list(stdout_log);
    fclose(stdout_log);

    tty_foreground(ScreenForeground);
    tty_background(ScreenBackground);
    tty_bright(ScreenBrightness);
    tty_clrscr();

    settitle();
    setstatus(NULL);
    setsignal();
    setheader();

    current_process = min(current_process, processes - 1);

    update_all();

    while (1)
    {
	ks  = tty_getkey(&repeat_count);
	key = (int)ks->aux_data;

	switch (key)
	{
	    case BUILTIN_CursorUp:

	        need_update_all = need_update = 0;
	        while (repeat_count--)
	        {
	    	    if (current_process == 0) break;
	            if (current_process == first_on_screen)
	            {
	                current_process--;
	                first_on_screen--;
	                need_update_all = 1;
	            }
	            else
	            {
	                current_process--;
	                if (!need_update)
	                    update_process(current_process + 1, ON);
	                need_update = 1;
	            }
	        }
	        if (need_update_all) update_all();
	        else
	            if (need_update)
	                update_process(current_process, ON);
	        break;

	    case BUILTIN_CursorDown:

	        need_update_all = need_update = 0;
	        while (repeat_count--)
	        {
	    	    if (current_process < processes - 1)
	    	        current_process++;
	    	    else break;
	            if (current_process - first_on_screen >= SCREEN_Y - 3)
	            {
	                first_on_screen++;
	                need_update_all = 1;
	                continue;
	            }
		    if (!need_update)
	                update_process(current_process - 1, ON);
	            need_update = 1;
	        }
	        if (need_update_all) update_all();
	        else
	            if (need_update)
	                update_process(current_process, ON);
	        break;

	    case BUILTIN_PageUp:

	        if (current_process == 0) break;

		old_current_process = current_process;

	        if (current_process < SCREEN_Y - 3)
	            current_process = first_on_screen = 0;
	        else
	        {
	            current_process -= SCREEN_Y - 3;
	            first_on_screen = max(0, first_on_screen - (SCREEN_Y - 3));
	        }

	        if (processes > SCREEN_Y - 3)
	            update_all();
	        else
	        {
		    update_process(old_current_process, ON);
		    update_process(current_process, ON);
	        }

	        break;

	    case BUILTIN_PageDown:

	        if (current_process == processes - 1) break;

	        old_current_process = current_process;

		if (processes - 1 - first_on_screen < SCREEN_Y - 3)
		    current_process = processes - 1;
		else
		    if (processes - 1 - current_process < SCREEN_Y - 3)
		    {
			current_process = processes - 1;
			first_on_screen = processes - 1 - (SCREEN_Y - 3) + 1;
		    }
		    else
		    {
			current_process += SCREEN_Y - 3;
			first_on_screen = min(first_on_screen + SCREEN_Y - 3,
					      (processes - 1) -
					      (SCREEN_Y - 3) + 1);
		    }

		if (processes > SCREEN_Y - 3)
		    update_all();
		else
		{
		    update_process(old_current_process, ON);
		    update_process(current_process, ON);
		}

	        break;

	    case BUILTIN_Home:

	        if (current_process == 0) break;
	        current_process = first_on_screen = 0;
	        update_all();
	    	break;

	    case BUILTIN_End:

	        if (current_process == processes - 1) break;
	        current_process = processes - 1;
	        first_on_screen = max(0, (processes - 1) - (SCREEN_Y - 3) + 1);
	        update_all();
	    	break;

	    case BUILTIN_NextSignal:

	        signal_type++;
	        signal_type %= sizeof(signals) / sizeof(struct SIGNAL);
	        setsignal();
	        break;

	    case BUILTIN_SIGHUP : signal_type =  0; setsignal(); break;
	    case BUILTIN_SIGINT : signal_type =  1; setsignal(); break;
	    case BUILTIN_SIGQUIT: signal_type =  2; setsignal(); break;
	    case BUILTIN_SIGILL : signal_type =  3; setsignal(); break;
	    case BUILTIN_SIGFPE : signal_type =  4; setsignal(); break;
	    case BUILTIN_SIGKILL: signal_type =  5; setsignal(); break;
	    case BUILTIN_SIGUSR1: signal_type =  6; setsignal(); break;
	    case BUILTIN_SIGSEGV: signal_type =  7; setsignal(); break;
	    case BUILTIN_SIGUSR2: signal_type =  8; setsignal(); break;
	    case BUILTIN_SIGPIPE: signal_type =  9; setsignal(); break;
	    case BUILTIN_SIGALRM: signal_type = 10; setsignal(); break;
	    case BUILTIN_SIGTERM: signal_type = 11; setsignal(); break;
	    case BUILTIN_SIGCHLD: signal_type = 12; setsignal(); break;
	    case BUILTIN_SIGCONT: signal_type = 13; setsignal(); break;

	    case BUILTIN_Refresh:

	    	goto restart;

	    case BUILTIN_Exit:

	        goto end;

	    case BUILTIN_KillProcess:

	        if (!kill_process(current_process))
	        {
	            tty_beep();
	            memset(buf, ' ', SCREEN_X);
		    sprintf(tmp, "Error: %s", (errno==EPERM)?no_perm:no_proc);
		    memcpy(buf, tmp, strlen(tmp));
		    setstatus(buf);
		    errno = 0;
		    tty_getkey(NULL);
		    setstatus(NULL);
		    setsignal();
	        }
	        break;
	}
    }

  end:

    clean_up();
    return 0;
}
