/*
 * Copyright 1998 by Albert Cahalan; all rights resered.         
 * This file may be used subject to the terms and conditions of the
 * GNU Library General Public License Version 2, or any later version  
 * at your option, as published by the Free Software Foundation.
 * This program 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 Library General Public License for more details.
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>

/*#undef __GLIBC_MINOR__
#define __GLIBC_MINOR__ 1 */
#include "common.h"
/*#undef __GLIBC_MINOR__
#define __GLIBC_MINOR__ 0 */

#include <sys/sysmacros.h>
#include "../proc/version.h"
#include "../proc/sysinfo.h"

#ifndef __GNU_LIBRARY__
#define __GNU_LIBRARY__ -1
#endif
#ifndef __GLIBC__
#define __GLIBC__ -1
#endif
#ifndef __GLIBC_MINOR__
#define __GLIBC_MINOR__ -1
#endif

static char *saved_personality_text = "You found a bug!";

int all_processes = -1;
char *bsd_j_format = (char *) 0xdeadbeef;
char *bsd_l_format = (char *) 0xdeadbeef;
char *bsd_s_format = (char *) 0xdeadbeef;
char *bsd_u_format = (char *) 0xdeadbeef;
char *bsd_v_format = (char *) 0xdeadbeef;
int bsd_c_option = -1;
int bsd_e_option = -1;
uid_t cached_euid = -1;
pid_t cached_pid = -1;
dev_t cached_tty = -1;
char forest_prefix[4 * 32 * 1024 + 100];
int forest_type = -1;
unsigned format_flags = 0xffffffff;	/* -l -f l u s -j... */
format_node *format_list = (format_node *) 0xdeadbeef;	/* digested formatting options */
unsigned format_modifiers = 0xffffffff;	/* -c -j -y -P -L... */
int header_gap = -1;
int header_type = -1;
int show_threads = -1;
int include_dead_children = -1;
int lines_to_next_header = -1;
int max_line_width = 666;
const char *namelist_file = (const char *) 0xdeadbeef;
int negate_selection = -1;
int running_only = -1;
unsigned personality = 0xffffffff;
int prefer_bsd = -1;
int screen_cols = -1;
int screen_rows = -1;
selection_node *selection_list = (selection_node *) 0xdeadbeef;
unsigned simple_select = 0xffffffff;
sort_node *sort_list = (sort_node *) 0xdeadbeef;	/* ready-to-use sort list */
char *sysv_f_format = (char *) 0xdeadbeef;
char *sysv_fl_format = (char *) 0xdeadbeef;
char *sysv_j_format = (char *) 0xdeadbeef;
char *sysv_l_format = (char *) 0xdeadbeef;
int unix_f_option = -1;
int use_aix_codes = -1;
int user_is_number = -1;
int wchan_is_number = -1;

static void
reset_selection_list(void)
{
	selection_node *old;
	selection_node *walk = selection_list;
	if (selection_list == (selection_node *) 0xdeadbeef) {
		selection_list = NULL;
		return;
	}
	while (walk) {
		old = walk;
		walk = old->next;
		free(old->u);
		free(old);
	}
	selection_list = NULL;
}

static void
set_screen_size(void)
{
	struct winsize ws;
	char *columns;		/* Unix98 environment variable */
	char *lines;		/* Unix98 environment variable */

	if (ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col > 0 && ws.ws_row > 0) {
		screen_cols = ws.ws_col;
		screen_rows = ws.ws_row;
	} else {
		struct stat sb;
		fstat(1, &sb);
		if (!S_ISFIFO(sb.st_mode)) {
			/* TODO: ought to do tgetnum("co") and tgetnum("li") now */
			screen_cols = 80;
			screen_rows = 24;
		} else {
			screen_cols = 80 * 4;	/* arbitrary */
			screen_rows = 24;
		}
	}
	columns = getenv("COLUMNS");
	if (columns && *columns) {
		long t;
		char *endptr;
		t = strtol(columns, &endptr, 0);
		if (!*endptr && (t > 0) && (t < (long) OUTBUF_SIZE))
			screen_cols = (int) t;
	}
	lines = getenv("LINES");
	if (lines && *lines) {
		long t;
		char *endptr;
		t = strtol(lines, &endptr, 0);
		if (!*endptr && (t > 0) && (t < (long) OUTBUF_SIZE))
			screen_rows = (int) t;
	}
	if ((screen_cols < 9) || (screen_rows < 2))
		fprintf(stderr,
			"Your %dx%d screen size is bogus. Expect trouble.\n",
			screen_cols, screen_rows);
}

/**************** personality control **************/

