/*
 * Configurable ps-like program.
 * Command line option parsing.
 *
 * Copyright (c) 2014 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 */

#include "ips.h"


/*
 * Definitions for version printout.
 */
#define	VERSION		"4.2"
#define	AUTHOR_NAME	"David I. Bell"
#define	AUTHOR_EMAIL	"dbell@tip.net.au"


/*
 * Command line options.
 */
#define	OPT_ILLEGAL		0
#define	OPT_SET_COLUMNS		1
#define	OPT_ADD_COLUMNS		2
#define	OPT_REMOVE_COLUMNS	3
#define	OPT_SEPARATION		4
#define	OPT_WIDTH		5
#define	OPT_SLEEP_TIME		6
#define	OPT_NO_SELF		7
#define	OPT_MY_PROCS		9
#define	OPT_ACTIVE_ONLY		10
#define	OPT_NO_ROOT		11
#define	OPT_CLEAR_SCREEN	12
#define	OPT_READ_FILE		13
#define	OPT_NORMAL_SORT		14
#define	OPT_REVERSE_SORT	15
#define	OPT_NORMAL_SORT_EXPR	16
#define	OPT_NO_SORT		17
#define	OPT_LOOP		18
#define	OPT_INIT_TIME		19
#define	OPT_CONDITION		20
#define	OPT_TOP_COUNT		21
#define	OPT_NO_HEADER		22
#define	OPT_PIDS		23
#define	OPT_USERS		24
#define	OPT_HELP		25
#define	OPT_LIST_COLUMNS	26
#define	OPT_END			27
#define	OPT_NO_INIT		28
#define	OPT_ACTIVE_TIME		29
#define	OPT_LIST_MACROS		30
#define	OPT_VERSION		31
#define	OPT_DEFAULT		32
#define	OPT_SYNC_TIME		33
#define	OPT_GROUPS		34
#define	OPT_VERTICAL		35
#define	OPT_REVERSE_SORT_EXPR	36
#define	OPT_COLUMN_WIDTH	37
#define	OPT_PROGRAMS		38
#define	OPT_CURSES		39
#define	OPT_X11			40
#define	OPT_ONCE		41
#define	OPT_DISPLAY		42
#define	OPT_FONT		43
#define	OPT_FOREGROUND		44
#define	OPT_BACKGROUND		45
#define	OPT_SCROLL_TIME		46
#define	OPT_OVERLAP_LINES	47
#define	OPT_INFO		48
#define	OPT_DEATH_TIME		49
#define	OPT_GEOMETRY		50
#define	OPT_SHOW_THREADS	51
#define	OPT_USE_THREADS		52
#define	OPT_NO_COPY		53
#define	OPT_INFO_COLOR		54
#define	OPT_HEADER_COLOR	55
#define	OPT_ROW_COLOR		56
#define	OPT_PERCENT_SECONDS	57

#define	OPT_LAST_OPTION		57


/*
 * Table of mappings between option names and option ids.
 */
typedef	struct
{
	char *	name;			/* option name */
	int	id;			/* option id */
} OPTION;


static	OPTION	option_table[OPT_LAST_OPTION + 2] =
{
	{"col",		OPT_SET_COLUMNS},
	{"addcol",	OPT_ADD_COLUMNS},
	{"remcol",	OPT_REMOVE_COLUMNS},
	{"sep",		OPT_SEPARATION},
	{"width",	OPT_WIDTH},
	{"sleep",	OPT_SLEEP_TIME},
	{"noself",	OPT_NO_SELF},
	{"my",		OPT_MY_PROCS},
	{"active",	OPT_ACTIVE_ONLY},
	{"noroot",	OPT_NO_ROOT},
	{"clear",	OPT_CLEAR_SCREEN},
	{"read",	OPT_READ_FILE},
	{"sort",	OPT_NORMAL_SORT},
	{"revsort",	OPT_REVERSE_SORT},
	{"sortexpr",	OPT_NORMAL_SORT_EXPR},
	{"revsortexpr",	OPT_REVERSE_SORT_EXPR},
	{"nosort",	OPT_NO_SORT},
	{"loop",	OPT_LOOP},
	{"once",	OPT_ONCE},
	{"curses",	OPT_CURSES},
	{"x11",		OPT_X11},
	{"vert",	OPT_VERTICAL},
	{"initsleep",	OPT_INIT_TIME},
	{"cond",	OPT_CONDITION},
	{"top",		OPT_TOP_COUNT},
	{"noheader",	OPT_NO_HEADER},
	{"pid",		OPT_PIDS},
	{"user",	OPT_USERS},
	{"group",	OPT_GROUPS},
	{"program",	OPT_PROGRAMS},
	{"h",		OPT_HELP},
	{"help",	OPT_HELP},
	{"?",		OPT_HELP},
	{"listcolumns",	OPT_LIST_COLUMNS},
	{"end",		OPT_END},
	{"noinit",	OPT_NO_INIT},
	{"activetime",	OPT_ACTIVE_TIME},
	{"deathtime",	OPT_DEATH_TIME},
	{"listmacros",	OPT_LIST_MACROS},
	{"version",	OPT_VERSION},
	{"default",	OPT_DEFAULT},
	{"synctime",	OPT_SYNC_TIME},
	{"colwidth",	OPT_COLUMN_WIDTH},
	{"display",	OPT_DISPLAY},
	{"font",	OPT_FONT},
	{"foreground",	OPT_FOREGROUND},
	{"background",	OPT_BACKGROUND},
	{"scroll",	OPT_SCROLL_TIME},
	{"overlap",	OPT_OVERLAP_LINES},
	{"info",	OPT_INFO},
	{"geometry",	OPT_GEOMETRY},
	{"showthreads",	OPT_SHOW_THREADS},
	{"usethreads",	OPT_USE_THREADS},
	{"nocopy",	OPT_NO_COPY},
	{"infocolor",	OPT_INFO_COLOR},
	{"headercolor",	OPT_HEADER_COLOR},
	{"rowcolor",	OPT_ROW_COLOR},
	{"percentseconds",	OPT_PERCENT_SECONDS},
	{NULL,		OPT_ILLEGAL}
};


