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

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 <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <termios.h>

#include "xmalloc.h"
#include "tty.h"
#include "window.h"
#include "config.h"
#include "tilde.h"
#include "misc.h"


int SCREEN_X, SCREEN_Y;

#define MAX_KEYS        2048
#define MAX_PROCESS	1024
#define PS_FIELDS	  12


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

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

#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 AnsiColorSequences = ON;
#else	/* not HAVE_LINUX */
int AnsiColorSequences = OFF;
#endif	/* not HAVE_LINUX */


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

char *home;
char *program;
char *tty_name;
int  tty_name_len;
char *header_text;
char *TempDirectory;
char *stdout_log_name = NULL;
char *stderr_log_name = NULL;
char *ps_vect[MAX_PROCESS];
char *global_buf = NULL;
int first_on_screen, current_process;
window *title_win, *header_win, *screen_win, *status_win;
static char title_text[] =
    " UNIX Interactive Tools 4.3.1 - Process Viewer/Killer";
static char *UitPsModeHelp;
static char no_perm[] = "not owner !";
static char no_proc[] = "no such process ! (REFRESH recommended)";


struct SIGNAL
{
    char signame[8];
    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	25


#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 BUILTIN_HardRefresh	24


#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>",
    "<HardRefresh>",
};


void removelog(void)
{
    if (stdout_log_name) remove(stdout_log_name);
    if (stderr_log_name) remove(stderr_log_name);
}


void settitle(void)
{
    memset(global_buf, ' ', SCREEN_X);
    memcpy(global_buf, title_text, strlen(title_text));

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

    window_cursormove_notify(title_win, 0, 0);
    window_write(global_buf, SCREEN_X);
}


void setheader(void)
{
    memset(global_buf, ' ', SCREEN_X);
    memcpy(global_buf, header_text, strlen(header_text));

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

    window_cursormove_notify(header_win, 0, 0);
    window_write(global_buf, SCREEN_X);
}


void setstatus(char *what)
{
    memset(global_buf, ' ', SCREEN_X);

    if (what)
	memcpy(global_buf, what, strlen(what));
    else
	memcpy(global_buf, UitPsModeHelp, strlen(UitPsModeHelp));

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

    window_cursormove_notify(status_win, 0, 0);
    window_write(global_buf, SCREEN_X - (sizeof(signals[0].signame) - 1) - 1);
}


void setsignal(void)
{
    int len = sizeof(signals[0].signame) - 1;

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

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


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] = xmalloc(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)
{
    memset(global_buf, ' ', SCREEN_X);
    memcpy(global_buf, ps_vect[process], strlen(ps_vect[process]));

    if (update_color)
    {
	tty_bright(ScreenBrightness);

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

    window_cursormove_notify(screen_win, process - first_on_screen, 0);
    window_write(global_buf, SCREEN_X);
}


void update_all(void)
{
    int i;

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

    tty_cursor(OFF);

    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_notify(screen_win, i - first_on_screen, 0);

    update_process(current_process, ON);

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

    memset(global_buf, ' ', SCREEN_X);

    for (; i - first_on_screen < SCREEN_Y - 3; i++)
    {
	window_cursormove_notify(screen_win, i - first_on_screen, 0);
	window_write(global_buf, SCREEN_X);
    }

    window_cursormove(screen_win, current_process-first_on_screen, SCREEN_X-1);
    tty_cursor(ON);
}


void clean_up(void)
{
    tty_exit();
    removelog();
}


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


int ps(char *args)
{
    char *ps_cmd;
    FILE *stdout_log, *stderr_log;

    close(1);
    close(2);
    stdout_log = fopen(stdout_log_name, "w");
    stderr_log = fopen(stderr_log_name, "w");

    ps_cmd = xmalloc(16 + strlen(args) + 1);

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

    if (system(ps_cmd))
    {
	fclose(stderr_log);
	fclose(stdout_log);
	open(tty_name, O_RDWR);
	open(tty_name, O_RDWR);
	fprintf(stderr, "%s: invalid command line.\n", program);
	return 0;
    }

    free(ps_cmd);
    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);
    clean_up();

    switch (signum)
    {
	case SIGHUP:

	    /* :-) */
	    fprintf(stderr, "%s: got SIGHUP.\n", program);
	    break;

	case SIGINT:

	    break;

	case SIGTERM:
	case SIGQUIT:

	    fprintf(stderr, "%s: got %s.\n",
	    	   program, ((signum == SIGTERM) ? "SIGTERM" : "SIGQUIT"));
	    break;

	case SIGSEGV:

	    fprintf(stderr, "%s: got SIGSEGV.\n", program);
	    fprintf(stderr, "%s: please report to tudor@ulise.cs.pub.ro.\n",
	    	    program);
	    break;
    }

    exit(1);
}


int main(int argc, char *argv[])
{
    char *contents;
    char key_seq[16];
    FILE *stdout_log;
    char *tmp, *buf;
    struct key_struct *ks;
    int key, repeat_count;
    int i, j, index, first_time = 1;
    char *data = NULL;
    int need_update, need_update_all, old_current_process;


    program = argv[0];

    home = getenv("HOME");
    if (!home) home = ".";

    get_tty_name();
    get_login_name();

    tty_get_capabilities();
    tty_kbdinit(TTY_FULL_INPUT);

    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(SIGALRM, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGFPE,  SIG_IGN);

    configuration_check();


    use_section("[Setup]");

    configuration_getvarinfo("TempDirectory", &data, 1, DO_SEEK);
    TempDirectory = data ? tilde_expand(data) : "/tmp";

    AnsiColorSequences = get_flag_var("AnsiColorSequences", OFF);


    use_section("[UITPS-Setup]");

    UitPsModeHelp = get_string_var("UitPsModeHelp", "");


    use_section(AnsiColorSequences ? cSection : bwSection);

    get_colorset_var(PSColors, PSFields, PS_FIELDS);


    use_section("[UITPS-Keys]");

    for (i = 0; i < MAX_KEYS; i++)
    {
        configuration_getvarinfo(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, "%s: invalid built-in operation: %s.\n",
		    program, contents);
    }

    if (i == MAX_KEYS)
        fprintf(stderr,	"%s: too many key sequences; only %d are allowed.\n",
		program, MAX_KEYS);

    configuration_end();

    tty_getsize(&SCREEN_X, &SCREEN_Y);
    tty_startup();

    stdout_log_name = xmalloc(32 + strlen(TempDirectory) + 1);
    stderr_log_name = xmalloc(32 + strlen(TempDirectory) + 1);
    sprintf(stdout_log_name, "%s/uitps.1.%d", TempDirectory, getpid());
    sprintf(stderr_log_name, "%s/uitps.2.%d", TempDirectory, getpid());

    global_buf  = xmalloc(SCREEN_X + 1);

    header_text = xmalloc(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 - 3, SCREEN_X);
    status_win = window_init(0, SCREEN_Y - 1, 1,	    SCREEN_X);

    tty_set_mode(TTY_NONCANONIC);

    first_on_screen = current_process = 0;

restart:

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

    stdout_log = fopen(stdout_log_name, "r");
    removelog();

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

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

    if (first_time)
    {
	tty_bright(ScreenBrightness);
	tty_foreground(ScreenForeground);
	tty_background(ScreenBackground);
	tty_clrscr();
	first_time = 0;
    }

    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_HardRefresh:

	    	first_time = 1;

	    case BUILTIN_Refresh:

	    	goto restart;

	    case BUILTIN_Exit:

	        goto end;

	    case BUILTIN_KillProcess:

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

  end:

    clean_up();
    return 0;
}
