/*  
    Process viewer/killer utility
    Copyright (c) Tudor Hulubei & Andrei Pitis, April 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_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

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.1c - Process Viewer/Killer";
static char status_text[] = " Arrows-MOVE  space-SIGNALS  r-REFRESH  ^C/F10-QUIT   ENTER-KILL";
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 */
};


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, status_text, strlen(status_text));

    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;
    int key, repeat_count;
    char tmp[256], buf[256];
    int sectionptr = 0, i, index;
    char *home, *data = NULL, fname[PATH_MAX], *section;
    int need_update, need_update_all, old_current_process;

    
    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");

    config = configuration_init(configfile);
 
    if (configuration_getstatus(config) == STATUS_OK)
    {
        if ((sectionptr = configuration_getsectionptr(config, "[Setup]")) == -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
	}

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

	if ((sectionptr = configuration_getsectionptr(config, section)) == -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;
	    }
	configuration_end(config);
    }
    else
        fprintf(stderr, "Cannot open configuration file %s.\nUsing defaults.\n", configfile);

    tty_getsize(&SCREEN_X, &SCREEN_Y);

    header_text = 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);

    termcap_init();
    tty_kbdinit();
    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 ((key = tty_getkey(&repeat_count)), 1)
    {
        if (isupper(key)) key = tolower(key);
	switch (key)
	{
	    case -(188 + TTY_FIRST_CONFIG_KEY):		/* hard coded ^y */
	    case -TTY_CURSOR_UP:
	        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 -(22 + TTY_FIRST_CONFIG_KEY):		/* hard coded ^b */
	    case -TTY_CURSOR_DOWN:
	        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 '-':
	    case -TTY_CURSOR_LEFT:
	    case -TTY_PGUP:
	        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 '+':
	    case -TTY_CURSOR_RIGHT:
	    case -TTY_PGDN:
	        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 -(72 + TTY_FIRST_CONFIG_KEY):		/* hard coded ^ku */
	    case -TTY_HOME:
	        if (current_process == 0) break;
	        current_process = first_on_screen = 0;
	        update_all();
	    	break;

	    case -(73 + TTY_FIRST_CONFIG_KEY):		/* hard coded ^kv */
	    case -TTY_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 ' ':
	        signal_type++;
	        signal_type %= sizeof(signals) / sizeof(struct SIGNAL);
	        setsignal();
	        break;

	    case 'h': signal_type =  0; setsignal(); break;	/* SIGHUP  */
	    case 'i': signal_type =  1; setsignal(); break;	/* SIGINT  */
	    case 'q': signal_type =  2; setsignal(); break;	/* SIGQUIT */
	    case 'j': signal_type =  3; setsignal(); break;	/* SIGILL  */
	    case 'f': signal_type =  4; setsignal(); break;	/* SIGFPE  */
	    case 'k': signal_type =  5; setsignal(); break;	/* SIGKILL */
	    case '1': signal_type =  6; setsignal(); break;	/* SIGUSR1 */
	    case 's': signal_type =  7; setsignal(); break;	/* SIGSEGV */
	    case '2': signal_type =  8; setsignal(); break;	/* SIGUSR2 */
	    case 'p': signal_type =  9; setsignal(); break;	/* SIGPIPE */
	    case 'a': signal_type = 10; setsignal(); break;	/* SIGALRM */
	    case 't': signal_type = 11; setsignal(); break;	/* SIGTERM */
	    case 'y': signal_type = 12; setsignal(); break;	/* SIGCHLD */
	    case 'c': signal_type = 13; setsignal(); break;	/* SIGCONT */

	    case 'r': 
	    	goto restart;

	    case -TTY_F10:
	        goto end;

	    case KEY_ENTER:
	        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;
}