/*
 * Static routines.
 */
static	BOOL	ReadFile(ARGS *);
static	BOOL	SetSleepTime(ARGS *);
static	BOOL	SetActiveTime(ARGS *);
static	BOOL	SetDeathTime(ARGS *);
static	BOOL	SetSyncTime(ARGS *);
static	BOOL	SetTopCount(ARGS *);
static	BOOL	SetInitTime(ARGS *);
static	BOOL	SetCondition(ARGS *);
static	BOOL	SetSeparation(ARGS *);
static	BOOL	SetWidth(ARGS *);
static	BOOL	SetColumns(ARGS *);
static	BOOL	SetColumnWidth(ARGS *);
static	BOOL	SetPids(ARGS *);
static	BOOL	SetUsers(ARGS *);
static	BOOL	SetGroups(ARGS *);
static	BOOL	SetPrograms(ARGS *);
static	BOOL	SetFont(ARGS *);
static	BOOL	SetGeometry(ARGS *);
static	BOOL	SetForeground(ARGS *);
static	BOOL	SetBackground(ARGS *);
static	BOOL	SetDisplay(ARGS *);
static	BOOL	SetScrollTime(ARGS *);
static	BOOL	SetOverlapLines(ARGS *);
static	BOOL	SetInfoColor(ARGS *);
static	BOOL	SetHeaderColor(ARGS *);
static	BOOL	SetDefault(ARGS *);
static	BOOL	AddColumns(ARGS *);
static	BOOL	RemoveColumns(ARGS *);
static	BOOL	SetRowColorCondition(ARGS * ap);
static	BOOL	SetPercentSeconds(ARGS *);

static	void	DefaultOneOption(int);
static	void	PrintUsage(void);
static	void	PrintVersion(void);


/*
 * Parse the command line options.
 * The program name argument must have already been removed.
 * Returns TRUE if they were successfully parsed.
 */