typedef struct personality_table_struct {
	const char *name;	/* personality name */
	const void *jump;	/* See gcc extension info.   :-)   */
} personality_table_struct;

static int
compare_personality_table_structs(const void *a, const void *b)
{
	return strcasecmp(((personality_table_struct *) a)->name,
			  ((personality_table_struct *) b)->name);
}

static const char *
set_personality(void)
{
	char *s;
	size_t sl;
	char buf[16];
	personality_table_struct findme = { buf, NULL };
	personality_table_struct *found;
	static const personality_table_struct personality_table[] = {
		{"aix", &&case_aix},
		{"bsd", &&case_bsd},
		{"compaq", &&case_compaq},
		{"debian", &&case_debian},
		{"default", &&case_default},
		{"digital", &&case_digital},
		{"gnu", &&case_gnu},
		{"hp", &&case_hp},
		{"hpux", &&case_hpux},
		{"irix", &&case_irix},
		{"linux", &&case_linux},
		{"old", &&case_old},
		{"posix", &&case_posix},
		{"sco", &&case_sco},
		{"sgi", &&case_sgi},
		{"solaris2", &&case_solaris2},
		{"sunos4", &&case_sunos4},
		{"sysv", &&case_sysv},
		{"unix", &&case_unix},
		{"unix95", &&case_unix95},
		{"unix98", &&case_unix98},
		{"unknown", &&case_unknown}
	};
	const int personality_table_count =
	    sizeof (personality_table) / sizeof (personality_table_struct);

	personality = 0;
	prefer_bsd = 0;

	bsd_j_format = "OL_j";
	bsd_l_format = "OL_l";
	bsd_s_format = "OL_s";
	bsd_u_format = "OL_u";
	bsd_v_format = "OL_v";

	/* When these are NULL, the code does SysV output modifier logic */
	sysv_f_format = NULL;
	sysv_fl_format = NULL;
	sysv_j_format = NULL;
	sysv_l_format = NULL;

	s = getenv("PS_PERSONALITY");
	if (!s || !*s)
		s = getenv("CMD_ENV");
	if (!s || !*s)
		s = "unknown";	/* "Do The Right Thing[tm]" */
	if (getenv("I_WANT_A_BROKEN_PS"))
		s = "old";
	sl = strlen(s);
	if (sl > 15)
		return "Environment specified an unknown personality.";
	strncpy(buf, s, sl);
	buf[sl] = '\0';
	saved_personality_text = strdup(buf);

	found = bsearch(&findme, personality_table, personality_table_count,
			sizeof (personality_table_struct),
			compare_personality_table_structs);

	if (!found)
		return "Environment specified an unknown personality.";

	goto *(found->jump);	/* See gcc extension info.  :-)   */
	/* FIXME: change to enum's and switch statement */

      case_bsd:
	personality = PER_FORCE_BSD | PER_BSD_h | PER_BSD_m;
	prefer_bsd = 1;
	bsd_j_format = "FB_j";
	bsd_l_format = "FB_l";
	/* bsd_s_format not used */
	bsd_u_format = "FB_u";
	bsd_v_format = "FB_v";
	return NULL;

      case_old:
	personality = PER_FORCE_BSD | PER_OLD_m;
	prefer_bsd = 1;
	return NULL;

      case_debian:		/* Toss this? They don't seem to care much. */
      case_gnu:
	personality = PER_GOOD_o | PER_CUMUL_MARKED;
	prefer_bsd = 1;
	sysv_f_format = "RD_f";
	/* sysv_fl_format = "RD_fl"; *//* Debian can't do this! */
	sysv_j_format = "RD_j";
	sysv_l_format = "RD_l";
	return NULL;

      case_linux:
	personality = PER_GOOD_o | PER_ZAP_ADDR | PER_SANE_USER;
	return NULL;

      case_default:		/* use defaults for ps, ignoring other environment variables */
	return NULL;

      case_unknown:		/* defaults, but also check inferior environment variables */
	if (getenv("UNIX95")	/* Irix */
	    ||getenv("POSIXLY_CORRECT")	/* most gnu stuff */
	    ||(getenv("POSIX2") && !strcmp(getenv("POSIX2"), "on"))	/* Unixware 7 */
	    )
		personality = PER_BROKEN_o;
	return NULL;

      case_aix:
	bsd_j_format = "FB_j";
	bsd_l_format = "FB_l";
	/* bsd_s_format not used */
	bsd_u_format = "FB_u";
	bsd_v_format = "FB_v";
	return NULL;

      case_compaq:
      case_digital:
	personality = PER_GOOD_o | PER_BSD_h;
	prefer_bsd = 1;		/* TODO check this */
	sysv_f_format = "F5FMT";
	sysv_fl_format = "FL5FMT";
	sysv_j_format = "JFMT";
	sysv_l_format = "L5FMT";
	bsd_j_format = "JFMT";
	bsd_l_format = "LFMT";
	bsd_s_format = "SFMT";
	bsd_u_format = "UFMT";
	bsd_v_format = "VFMT";
	return NULL;

      case_sunos4:
	personality = PER_SUN_MUTATE_a;
	prefer_bsd = 1;
	bsd_j_format = "FB_j";
	bsd_l_format = "FB_l";
	/* bsd_s_format not used */
	bsd_u_format = "FB_u";
	bsd_v_format = "FB_v";
	return NULL;

      case_irix:
      case_sgi:
	s = getenv("_XPG");
	if (s && s[0] > '0' && s[0] <= '9')
		personality = PER_BROKEN_o;
	else
		personality = PER_IRIX_l;
	return NULL;

      case_hp:
      case_hpux:
      case_posix:
      case_sco:
      case_solaris2:
      case_sysv:
      case_unix95:
      case_unix98:
      case_unix:
	personality = PER_BROKEN_o;
	return NULL;
}

