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

#include <signal.h>
#include <sys/stat.h>

#include "ips.h"


/*
 * List of columns being shown.
 */
int	showCount;
COLUMN *showList[MAX_COLUMNS];


/*
 * Other global variables.
 */
ULONG		startUptime;		/* uptime jiffies at start */
time_t		startTime;		/* clock time at start */
time_t		currentTime;		/* current clock time */
long		totalMemoryClicks;	/* amount of total memory */
long		ticksPerSecond;		/* number of clock ticks per second */
long		pageSize;		/* number of bytes in a page */
ULONG		liveCounter;		/* counter for live procs */
int		newCpuIndex;		/* new CPU sample index */
int		oldCpuIndex;		/* old CPU sample index */
BOOL		ancientFlag;		/* seeing pre-existing procs */
BOOL		showThreads;		/* show threads */
BOOL		noCopy;			/* don't copy data for threads */
BOOL		noSelf;			/* don't show myself */
BOOL		noRoot;			/* don't show root procs */
BOOL		isInfoShown;		/* show info line at top */
BOOL		noHeader;		/* don't show column header */
BOOL		myProcs;		/* only show my procs */
BOOL		activeOnly;		/* only show active procs */
BOOL		clearScreen;		/* clear screen each loop */
BOOL		isLooping;		/* loop showing status */
BOOL		isRunning;		/* we still want to run */
BOOL		isFrozen;		/* data collection is frozen */
BOOL		isUpdateForced;		/* update once even if frozen */
BOOL		isRefreshNeeded;	/* need to refresh display */
BOOL		useCurses;		/* use curses display */
BOOL		useX11;			/* use X11 display */
BOOL		isVertical;		/* isVertical output format */
BOOL		isTopMode;		/* top option was used */
BOOL		isTopAuto;		/* autosize height for top */
BOOL		useOpenFiles;		/* using open file info */
BOOL		useCurrentDirectory;	/* using current dir info */
BOOL		useRootDirectory;	/* using root dir info */
BOOL		useExecInode;		/* using executable info */
BOOL		useDeviceNames;		/* using device name info */
BOOL		useUserNames;		/* using user name info */
BOOL		useGroupNames;		/* using group name info */
BOOL		useInitSleep;		/* using initial sleep */
BOOL		useCommand;		/* using command line info */
BOOL		useSelf;		/* using my own proc info */
BOOL		useEnvironment;		/* using environment info */
BOOL		useWaitChan;		/* using wait channel symbol */
BOOL		useThreads;		/* use thread data */
BOOL		useStdioTable[3];	/* using various stdio info */
pid_t		myPid;			/* my pid */
uid_t		myUid;			/* my real user id */
gid_t		myGid;			/* my real group id */
dev_t		nullDevice;		/* device of /dev/null */
ino_t		nullInode;		/* inode of /dev/null */
int		procAllocCount;		/* allocated proc structures */
int		deathTime;		/* seconds for dead processes */
int		activeTime;		/* seconds for active procs */
int		pidCount;		/* pids in pidList */
int		userCount;		/* users in userList */
int		groupCount;		/* groups in groupList */
int		programCount;		/* programs in programList */
int		outputWidth;		/* width of output */
int		outputHeight;		/* height of output */
int		separation;		/* blanks between columns */
int		sleepTimeMs;		/* milliseconds between loops */
int		syncTime;		/* seconds between syncs */
int		initSleepTime;		/* seconds for initial sleep */
int		topCount;		/* number of procs for top */
int		percentSeconds;		/* seconds for cpu percentages */
int		scrollSeconds;		/* seconds between scrolling */
int		overlapLines;		/* lines of overlap */
int		skipCount;		/* lines to skip in display */
int		procShowCount;		/* processes wanting showing */
int		procTotalCount;		/* count of all processes */
int		threadShowCount;	/* threads wanting showing */
int		threadTotalCount;	/* count of all threads */
int		infoColorId;		/* color id for info line */
int		headerColorId;		/* color id for header line */
DISPLAY *	display;		/* the output display device */
PROC *		processList;		/* list of existent procs */
PROC *		freeProcessList;	/* free proc structure list */
char *		geometry;		/* window geometry string */
char *		fontName;		/* font name */
char *		foregroundName;		/* foreground color name */
char *		backgroundName;		/* background color name */
char *		displayName;		/* display name */
const char *	displayType = DISPLAY_TYPE_TTY;		/* display type */
char		emptyString[4];		/* empty string */
char		rootString[4] = "/";	/* root path string */
pid_t		pidList[MAX_PIDS];	/* pids to be shown */
uid_t		userList[MAX_USERS];	/* user ids to be shown */
gid_t		groupList[MAX_GROUPS];	/* group ids to be shown */
char		programList[MAX_PROGRAMS][MAX_PROGRAM_LEN + 2];