BOOL
ParseOptions(ARGS * ap, int depth)
{
	const OPTION *	option;
	const char *	str;
	BOOL		status;
	BOOL		isSuccess;

	isSuccess = TRUE;

	while (ap->count-- > 0)
	{
		str = *ap->table++;

		/*
		 * If this is not an option, then it must be the name
		 * of an option declaration, so expand that name and
		 * collect those options.
		 */
		if (*str != '-')
		{
			if (!ExpandOptionName(str, depth))
				return FALSE;

			continue;
		}

		/*
		 * It is a normal option, so look it up and handle it.
		 */
		str++;

		option = option_table;

		while (option->name && (strcmp(option->name, str) != 0))
			option++;

		status = TRUE;

		switch (option->id)
		{
			case OPT_SET_COLUMNS:
				status = SetColumns(ap);
				break;

			case OPT_ADD_COLUMNS:
				status = AddColumns(ap);
				break;

			case OPT_REMOVE_COLUMNS:
				status = RemoveColumns(ap);
				break;

			case OPT_SEPARATION:
				status = SetSeparation(ap);
				break;

			case OPT_WIDTH:
				status = SetWidth(ap);
				break;

			case OPT_SLEEP_TIME:
				status = SetSleepTime(ap);
				break;

			case OPT_ACTIVE_TIME:
				status = SetActiveTime(ap);
				break;

			case OPT_DEATH_TIME:
				status = SetDeathTime(ap);
				break;

			case OPT_SYNC_TIME:
				status = SetSyncTime(ap);
				break;

			case OPT_COLUMN_WIDTH:
				status = SetColumnWidth(ap);
				break;

			case OPT_TOP_COUNT:
				status = SetTopCount(ap);
				break;

			case OPT_INIT_TIME:
				status = SetInitTime(ap);
				break;

			case OPT_CONDITION:
				status = SetCondition(ap);
				break;

			case OPT_NO_SELF:
				noSelf = TRUE;
				break;

			case OPT_NO_ROOT:
				noRoot = TRUE;
				break;

			case OPT_NO_HEADER:
				noHeader = TRUE;
				break;

			case OPT_INFO:
				isInfoShown = TRUE;
				break;

			case OPT_SHOW_THREADS:
				showThreads = TRUE;
				break;

			case OPT_USE_THREADS:
				useThreads = TRUE;
				break;

			case OPT_NO_COPY:
				noCopy = TRUE;

			case OPT_MY_PROCS:
				myProcs = TRUE;
				break;

			case OPT_ACTIVE_ONLY:
				activeOnly = TRUE;
				break;

			case OPT_CLEAR_SCREEN:
				clearScreen = TRUE;
				break;

			case OPT_READ_FILE:
				status = ReadFile(ap);
				break;

			case OPT_NORMAL_SORT:
				status = AppendColumnSort(ap, FALSE);
				break;

			case OPT_REVERSE_SORT:
				status = AppendColumnSort(ap, TRUE);
				break;

			case OPT_NORMAL_SORT_EXPR:
				status = AppendExpressionSort(ap, FALSE);
				break;

			case OPT_REVERSE_SORT_EXPR:
				status = AppendExpressionSort(ap, TRUE);
				break;

			case OPT_NO_SORT:
				ClearSorting();
				break;

			case OPT_DEFAULT:
				status = SetDefault(ap);
				break;

			case OPT_ONCE:
				isLooping = FALSE;
				displayType = DISPLAY_TYPE_TTY;
				break;

			case OPT_LOOP:
				isLooping = TRUE;
				displayType = DISPLAY_TYPE_TTY;
				break;

			case OPT_CURSES:
				isLooping = TRUE;
				displayType = DISPLAY_TYPE_CURSES;
				break;

			case OPT_X11:
				isLooping = TRUE;
				displayType = DISPLAY_TYPE_X11;
				break;

			case OPT_VERTICAL:
				isVertical = TRUE;
				break;

			case OPT_PIDS:
				status = SetPids(ap);
				break;

			case OPT_USERS:
				status = SetUsers(ap);
				break;

			case OPT_GROUPS:
				status = SetGroups(ap);
				break;

			case OPT_PROGRAMS:
				status = SetPrograms(ap);
				break;

			case OPT_GEOMETRY:
				status = SetGeometry(ap);
				break;

			case OPT_FONT:
				status = SetFont(ap);
				break;

			case OPT_FOREGROUND:
				status = SetForeground(ap);
				break;

			case OPT_BACKGROUND:
				status = SetBackground(ap);
				break;

			case OPT_DISPLAY:
				status = SetDisplay(ap);
				break;

			case OPT_PERCENT_SECONDS:
				status = SetPercentSeconds(ap);
				break;

			case OPT_SCROLL_TIME:
				status = SetScrollTime(ap);
				break;

			case OPT_OVERLAP_LINES:
				status = SetOverlapLines(ap);
				break;

			case OPT_INFO_COLOR:
				status = SetInfoColor(ap);
				break;

			case OPT_HEADER_COLOR:
				status = SetHeaderColor(ap);
				break;

			case OPT_ROW_COLOR:
				status = SetRowColorCondition(ap);
				break;

			case OPT_HELP:
				PrintUsage();
				break;

			case OPT_LIST_COLUMNS:
				ListColumns();
				exit(0);

			case OPT_LIST_MACROS:
				ListMacros();
				exit(0);

			case OPT_VERSION:
				PrintVersion();
				exit(0);

			case OPT_END:
				break;

			case OPT_NO_INIT:
				fprintf(stderr,
"The -noinit option can only be used as the first command line argument\n");

				status = FALSE;
				break;

			default:
				fprintf(stderr, "Unknown option -%s\n", str);
				status = FALSE;
				break;
		}

		if (!status)
			isSuccess = FALSE;
	}

	return isSuccess;
}


/*
 * Expand an option definition.
 * Such a definition has to be complete within the expansion.
 * For the top level expansions only, we uppercase words for convenience.
 */
BOOL
ExpandOptionName(const char * name, int depth)
{
	ARGS	args;
	char	upName[MAX_MACRO_LEN + 2];

	if (depth > MAX_OPTION_DEPTH)
	{
		fprintf(stderr, "Too many levels of option macros\n");

		return FALSE;
	}

	if (strlen(name) > MAX_MACRO_LEN)
	{
		fprintf(stderr, "Macro name \"%c%s\" is too long\n",
			*name + 'A' - 'a', name + 1);

		return FALSE;
	}

	/*
	 * If we are on the top level command line, then uppercase the
	 * macro name for convenience.
	 */
	if ((depth == 0) && isLower(*name))
	{
		strcpy(upName, name);

		upName[0] += 'A' - 'a';

		name = upName;
	}

	if (isLower(*name))
	{
		fprintf(stderr, "Option macro \"%s\" must be upper case\n",
			name);

		return FALSE;
	}

	if (!isMacro(*name))
	{
		fprintf(stderr, "String value \"%s\" is not a macro name\n",
			name);

		return FALSE;
	}

	if (!ExpandMacro(MACRO_TYPE_OPTION, name, &args))
		return FALSE;

	return ParseOptions(&args, depth + 1);
}


/*
 * Expand command line arguments for an option by looking for macro names
 * and expanding the macros recursively.  The expanded arguments are placed
 * into the specified table.  Returns the number of arguments resulting
 * from the expansions, or -1 on an error.
 */