/************ Call this to reinitialize everything ***************/
void
reset_global(void)
{
	static proc_t p;
	reset_selection_list();
	look_up_our_self(&p);
	set_screen_size();
	set_personality();

	all_processes = 0;
	bsd_c_option = 0;
	bsd_e_option = 0;
	cached_euid = geteuid();
	cached_pid = getpid();
	cached_tty = p.tty;
/* forest_prefix must be all zero because of POSIX */
	forest_type = 0;
	format_flags = 0;	/* -l -f l u s -j... */
	format_list = NULL;	/* digested formatting options */
	format_modifiers = 0;	/* -c -j -y -P -L... */
	header_gap = -1;	/* send lines_to_next_header to -infinity */
	header_type = HEAD_SINGLE;
	show_threads = 0;
	include_dead_children = 0;
	lines_to_next_header = 1;
	max_line_width = 80;
	namelist_file = NULL;
	negate_selection = 0;
	running_only = 0;
	selection_list = NULL;
	simple_select = 0;
	sort_list = NULL;
	unix_f_option = 0;
	use_aix_codes = 0;
	user_is_number = 0;
	wchan_is_number = 0;
}

/*********** spew variables ***********/
void
self_info(void)
{
	fprintf(stderr,
		"BSD j    %s\n"
		"BSD l    %s\n"
		"BSD s    %s\n"
		"BSD u    %s\n"
		"BSD v    %s\n"
		"SysV -f  %s\n"
		"SysV -fl %s\n"
		"SysV -j  %s\n"
		"SysV -l  %s\n"
		"\n",
		bsd_j_format ? bsd_j_format : "(none)",
		bsd_l_format ? bsd_l_format : "(none)",
		bsd_s_format ? bsd_s_format : "(none)",
		bsd_u_format ? bsd_u_format : "(none)",
		bsd_v_format ? bsd_v_format : "(none)",
		sysv_f_format ? sysv_f_format : "(none)",
		sysv_fl_format ? sysv_fl_format : "(none)",
		sysv_j_format ? sysv_j_format : "(none)",
		sysv_l_format ? sysv_l_format : "(none)");

	display_version();
	fprintf(stderr, "Linux version %d.%d.%d\n",
		LINUX_VERSION_MAJOR(linux_version_code),
		LINUX_VERSION_MINOR(linux_version_code),
		LINUX_VERSION_PATCH(linux_version_code)
	    );
	/* __libc_print_version(); *//* how can we get the run-time version? */
	fprintf(stderr, "Compiled with: libc %d, internal version %d.%d\n\n",
		__GNU_LIBRARY__, __GLIBC__, __GLIBC_MINOR__);

	fprintf(stderr,
		"header_gap=%d lines_to_next_header=%d max_line_width=%d\n"
		"screen_cols=%d screen_rows=%d\n"
		"\n",
		header_gap, lines_to_next_header, max_line_width,
		screen_cols, screen_rows);

/*  open_psdb(namelist_file); */
	fprintf(stderr,
		"personality=0x%08x (from \"%s\")\n"
		"EUID=%d TTY=%d,%d Hertz=%Lu\n"
/*    "namelist_file=\"%s\"\n" */
		, personality, saved_personality_text, cached_euid, (int) major(cached_tty), (int) minor(cached_tty), Hertz	/* ,
																   namelist_file?namelist_file:"<no System.map file>"  */
	    );
}