/*
 * Static procedures.
 */
static	void	GetTerminalSize(void);
static	void	SetUseFlags(void);
static	void	VerifyDescriptors(void);
static	void	HandleSigPipe(int);


int
main(int argc, char ** argv)
{
	ARGS *	ap;
	ARGS	args;

	/*
	 * Make sure someone hasn't closed off our standard file
	 * descriptors so that we don't accidentally overwrite
	 * something with our output.
	 */
	VerifyDescriptors();

	ap = &args;
	ap->table = ++argv;
	ap->count = --argc;

	myPid = getpid();
	myUid = getuid();
	myGid = getgid();

	DpySetDisplay(NULL);

	InitializeColors();

	DefaultAllOptions();

	/*
	 * Parse the system definition file.
	 */
	if (!ParseSystemInitFile())
		return 1;

	/*
	 * Check specially for the -noinit option before reading the user's
	 * option file.  This option MUST be immediately after the program
	 * name.  This check is a bit of a hack.
	 */
	if ((ap->count > 0) && (strcmp(ap->table[0], "-noinit") == 0))
	{
		ap->table++;
		ap->count--;
	}
	else if (!ParseUserInitFile())
		return 1;

	/*
	 * Look up and execute the initial macros if they exist.
	 */
	if (MacroExists(MACRO_TYPE_OPTION, SYSTEM_INIT_MACRO) &&
		!ExpandOptionName(SYSTEM_INIT_MACRO, 0))
	{
		fprintf(stderr,
			"Problem expanding system initial macro \"%s\"\n",
			SYSTEM_INIT_MACRO);

		return 1;
	}

	if (MacroExists(MACRO_TYPE_OPTION, USER_INIT_MACRO) &&
		!ExpandOptionName(USER_INIT_MACRO, 0))
	{
		fprintf(stderr,
			"Problem expanding system initial macro \"%s\"\n",
			USER_INIT_MACRO);

		return 1;
	}

	/*
	 * Parse the command line options possibly expanding them.
	 * Indicate that we are level zero so that bare macro names on the
	 * command line will be recognized even if they are lower case.
	 */
	if (!ParseOptions(ap, 0))
		return 1;

	/*
	 * Set the flags for what items we need collecting based on
	 * the columns that have been referenced.
	 */
	SetUseFlags();

	/*
	 * Initialize for collecting of process data.
	 */
	if (!InitializeProcessData())
		return 1;

	/*
	 * Catch SIGPIPE so we can be clean if a window is closed.
	 */
	signal(SIGPIPE, HandleSigPipe);

	/*
	 * Initialize for displaying of process status.
	 */
	if (!InitializeDisplay())
		return 1;

	/*
	 * If we require a time interval before displaying results
	 * (such as for calculating percentage cpu), then collect
	 * the initial process status and sleep a while.
	 */
	InitialProcessScan();

	/*
	 * Here is the main loop.  It terminates after one iteration
	 * if we just want a snapshot.
	 */
	isRunning = TRUE;

	while (isRunning)
	{
		/*
		 * Release any string storage used during the last loop.
		 */
		FreeTempStrings();

		/*
		 * Collect the new process status if we aren't frozen
		 * unless the update forced flag was also set.
		 */
		if (!isFrozen || isUpdateForced)
			ScanProcesses();

		isUpdateForced = FALSE;

		/*
		 * Recalculate the window size information.
		 */
		GetTerminalSize();

		/*
		 * Show the selected processes.
		 */
		ShowSelectedProcesses();

		/*
		 * If we don't want to loop, then we are done.
		 */
		if (!isLooping)
			break;

		/*
		 * Sleep while handling commands if any.
		 * If we are frozen then sleep a long time (i.e, 10 minutes).
		 */
		WaitForCommands(isFrozen ? 60*10*1000 : sleepTimeMs);
	}

	/*
	 * Close the display and return success.
	 */
	DpyClose();

	return 0;
}