int
ExpandArguments(ARGS * ap, char ** table, int tableLen)
{
	char *	name;
	int	count;
	int	subCount;
	ARGS	args;

	count = 0;

	while ((ap->count > 0) && (**ap->table != '-'))
	{
		name = *ap->table++;
		ap->count--;

		/*
		 * Make sure there is room left in the table.
		 */
		if (tableLen <= 0)
		{
			fprintf(stderr, "Too many arguments\n");

			return -1;
		}

		/*
		 * If the argument is not a macro name, then just put it
		 * into the returned argument table 
		 */
		if (!isMacro(*name))
		{
			*table++ = name;
			tableLen--;
			count++;

			continue;
		}

		/*
		 * The argument is a macro name, so expand it into more
		 * arguments.
		 */
		if (!ExpandMacro(MACRO_TYPE_COLUMN, name, &args))
			return -1;

		/*
		 * Add the result of the expansion into the table at this
		 * point, possibly doing a recursive expansion.
		 */
		subCount = ExpandArguments(&args, table, tableLen);

		if (subCount < 0)
			return -1;

		/*
		 * Increment past the number of entries that the expansion
		 * added to the table.
		 */
		table += subCount;
		tableLen -= subCount;
		count += subCount;
	}

	return count;
}


/*
 * Set the separation between columns.
 */