/*
 * Set the use variables according to the items required by the columns
 * that have been referenced.  These variables are used to avoid
 * collecting expensive-to-obtain data when it is not required.
 */
static void
SetUseFlags(void)
{
	int	i;
	USEFLAG	flags;

	flags = USE_NONE;

	/*
	 * Scan the columns that are being shown.
	 */
	for (i = 0; i < showCount; i++)
		flags |= showList[i]->useFlag;

	/*
	 * Add in the columns that are being sorted by.
	 */
	flags |= GetSortingUseFlags();

	/*
	 * Add in the columns referenced by conditions.
	 */
	flags |= GetConditionUseFlags();

	/*
	 * Add in the columns referenced by coloring conditions.
	 */
	flags |= GetRowColorUseFlags();

	/*
	 * Now set the boolean variables according to the flags.
	 */
	if (flags & USE_INIT)
		useInitSleep = TRUE;

	if (flags & USE_USER_NAME)
		useUserNames = TRUE;

	if (flags & USE_GROUP_NAME)
		useGroupNames = TRUE;

	if (flags & USE_DEV_NAME)
		useDeviceNames = TRUE;

	if (flags & USE_OPEN_FILE)
		useOpenFiles = TRUE;

	if (flags & USE_CURR_DIR)
		useCurrentDirectory = TRUE;

	if (flags & USE_ROOT_DIR)
		useRootDirectory = TRUE;

	if (flags & USE_EXEC_INODE)
		useExecInode = TRUE;

	if (flags & USE_COMMAND)
		useCommand = TRUE;

	if (flags & USE_SELF)
		useSelf = TRUE;

	if (flags & USE_ENVIRON)
		useEnvironment = TRUE;

	if (flags & USE_WCHAN)
		useWaitChan = TRUE;

	if (flags & USE_THREADS)
		useThreads = TRUE;

	if (flags & USE_STDIN)
		useStdioTable[0] = TRUE;

	if (flags & USE_STDOUT)
		useStdioTable[1] = TRUE;

	if (flags & USE_STDERR)
		useStdioTable[2] = TRUE;
}


/*
 * Routine called to get the new terminal size from the display device.
 */
static void
GetTerminalSize(void)
{
	outputWidth = DpyGetCols();
	outputHeight = DpyGetRows();

	if (outputWidth <= 0)
		outputWidth = 1;

	if (outputHeight <= 0)
		outputHeight = 1;

	/*
	 * If we are automatically doing the top few processes
	 * then set the top count to the number of lines.
	 */
	if (isTopAuto)
	{
		topCount = outputHeight;

		if (DpyDoesScroll())
			topCount--;

		if (!noHeader)
			topCount--;

		if (isInfoShown)
			topCount--;

		if (topCount <= 0)
			topCount = 1;
	}
}


/*
 * Make sure that the standard file descriptors are opened.
 * If not, then stop right now without doing anything.
 * This check is required so that we can't be tricked into
 * writing error output into opened files.
 */
static void
VerifyDescriptors(void)
{
	struct stat	statBuf;

	if ((fstat(STDIN_FILENO, &statBuf) < 0) ||
		(fstat(STDOUT_FILENO, &statBuf) < 0) ||
		(fstat(STDERR_FILENO, &statBuf) < 0))
	{
		_exit(99);
	}
}


/*
 * Routine to catch SIGPIPE so we can exit cleanly.
 */
static void
HandleSigPipe(int arg)
{
	_exit(2);
}

/* END CODE */