static BOOL
SetSeparation(ARGS * ap)
{
	const const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing separation argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	separation = GetDecimalNumber(&str);

	if (*str || (separation < 0) || (separation > MAX_SEPARATION))
	{
		fprintf(stderr, "Bad separation argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Read the specified file containing macro definitions.
 * The file must exist.
 */
static BOOL
ReadFile(ARGS * ap)
{
	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing file argument\n");

		return FALSE;
	}

	ap->count--;

	return ParseFile(*ap->table++, FALSE);
}


/*
 * Set the default value associated with one or more options.
 * If no option names are given, then all defaults are set.
 */
static BOOL
SetDefault(ARGS * ap)
{
	OPTION *option;
	char *	name;

	/*
	 * If no option name was given, then default everything.
	 */
	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		DefaultAllOptions();

		return TRUE;
	}

	/*
	 * There was at least one option given, so default them.
	 */
	while ((ap->count > 0) && (**ap->table != '-'))
	{
		ap->count--;
		name = *ap->table++;

		option = option_table;

		while (option->name && (strcmp(option->name, name) != 0))
			option++;

		if (option->name == NULL)
		{
			fprintf(stderr, "Undefined option name \"%s\"\n", name);

			return FALSE;
		}

		DefaultOneOption(option->id);
	}

	return TRUE;
}


/*
 * Routine to default all option values.
 * This is called at program startup to initialize parameters,
 * and also when the user requests it.
 */
void
DefaultAllOptions(void)
{
	int	id;

	for (id = 0; id <= OPT_LAST_OPTION; id++)
		DefaultOneOption(id);
}


/*
 * Routine to default the specified option id.
 * This is only meaningful for certain options, but calling this for the
 * remaining options isn't a problem.  This routine never reports an error.
 */
static void
DefaultOneOption(int id)
{
	switch (id)
	{
		case OPT_SET_COLUMNS:
		case OPT_ADD_COLUMNS:
		case OPT_REMOVE_COLUMNS:
			DefaultColumns();
			break;

		case OPT_SEPARATION:
			separation = 2;
			break;

		case OPT_WIDTH:
			outputWidth = DEFAULT_WIDTH;
			break;

		case OPT_SLEEP_TIME:
			sleepTimeMs = DEFAULT_SLEEP_SEC * 1000;
			break;

		case OPT_INFO:
			isInfoShown = FALSE;
			break;

		case OPT_NO_SELF:
			noSelf = FALSE;
			break;

		case OPT_MY_PROCS:
			myProcs = FALSE;
			break;

		case OPT_SHOW_THREADS:
			showThreads = FALSE;
			break;

		case OPT_USE_THREADS:
			useThreads = FALSE;
			break;

		case OPT_NO_COPY:
			noCopy = FALSE;

		case OPT_ACTIVE_ONLY:
			activeOnly = FALSE;
			break;

		case OPT_NO_ROOT:
			noRoot = FALSE;
			break;

		case OPT_CLEAR_SCREEN:
			clearScreen = FALSE;
			break;

		case OPT_NORMAL_SORT:
		case OPT_REVERSE_SORT:
		case OPT_NORMAL_SORT_EXPR:
		case OPT_REVERSE_SORT_EXPR:
		case OPT_NO_SORT:
			ClearSorting();
			break;

		case OPT_LOOP:
			isLooping = FALSE;
			break;

		case OPT_VERTICAL:
			isVertical = FALSE;
			break;

		case OPT_INIT_TIME:
			initSleepTime = DEFAULT_INIT_SEC;
			break;

		case OPT_CONDITION:
			ClearCondition();
			break;

		case OPT_TOP_COUNT:
			topCount = 0;
			break;

		case OPT_NO_HEADER:
			noHeader = FALSE;
			break;

		case OPT_PIDS:
			pidCount = 0;
			break;

		case OPT_USERS:
			userCount = 0;
			break;

		case OPT_GROUPS:
			groupCount = 0;
			break;

		case OPT_PROGRAMS:
			programCount = 0;
			break;

		case OPT_ACTIVE_TIME:
			activeTime = DEFAULT_ACTIVE_SEC;
			break;

		case OPT_DEATH_TIME:
			deathTime = DEFAULT_DEATH_SEC;
			break;

		case OPT_SYNC_TIME:
			syncTime = DEFAULT_SYNC_SEC;
			break;

		case OPT_COLUMN_WIDTH:
			DefaultColumnWidths();
			break;

		case OPT_GEOMETRY:
			ReplaceString(&geometry, DEFAULT_GEOMETRY);
			break;

		case OPT_FONT:
			ReplaceString(&fontName, DEFAULT_FONT);
			break;

		case OPT_FOREGROUND:
			ReplaceString(&foregroundName, DEFAULT_FOREGROUND);
			break;

		case OPT_BACKGROUND:
			ReplaceString(&backgroundName, DEFAULT_BACKGROUND);
			break;

		case OPT_DISPLAY:
			ReplaceString(&displayName, NULL);
			break;

		case OPT_PERCENT_SECONDS:
			percentSeconds = PERCENT_DEFAULT_SECONDS;
			break;

		case OPT_SCROLL_TIME:
			scrollSeconds = DEFAULT_SCROLL_SEC;
			break;

		case OPT_OVERLAP_LINES:
			overlapLines = DEFAULT_OVERLAP_LINES;
			break;

		case OPT_INFO_COLOR:
			infoColorId = DEFAULT_COLOR_ID;
			break;

		case OPT_HEADER_COLOR:
			headerColorId = DEFAULT_COLOR_ID;
			break;

		case OPT_ROW_COLOR:
			ClearRowColorConditions();
			break;

		default:
			break;
	}
}


/*
 * Set the condition for displaying of processes.
 */
static BOOL
SetCondition(ARGS * ap)
{
	const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing condition expression\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	return ParseCondition(str);
}


/*
 * Set the sleep time between updates.
 * This value can be a floating point number.
 */
static BOOL
SetSleepTime(ARGS * ap)
{
	const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing sleep time argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	sleepTimeMs = (int) (GetFloatingNumber(&str) * 1000.0);

	if (*str || (sleepTimeMs < 0))
	{
		fprintf(stderr, "Bad sleep time argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Set the number of seconds to keep an active process in the display.
 */
static BOOL
SetActiveTime(ARGS * ap)
{
	const const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing active time argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	activeTime = GetDecimalNumber(&str);

	if (*str || (activeTime < 0))
	{
		fprintf(stderr, "Bad active time argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Set the number of seconds to keep a dead process in the display.
 */
static BOOL
SetDeathTime(ARGS * ap)
{
	const const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing death time argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	deathTime = GetDecimalNumber(&str);

	if (*str || (deathTime < 0))
	{
		fprintf(stderr, "Bad death time argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Set the number of seconds to force synchronization with process status.
 */
static BOOL
SetSyncTime(ARGS * ap)
{
	const const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing sync time argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	syncTime = GetDecimalNumber(&str);

	if (*str || (syncTime < 0))
	{
		fprintf(stderr, "Bad sync time argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Set the number of processes to show from the top.
 * The count can be omitted to indicate that we calculate it
 * from the terminal height.
 */
static BOOL
SetTopCount(ARGS * ap)
{
	const char *	str;

	isTopMode = TRUE;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		topCount = outputHeight - 1;

		if (!noHeader)
			topCount--;

		if (isInfoShown)
			topCount--;

		if (topCount <= 0)
			topCount = 1;

		isTopAuto = TRUE;

		return TRUE;
	}

	ap->count--;
	str = *ap->table++;

	topCount = GetDecimalNumber(&str);

	if (*str || (topCount < 0))
	{
		fprintf(stderr, "Bad top argument\n");

		return FALSE;
	}

	isTopAuto = FALSE;

	return TRUE;
}


/*
 * Set the list of pids to be examined.
 */
static BOOL
SetPids(ARGS * ap)
{
	const char *	str;
	pid_t	pid;
	int	i;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing pid argument\n");

		return FALSE;
	}

	while ((ap->count > 0) && (**ap->table != '-'))
	{
		ap->count--;
		str = *ap->table++;

		pid = (pid_t) GetDecimalNumber(&str);

		if (*str || (((long) pid) <= 0))
		{
			fprintf(stderr, "Bad pid argument\n");

			return FALSE;
		}

		for (i = 0; i < pidCount; i++)
		{
			if (pidList[i] == pid)
				break;
		}

		if (i < pidCount)
			continue;

		if (pidCount >= MAX_PIDS)
		{
			fprintf(stderr, "Too many pids specified\n");

			return FALSE;
		}

		pidList[pidCount++] = pid;
	}

	return TRUE;
}


/*
 * Set the list of users to be examined.
 */
static BOOL
SetUsers(ARGS * ap)
{
	const char *	str;
	uid_t	uid;
	int	i;

	CollectUserNames();

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing user argument\n");

		return FALSE;
	}

	while ((ap->count > 0) && (**ap->table != '-'))
	{
		ap->count--;
		str = *ap->table++;

		if (isDigit(*str))
		{
			uid = (uid_t) GetDecimalNumber(&str);

			if (*str)
			{
				fprintf(stderr, "Bad uid argument\n");

				return FALSE;
			}
		}
		else
		{
			uid = FindUserId(str);

			if (uid == BAD_UID)
			{
				fprintf(stderr, "Unknown user \"%s\"\n", str);

				return FALSE;
			}
		}

		for (i = 0; i < userCount; i++)
		{
			if (userList[i] == uid)
				break;
		}

		if (i < userCount)
			continue;

		if (userCount >= MAX_USERS)
		{
			fprintf(stderr, "Too many users specified\n");

			return FALSE;
		}

		userList[userCount++] = uid;
	}

	return TRUE;
}


/*
 * Set the list of groups to be examined.
 */
static BOOL
SetGroups(ARGS * ap)
{
	const char *	str;
	gid_t	gid;
	int	i;

	CollectGroupNames();

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing group argument\n");

		return FALSE;
	}

	while ((ap->count > 0) && (**ap->table != '-'))
	{
		ap->count--;
		str = *ap->table++;

		if (isDigit(*str))
		{
			gid = (gid_t) GetDecimalNumber(&str);

			if (*str)
			{
				fprintf(stderr, "Bad gid argument\n");

				return FALSE;
			}
		}
		else
		{
			gid = FindGroupId(str);

			if (gid == BAD_GID)
			{
				fprintf(stderr, "Unknown group \"%s\"\n", str);

				return FALSE;
			}
		}

		for (i = 0; i < groupCount; i++)
		{
			if (groupList[i] == gid)
				break;
		}

		if (i < groupCount)
			continue;

		if (groupCount >= MAX_GROUPS)
		{
			fprintf(stderr, "Too many groups specified\n");

			return FALSE;
		}

		groupList[groupCount++] = gid;
	}

	return TRUE;
}


/*
 * Set the list of programs to be examined.
 */
static BOOL
SetPrograms(ARGS * ap)
{
	char *	str;
	int	i;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing program argument\n");

		return FALSE;
	}

	while ((ap->count > 0) && (**ap->table != '-'))
	{
		ap->count--;
		str = *ap->table++;

		/*
		 * If the name is too long, then silently ignore it
		 * since it cannot match.
		 */
		if (strlen(str) > MAX_PROGRAM_LEN)
			continue;

		for (i = 0; i < programCount; i++)
		{
			if (strcmp(str, programList[i]) == 0)
				break;
		}

		if (i < programCount)
			continue;

		if (programCount >= MAX_PROGRAMS)
		{
			fprintf(stderr, "Too many programss specified\n");

			return FALSE;
		}

		strcpy(programList[programCount++], str);
	}

	return TRUE;
}


/*
 * Set the sleep time before the initial update.
 */
static BOOL
SetInitTime(ARGS * ap)
{
	const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing init time argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	initSleepTime = GetDecimalNumber(&str);

	if (*str || (initSleepTime < 0))
	{
		fprintf(stderr, "Bad init time argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Set the display width.
 */
static BOOL
SetWidth(ARGS * ap)
{
	const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing width argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	outputWidth = GetDecimalNumber(&str);

	if (*str || (outputWidth <= 0))
	{
		fprintf(stderr, "Bad width argument\n");

		return FALSE;
	}

	if (outputWidth > MAX_WIDTH)
		outputWidth = MAX_WIDTH;

	return TRUE;
}


/*
 * Set the width of the specified column to the indicated value,
 * or else back to its default value if no value was given.
 */
static BOOL
SetColumnWidth(ARGS * ap)
{
	COLUMN *	column;
	const char *	name;
	const char *	str;
	int		width;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing Column name\n");

		return FALSE;
	}

	ap->count--;
	name = *ap->table++;

	/*
	 * Find the column from the name.
	 */
	column = FindColumn(name);

	if (column == NULL)
	{
		fprintf(stderr, "Bad column name %s\n", name);

		return FALSE;
	}

	/*
	 * If no additional argument was given,
	 * then set the column's width back to its initial value.
	 */
	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		column->width = column->initWidth;

		return TRUE;
	}

	/*
	 * There was a width specified, so parse it and set the
	 * column width to that value.
	 */
	ap->count--;
	str = *ap->table++;

	width = GetDecimalNumber(&str);

	if (*str || (width <= 0))
	{
		fprintf(stderr, "Bad column width argument\n");

		return FALSE;
	}

	if (width > MAX_WIDTH)
		width = MAX_WIDTH;

	column->width = width;

	return TRUE;
}


/*
 * Set the columns to display according to the indicated names.
 */
static BOOL
SetColumns(ARGS * ap)
{
	showCount = 0;

	return AddColumns(ap);
}


/*
 * Add the columns to display according to the indicated names.
 * The columns are added to the end of the list of columns to display,
 * or else at the column number specified before the column name.
 * This removes any previous usage of the column.
 */
static BOOL
AddColumns(ARGS * ap)
{
	const char *	name;
	COLUMN *	column;
	int		count;
	int		i;
	int		j;
	int		pos;
	char *		table[MAX_WORDS];

	count = ExpandArguments(ap, table, MAX_WORDS);

	if (count < 0)
		return FALSE;

	/*
	 * Default the insert position to be after all existing columns.
	 */
	pos = showCount;

	/*
	 * Go through the column names one by one, inserting them into
	 * the show list.
	 */
	for (i = 0; i < count; i++)
	{
		name = table[i];

		/*
		 * If the column name is actually a number, then the
		 * argument is the column number to insert columns at.
		 * Subtract one to convert it to an array offset.
		 */
		if (isDigit(*name))
		{
			pos = 0;

			while (isDigit(*name))
				pos = pos * 10 + *name++ - '0';

			if (*name || (pos < 0))
			{
				fprintf(stderr, "Bad column position argument\n");

				return FALSE;
			}

			pos--;

			continue;
		}

		/*
		 * This is actually a column name, so look it up.
		 */
		column = FindColumn(name);

		if (column == NULL)
		{
			fprintf(stderr, "Bad column name %s\n", name);

			return FALSE;
		}

		/*
		 * Remove the column if it was already in the column list.
		 */
		for (j = 0; j < showCount; j++)
		{
			if (column == showList[j])
			{
				while (j < showCount)
				{
					showList[j] = showList[j + 1];
					j++;
				}

				showCount--;
			}
		}

		/*
		 * Now insert the column at the specified column position.
		 * Do this by moving all columns at the position up one,
		 * and putting the new column at the proper location.
		 * Make sure the position is valid beforehand, and increment
		 * the position afterward so that the next column to be
		 * inserted goes in after this one.
		 */
		if (pos < 0)
			pos = 0;

		if (pos > showCount)
			pos = showCount;

		for (j = showCount; j > pos; j--)
			showList[j] = showList[j - 1];

		showList[pos++] = column;
		showList[++showCount] = NULL;
	}

	return TRUE;
}


/*
 * Remove the indicated columns from the display.
 */
static BOOL
RemoveColumns(ARGS * ap)
{
	const char *	name;
	COLUMN *	column;
	int		count;
	int		i;
	int		j;
	char *		table[MAX_WORDS];

	count = ExpandArguments(ap, table, MAX_WORDS);

	if (count < 0)
		return FALSE;

	for (i = 0; i < count; i++)
	{
		name = table[i];

		column = FindColumn(name);

		if (column == NULL)
		{
			fprintf(stderr, "Bad column name %s\n", name);

			return FALSE;
		}

		for (j = 0; j < showCount; j++)
		{
			if (column == showList[j])
			{
				while (j < showCount)
				{
					showList[j] = showList[j + 1];
					j++;
				}

				showCount--;
			}
		}
	}

	return TRUE;
}


/*
 * Set the font name.
 */
static BOOL
SetFont(ARGS * ap)
{
	char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing font argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	ReplaceString(&fontName, str);

	return TRUE;
}


/*
 * Set geometry of the window.
 */
static BOOL
SetGeometry(ARGS * ap)
{
	char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing geometry argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	ReplaceString(&geometry, str);

	return TRUE;
}


/*
 * Set the foreground color.
 */
static BOOL
SetForeground(ARGS * ap)
{
	char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing foreground argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	ReplaceString(&foregroundName, str);

	return TRUE;
}


/*
 * Set the background color.
 */
static BOOL
SetBackground(ARGS * ap)
{
	char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing background argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	ReplaceString(&backgroundName, str);

	return TRUE;
}


/*
 * Set the display name for X11.
 */
static BOOL
SetDisplay(ARGS * ap)
{
	char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing display argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	ReplaceString(&displayName, str);

	return TRUE;
}


/*
 * Set the seconds for cpu percentage calculations.
 */
static BOOL
SetPercentSeconds(ARGS * ap)
{
	const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing cpu percentage argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	percentSeconds = GetDecimalNumber(&str);

	if (*str || (percentSeconds < 0) || (percentSeconds > PERCENT_MAX_SECONDS))
	{
		fprintf(stderr, "Bad cpu percentage argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Set the scolling time.
 */
static BOOL
SetScrollTime(ARGS * ap)
{
	const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing scroll time argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	scrollSeconds = GetDecimalNumber(&str);

	if (*str || (scrollSeconds < 0))
	{
		fprintf(stderr, "Bad scroll time argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Set the lines of overlap.
 */
static BOOL
SetOverlapLines(ARGS * ap)
{
	const char *	str;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing overlap argument\n");

		return FALSE;
	}

	ap->count--;
	str = *ap->table++;

	overlapLines = GetDecimalNumber(&str);

	if (*str || (overlapLines < 0))
	{
		fprintf(stderr, "Bad overlap argument\n");

		return FALSE;
	}

	return TRUE;
}


/*
 * Set the color of the information line.
 */
static BOOL
SetInfoColor(ARGS * ap)
{
	const char *	color;
	int		colorId;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing information color argument\n");

		return FALSE;
	}

	ap->count--;
	color = *ap->table++;

	colorId = AllocateColor(color);

	if (colorId == BAD_COLOR_ID)
	{
		fprintf(stderr, "Cannot allocate information color\n");

		return FALSE;
	}

	infoColorId = colorId;

	return TRUE;
}


/*
 * Set the color of the header line.
 */
static BOOL
SetHeaderColor(ARGS * ap)
{
	const char *	color;
	int		colorId;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing header color argument\n");

		return FALSE;
	}

	ap->count--;
	color = *ap->table++;

	colorId = AllocateColor(color);

	if (colorId == BAD_COLOR_ID)
	{
		fprintf(stderr, "Cannot allocate header color\n");

		return FALSE;
	}

	headerColorId = colorId;

	return TRUE;
}


/*
 * Set a condition for coloring or rows.
 */
static BOOL
SetRowColorCondition(ARGS * ap)
{
	char *	color;
	char *	condition;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing color\n");

		return FALSE;
	}

	ap->count--;
	color = *ap->table++;

	if ((ap->count <= 0) || (**ap->table == '-'))
	{
		fprintf(stderr, "Missing condition\n");

		return FALSE;
	}

	ap->count--;
	condition = *ap->table++;

	return ParseRowColorCondition(color, condition);
}


/*
 * Usage text
 */
static	const char *	const usage_text[] =
{
"Usage: ips <show-options> <condition-options> <other-options> <macro-names>",
"",
"<show-options> specify how to display the output:",
"  -col <col> ...           Set columns as the display list",
"  -addcol <col> ...        Add columns to display list",
"  -remcol <col> ...        Remove columns from display list",
"  -sort <col> ...          Add columns to sorting list as normal sort",
"  -revsort <col> ...       Add columns to sorting list as reverse sort",
"  -sortexpr <expr>         Add expression to sorting list as normal sort",
"  -revsortexpr <expr>      Add expression to sorting list as reverse sort",
"  -nosort                  Clear the sorting list",
"  -sep <count>             Set the separation between columns",
"  -width <size>            Set the total width of output for all columns",
"  -colwidth <col> [width]  Set (or default) the width for one column",
"  -noheader                Suppress display of header line",
"  -vert                    Output status in vertical format (multi-line)",
"  -clear                   Clear screen before displaying status in dumb display",
"  -info                    Show information line when in loop mode",
"  -scroll <seconds>        Set time between scrolling display when looping",
"  -overlap <count>         Set number of overlapped lines when scrolling",
"  -once                    Display status once only in dumb display (default)",
"  -loop                    Display status repeatedly in dumb display",
"  -curses                  Display status repeatedly in tty using curses",
"  -x11                     Display status repeatedly in new X11 window",
"  -display <name>          Set display name for X11 (default NULL)",
"  -geometry <string>       Set window geometry for X11 (default \"150x50\")",
"  -font <name>             Set font name for X11 (default \"fixed\")",
"  -foreground <color>      Set foreground color for X11 (default \"black\")",
"  -background <color>      Set background color for X11 (default \"white\")",
"  -headercolor <colors>    Set colors for header line",
"  -infocolor <colors>      Set colors for info line",
"  -rowcolor <colors> <cond>  Set colors for rows satisfying condition",
"",
"<condition-options> restrict which processes can be displayed:",
"  -noself                  Do not show the ips process itself",
"  -noroot                  Do not show processes owned by root",
"  -my                      Show processes with my own user id",
"  -pid <pid> ...           Show specified process ids",
"  -user <user> ...         Show specified user names or ids",
"  -group <group> ...       Show specified group names or ids",
"  -program <program> ...   Show specified program names",
"  -active                  Show active processes",
"  -activetime <seconds>    Set number of seconds processes remain active",
"  -deathtime <seconds>     Set number of seconds to show dead processes",
"  -top [count]             Show number of processes from the top or what fits",
"  -cond <expr>             Show processes for which the expression is nonzero",
"  -showthreads             Show threads instead of just processes",
"",
"<other-options> are:",
"  -sleep <seconds>         Set the sleep interval between loops",
"  -initsleep <seconds>     Set the initial sleep time for active checks",
"  -synctime <seconds>      Synchronize with some status at least this often",
"  -usethreads              Use thread information for process status",
"  -percentseconds <seconds>  Set seconds for cpu percentages (0 to 20)",
"  -read <filename>         Read definitions from the specified filename",
"  -noinit                  Don't read \"~/.ipsrc\" (must be first option)",
"  -end                     Used to end argument lists if needed",
"  -help                    Display this message and exit",
"  -listcolumns             Display the list of allowable columns and exit",
"  -listmacros              Display the list of defined macros and exit",
"  -version                 Display version information and exit",
"  -default [name] ...      Set specified options back to their default values",
"",
"<macro-names> are a list of macros (defined in the initialization files)",
"which are to be expanded into options.  The first character of each macro",
"name is converted to upper case for convenience.",
NULL
};


static void
PrintUsage(void)
{
	const char * const *	text;

	for (text = usage_text; *text; text++)
		fprintf(stderr, "%s\n", *text);

	exit(1);
}


static void
PrintVersion(void)
{
	printf("Version:   %s\n", VERSION);
	printf("Built on:  %s\n", __DATE__);
	printf("Author:    %s\n", AUTHOR_NAME);
	printf("Email:     %s\n", AUTHOR_EMAIL);
}

/* END CODE */
