/*
 * Configurable ps-like program.
 * Linux-specific process information collection routines.
 *
 * Copyright (c) 2014 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 */

#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>

#include "ips.h"


#define	PROCDIR		"/proc"		/* path for proc filesystem */
#define	BEGNAMECHAR	'('		/* begin char for program name */
#define	ENDNAMECHAR	')'		/* end char for program name */

/*
 * A generous size of a buffer which will hold the /proc file names
 * that we are interested in.  These file names only contain small fixed
 * components along with one or two possible integer values.
 */
#define	PROC_FILE_LEN	80


/*
 * Static variables.
 */
static	DIR *	procDir;		/* opendir for /proc */


/*
 * Local procedures.
 */
static	void	ExamineProcessId(pid_t pid, pthread_t tid);
static	void	GetProcessCommandLine(PROC * proc);
static	void	GetProcessEnvironment(PROC * proc);
static	void	GetProcessOpenFileCount(PROC * proc);
static	void	GetProcessCurrentDirectory(PROC * proc);
static	void	GetProcessRootDirectory(PROC * proc);
static	void	GetProcessExecInode(PROC * proc);
static	void	GetProcessStdioDescriptors(PROC * proc);
static	void	GetProcessWaitSymbol(PROC * proc);
static	void	ScanThreads(PROC * proc);
static	BOOL	IsCopyAllowed(const PROC * proc);

static	BOOL	ReadLinkPath(const char * name, char ** retpath,
			int * retpathlength);


/*
 * Initialize things that we need up front for process status collection.
 * Returns TRUE if successful.
 */
BOOL
InitializeProcessData(void)
{
	CollectStaticSystemInfo();

	if (useUserNames)
		CollectUserNames();

	if (useGroupNames)
		CollectGroupNames();

	if (useDeviceNames)
		CollectDeviceNames();

	/*
	 * Open the /proc directory so that the names in it can be found.
	 */
	procDir = opendir(PROCDIR);

	if (procDir == NULL)
	{
		fprintf(stderr, "Cannot open %s\n", PROCDIR);

		return FALSE;
	}

	ancientFlag = TRUE;

	return TRUE;
}


/*
 * Collect system information that we need.
 * This information collected here is static.
 */
void
CollectStaticSystemInfo(void)
{
	const char *	cp;
	int	fd;
	int	cc;
	char	buf[256];

	/*
	 * Get the number of ticks per second.
	 */
	ticksPerSecond = sysconf(_SC_CLK_TCK);

	if (ticksPerSecond <= 0)
		ticksPerSecond = 100;

	/*
	 * Get the page size.
	 */
	pageSize = sysconf(_SC_PAGESIZE);

	if (pageSize <= 0)
		pageSize = 4096;

	/*
	 * Collect the amount of memory on the system.
	 */
	fd = open(PROCDIR "/meminfo", O_RDONLY);

	if (fd < 0)
		return;

	cc = read(fd, buf, sizeof(buf) - 1);

	(void) close(fd);

	if (cc <= 0)
		return;

	buf[cc] = '\0';

	cp = strstr(buf, "MemTotal:");

	if (cp == NULL)
		return;

	cp += 9;

	totalMemoryClicks = GetDecimalNumber(&cp) / (pageSize / 1024);

	/*
	 * Get the starting uptime and time of day.
	 * This will be used to determine the age of processes.
	 */
	startUptime = GetUptime();
	startTime = time(NULL);
}


/*
 * Collect system information that we need.
 * This information collected here is dynamic.
 * For now all we need is the process and thread counts.
 */
void
CollectDynamicSystemInfo(void)
{
	const struct dirent *	dp;
	const char *	cp;
	int	fd;
	int	cc;
	char	buf[256];

	/*
	 * Read the load average information.
	 * This has a format like: 0.03 0.06 0.07 1/214 12496
	 */
	fd = open(PROCDIR "/loadavg", O_RDONLY);

	if (fd < 0)
		return;

	cc = read(fd, buf, sizeof(buf) - 1);

	(void) close(fd);

	if (cc <= 0)
		return;

	buf[cc] = '\0';

	/*
	 * The number of threads on the system follows a slash.
	 */
	cp = strchr(buf, '/');

	if (cp == 0)
		return;

	cp++;

	threadTotalCount = GetDecimalNumber(&cp);

	/*
	 * Get the number of processes by counting the number of
	 * filenames in the /proc directory which are numeric.
	 */
	procTotalCount = 0;

	seekdir(procDir, 0);

	while ((dp = readdir(procDir)) != NULL)
	{
		cp = dp->d_name;

		while (isDigit(*cp))
			cp++;

		if (*cp == '\0')
			procTotalCount++;
	}
}


/*
 * Get the uptime of the system in jiffies.  Since the /proc data format
 * is floating point seconds, we have to convert it.
 * Returns 0 if something is wrong.
 */
ULONG
GetUptime(void)
{
	const char *	cp;
	int	fd;
	int	cc;
	ULONG	intVal;
	ULONG	fracVal;
	ULONG	fracScale;
	char	buf[128];

	fd = open(PROCDIR "/uptime", O_RDONLY);

	if (fd < 0)
		return 0;

	cc = read(fd, buf, sizeof(buf) - 1);

	(void) close(fd);

	if (cc <= 0)
		return 0;

	buf[cc] = '\0';

	cp = buf;

	while (isBlank(*cp))
		cp++;

	intVal = 0;
	fracVal = 0;
	fracScale = 1;

	while (isDigit(*cp))
		intVal = intVal * 10 + *cp++ - '0';

	if (*cp == '.')
		cp++;

	while (isDigit(*cp))
	{
		fracVal = fracVal * 10 + *cp++ - '0';
		fracScale = fracScale * 10;
	}

	if ((*cp != ' ') && (*cp != '\n'))
		return 0;

	return (intVal * ticksPerSecond) + ((fracVal * ticksPerSecond) / fracScale);
}


/*
 * Scan all processes and set their new state.
 */
void
ScanProcesses(void)
{
	const struct dirent *	dp;
	const char *		name;
	pid_t			pid;
	int			i;

	UpdateTimes();

	CollectDynamicSystemInfo();

	/*
	 * If we require our own process information, then get that now.
	 */
	if (useSelf)
		ExamineProcessId(myPid, NO_THREAD_ID);

	/*
	 * If there were no specific pids given, then scan them all by
	 * reading the numeric entries from the "/proc" directory.
	 * Otherwise, examine just the specified processes.
	 */
	if (pidCount == 0)
	{
		seekdir(procDir, 0);

		while ((dp = readdir(procDir)) != NULL)
		{
			name = dp->d_name;

			pid = 0;

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

			if (*name)
				continue;

			if ((pid != myPid) || !useSelf)
				ExamineProcessId(pid, NO_THREAD_ID);
		}
	}
	else
	{
		for (i = 0; i < pidCount; i++)
		{
			if ((pidList[i] != myPid) || !useSelf)
				ExamineProcessId(pidList[i], NO_THREAD_ID);
		}
	}

	RemoveDeadProcesses();

	SortProcesses();

	UpdateProcessCounts();

	ancientFlag = FALSE;
}


/*
 * Collect data about the specified process and thread id.
 * A thread id of NO_THREAD_ID collects the main process data,
 * possibly along with the process data of all of its threads.
 * This allocates a new PROC structure if necessary.
 * If the process is successfully examined, the valid flag is set.
 */
static void
ExamineProcessId(pid_t pid, pthread_t tid)
{
	PROC *		proc;
	int		fd;
	int		cc;
	int		i;
	char *		begName;
	char *		endName;
	const char *	cp;
	long		ticksFromStart;
	long		secondsFromStart;
	BOOL		okSkip;
	struct	stat	statBuf;
	char		buf[512];
	char		name[PROC_FILE_LEN];

	proc = FindProcess(pid, tid);

	proc->isValid = FALSE;
	proc->isShown = FALSE;

	okSkip = ((pid != myPid) || !useSelf);

	if (okSkip && noSelf && (pid == myPid))
		return;

	if (proc->isThread)
		sprintf(name, "%s/%ld/task/%ld/stat", PROCDIR, (long) pid, (long) tid);
	else
		sprintf(name, "%s/%ld/stat", PROCDIR, (long) pid);

	fd = open(name, O_RDONLY);

	if (fd < 0)
	{
		if (errno != ENOENT)
			perror(name);

		return;
	}

	if (fstat(fd, &statBuf) < 0)
	{
		(void) close(fd);

		return;
	}

	proc->deathTime = 0;

	if (okSkip)
	{
		if (myProcs && (statBuf.st_uid != myUid))
		{
			(void) close(fd);

			return;
		}

		if (noRoot && (statBuf.st_uid == 0))
		{
			(void) close(fd);

			return;
		}

		if ((userCount > 0))
		{
			for (i = 0; i < userCount; i++)
			{
				if (statBuf.st_uid == userList[i])
					break;
			}

			if (i == userCount)
			{
				(void) close(fd);

				return;
			}
		}

		if ((groupCount > 0))
		{
			for (i = 0; i < groupCount; i++)
			{
				if (statBuf.st_gid == groupList[i])
					break;
			}

			if (i == groupCount)
			{
				(void) close(fd);

				return;
			}
		}
	}

	proc->uid = statBuf.st_uid;
	proc->gid = statBuf.st_gid;

	/*
	 * Read the process status into a buffer.
	 */
	cc = read(fd, buf, sizeof(buf));

	if (cc < 0)
	{
		(void) close(fd);

		return;
	}

	(void) close(fd);

	if (cc == sizeof(buf))
	{
		fprintf(stderr, "status buffer overflow");

		return;
	}

	buf[cc] = '\0';

	/*
	 * The program name begins after a left parenthesis.
	 * Break the status string into two at that point.
	 */
	begName = strchr(buf, BEGNAMECHAR);

	if (begName == NULL)
	{
		fprintf(stderr, "Cannot find start of program name\n");

		return;
	}

	*begName++ = '\0';

	/*
	 * The program name ends after a right parenthesis.
	 * But, look for the rightmost one in case the program name
	 * itself contains a parenthesis!
	 */
	endName = strchr(begName, ENDNAMECHAR);

	if (endName == NULL)
	{
		fprintf(stderr, "Cannot find end of program name\n");

		return;
	}

	while ((cp = strchr(endName + 1, ENDNAMECHAR)) != NULL)
		endName = begName + (cp - begName);

	MakePrintable(begName, endName - begName);

	*endName++ = '\0';

	strncpy(proc->program, begName, MAX_PROGRAM_LEN);

	proc->program[MAX_PROGRAM_LEN] = '\0';

	/*
	 * Find the process state character, and then parse the numbers on
	 * the rest of the line to collect the remaining state information.
	 */
	cp = endName;

	while (isBlank(*cp))
		cp++;

	if ((*cp == '\0') || (*cp == '\n') || isDigit(*cp))
	{
		fprintf(stderr, "Bad proc state character\n");

		return;
	}

	proc->state = *cp++;
	proc->states[0] = proc->state;
	proc->states[1] = '\0';
	proc->parentPid = GetDecimalNumber(&cp);
	proc->processGroup = GetDecimalNumber(&cp);
	proc->sessionId = GetDecimalNumber(&cp);
	proc->ttyDevice = GetDecimalNumber(&cp);
	proc->ttyProcessGroup = GetDecimalNumber(&cp);
	proc->flags = GetDecimalNumber(&cp);
	proc->minorFaults = GetDecimalNumber(&cp);
	proc->childMinorFaults = GetDecimalNumber(&cp);
	proc->majorFaults = GetDecimalNumber(&cp);
	proc->childMajorFaults = GetDecimalNumber(&cp);
	proc->userRunTime = GetDecimalNumber(&cp);
	proc->systemRunTime = GetDecimalNumber(&cp);
	proc->childUserRunTime = GetDecimalNumber(&cp);
	proc->childSystemRunTime = GetDecimalNumber(&cp);
	proc->priority = GetDecimalNumber(&cp);
	proc->nice = GetDecimalNumber(&cp);
	proc->threadCount = GetDecimalNumber(&cp);
	proc->itRealValue = GetDecimalNumber(&cp);
	proc->startTimeTicks = GetDecimalNumber(&cp);
	proc->virtualSize = GetDecimalNumber(&cp);
	proc->rss = GetDecimalNumber(&cp);
	proc->rssLimit = GetDecimalNumber(&cp);
	proc->startCode = GetDecimalNumber(&cp);
	proc->endCode = GetDecimalNumber(&cp);
	proc->startStack = GetDecimalNumber(&cp);
	proc->esp = GetDecimalNumber(&cp);
	proc->eip = GetDecimalNumber(&cp);
	proc->signal = GetDecimalNumber(&cp);
	proc->sigBlock = GetDecimalNumber(&cp);
	proc->sigIgnore = GetDecimalNumber(&cp);
	proc->sigCatch = GetDecimalNumber(&cp);
	proc->waitChan = GetDecimalNumber(&cp);
	proc->pagesSwapped = GetDecimalNumber(&cp);
	proc->childPagesSwapped = GetDecimalNumber(&cp);
	proc->exitSignal = GetDecimalNumber(&cp);
	proc->processor = GetDecimalNumber(&cp);
	proc->realTimePriority = GetDecimalNumber(&cp);
	proc->policy = GetDecimalNumber(&cp);

	/*
	 * Convert the processes start time into clock time and age.
	 * Get the number of ticks after we started when the specified
	 * process started and convert that to elapsed seconds.
	 * This can be positive or negative according to whether the
	 * process started before or after us.
	 */
	ticksFromStart = proc->startTimeTicks - startUptime;

	if (ticksFromStart >= 0)
		secondsFromStart = ticksFromStart / ticksPerSecond;
	else
		secondsFromStart = -((-ticksFromStart) / ticksPerSecond);

	/*
	 * Add the elapsed seconds to the clock time when we started
	 * to get the clock time the process started.
	 */
	proc->startTimeClock = startTime + secondsFromStart;

	/*
	 * If the process is newly seen then save its starting runtime.
	 * This is used for the cpu percentage calculation.
	 */
	if (proc->isNew)
		proc->firstCpuTime = proc->userRunTime + proc->systemRunTime;

	/*
	 * See if the process has changed state, and is therefore active.
	 */
	CheckActiveProcess(proc);

	/*
	 * Get several pieces of extra data, but only if the process
	 * has changed state, and only if these data are required.
	 * However, get the extra data always if it is older than
	 * the specified sync time.
	 */
	if (proc->isChanged ||
		((proc->lastSyncTime + syncTime) <= currentTime))
	{
		proc->lastSyncTime = currentTime;

		GetProcessOpenFileCount(proc);
		GetProcessStdioDescriptors(proc);
		GetProcessCurrentDirectory(proc);
		GetProcessRootDirectory(proc);
		GetProcessExecInode(proc);
		GetProcessCommandLine(proc);
		GetProcessEnvironment(proc);
		GetProcessWaitSymbol(proc);
	}

	proc->liveCounter = liveCounter;
	proc->isValid = TRUE;

	if (IsShownProcess(proc))
		proc->isShown = TRUE;

	/*
	 * If we are showing or using threads and this process has more than
	 * one thread then collect information on them.
	 */
	if (showThreads || useThreads)
		ScanThreads(proc);

	/*
	 * Store the states string for threads if any.
	 */
	BuildStates(proc);
}


/*
 * Scan the list of threads for the indicated process.
 */
static void
ScanThreads(PROC * proc)
{
	DIR *			dir;
	const struct dirent *	dp;
	const char *		cp;
	pthread_t		tid;
	char			name[PROC_FILE_LEN];

	/*
	 * If this is a thread or there aren't multiple threads then
	 * do nothing.
	 */
	if (proc->isThread || (proc->threadCount <= 1))
		return;

	/*
	 * Read the list of thread ids from the task directory of the
	 * main process and examine each one.
	 */
	sprintf(name, "%s/%ld/task", PROCDIR, (long) proc->pid);

	dir = opendir(name);

	if (dir == NULL)
		return;

	while ((dp = readdir(dir)) != NULL)
	{
		cp = dp->d_name;

		tid = 0;

		while (isDigit(*cp))
			tid = tid * 10 + (*cp++ - '0');

		if (*cp == '\0')
			ExamineProcessId(proc->pid, tid);
	}

	closedir(dir);
}


/*
 * Get the wait channel symbol name for the process.
 */
static void
GetProcessWaitSymbol(PROC * proc)
{
	int	fd;
	int	len;
	char	name[PROC_FILE_LEN];

	/*
	 * If the wait channel is 0 or is not used then
	 * set an empty symbol name.
	 */
	if (!useWaitChan || (proc->waitChan == 0))
	{
		proc->waitChanSymbol[0] = '\0';

		return;
	}

	/*
	 * Open the wait channel file and read it into a buffer
	 * including trying to read one extra character.
	 */
	sprintf(name, "%s/%ld/wchan", PROCDIR, (long) proc->pid);

	fd = open(name, O_RDONLY);
	len = -1;

	if (fd >= 0)
	{
		len = read(fd, proc->waitChanSymbol, MAX_WCHAN_LEN + 1);

		(void) close(fd);
	}

	/*
	 * If the symbol wasn't found then store a dash for the symbol.
	 */
	if (len < 0)
	{
		proc->waitChanSymbol[0] = '-';
		proc->waitChanSymbol[1] = '\0';

		return;
	}

	/*
	 * If we read one more character than our limit, then we missed
	 * some of the symbol name line, so flag that by replacing the
	 * last character of the allowed length with a vertical bar.
	 */
	if (len > MAX_WCHAN_LEN)
	{
		len = MAX_WCHAN_LEN;
		proc->waitChanSymbol[MAX_WCHAN_LEN - 1] = '|';
	}

	/*
	 * Null terminate the wait symbol name and make it printable.
	 */
	proc->waitChanSymbol[len] = '\0';

	MakePrintable(proc->waitChanSymbol, len);
}


/*
 * Get the command line for the specified process.
 * If there isn't one, then use the program name as the command line,
 * surrounded by parenthesis.  If the command line is small, then it
 * fits within the proc structure, otherwise we have to malloc it.
 * Threads copy the command line string from the main process.
 */
static void
GetProcessCommandLine(PROC * proc)
{
	int	fd;
	int	len;
	char	name[PROC_FILE_LEN];
	char	buffer[MAX_COMMAND_LEN + 2];

	len = 0;

	if (!useCommand)
	{
		proc->hasCommand = FALSE;
		proc->commandLength = 0;
		proc->command[0] = '\0';

		return;
	}

	proc->hasCommand = TRUE;

	/*
	 * If we are a thread process and have an owner then copy the
	 * command from the owner structure.
	 */
	if (IsCopyAllowed(proc))
	{
		SetCommandLine(proc, proc->owner->command, proc->owner->commandLength);

		return;
	}

	/*
	 * Open the command line file and read it into a large buffer,
	 * including trying to read one extra character.
	 */
	sprintf(name, "%s/%ld/cmdline", PROCDIR, (long) proc->pid);

	fd = open(name, O_RDONLY);

	if (fd >= 0)
	{
		len = read(fd, buffer, MAX_COMMAND_LEN + 1);

		(void) close(fd);
	}

	/*
	 * If we could not get the command line, or if there was none
	 * there, then use the program name surrounded by parenthesis.
	 * Remember that there is no real command line for user tests.
	 */
	if ((fd < 0) || (len <= 0))
	{
		proc->hasCommand = FALSE;
		len = strlen(proc->program);

		buffer[0] = '(';
		memcpy(&buffer[1], proc->program, len);
		buffer[len + 1] = ')';

		len += 2;
	}

	/*
	 * If we read one more character than our limit, then we missed
	 * some of the command line, so flag that by replacing the last
	 * character of the allowed length with a vertical bar.
	 */
	if (len > MAX_COMMAND_LEN)
	{
		len = MAX_COMMAND_LEN;
		buffer[MAX_COMMAND_LEN - 1] = '|';
	}

	/*
	 * Null terminate the command line and make it printable.
	 */
	buffer[len] = '\0';

	MakePrintable(buffer, len);

	/*
	 * Store the command line into the structure.
	 */
	SetCommandLine(proc, buffer, len);
}


/*
 * Get the environment for the specified process.
 * This could be very large, so it is allocated dynamically and we
 * attempt to share strings among processes.  Threads copy the
 * environment string value from the main process.
 */
static void
GetProcessEnvironment(PROC * proc)
{
	int	fd;
	int	len;
	char	name[PROC_FILE_LEN];
	char	buffer[MAX_ENVIRON_LEN + 2];

	if (!useEnvironment)
		return;

	/*
	 * If we are a thread process and have an owner then copy the
	 * environment from the owner structure.
	 */
	if (IsCopyAllowed(proc))
	{
		SetSharedString(&proc->environment, &proc->environmentLength,
			proc->owner->environment, proc->owner->environmentLength);

		return;
	}

	/*
	 * Open the environment file and read it into a large buffer,
	 * including trying to read one extra character.
	 */
	sprintf(name, "%s/%ld/environ", PROCDIR, (long) proc->pid);

	fd = open(name, O_RDONLY);

	len = 0;

	if (fd >= 0)
	{
		len = read(fd, buffer, MAX_ENVIRON_LEN + 1);

		(void) close(fd);
	}

	/*
	 * If we could not open the file, or if there was nothing there,
	 * then free any old environment string and point to a null string.
	 */
	if ((fd < 0) || (len <= 0))
	{
		FreeSharedString(proc->environment);

		proc->environment = emptyString;
		proc->environmentLength = 0;

		return;
	}

	/*
	 * If we read one more character than our limit, then we missed
	 * some of the environment, so flag that by replacing the last
	 * character of the allowed length with a vertical bar.
	 */
	if (len > MAX_ENVIRON_LEN)
	{
		len = MAX_ENVIRON_LEN;
		buffer[MAX_ENVIRON_LEN - 1] = '|';
	}

	/*
	 * Null terminate the environment string and make it printable.
	 */
	buffer[len] = '\0';

	MakePrintable(buffer, len);

	/*
	 * Store the environment line into the structure.
	 */
	SetSharedString(&proc->environment, &proc->environmentLength,
		buffer, len);
}


/*
 * Get the number of open files for the process.
 * This is expensive and so is only gotten if the column is actually in use.
 * The permissions only allow this information to be gotten for the same user
 * id or if you are running as root.
 */
static void
GetProcessOpenFileCount(PROC * proc)
{
	DIR *			dir;
	const struct dirent *	dp;
	const char *		cp;
	int			count;
	char			name[PROC_FILE_LEN];

	proc->openFiles = -1;

	if (!useOpenFiles)
		return;

	/*
	 * If we are a thread process and have an owner then copy the
	 * open file count from the main process.
	 */
	if (IsCopyAllowed(proc))
	{
		proc->openFiles = proc->owner->openFiles;

		return;
	}

	/*
	 * Open the fd directory in the process status and count the
	 * number of numeric file names.
	 */
	sprintf(name, "%s/%ld/fd", PROCDIR, (long) proc->pid);

	dir = opendir(name);

	if (dir == NULL)
		return;

	count = 0;

	while ((dp = readdir(dir)) != NULL)
	{
		cp = dp->d_name;

		while (isDigit(*cp))
			cp++;

		if (*cp == '\0')
			count++;
	}

	closedir(dir);

	proc->openFiles = count;
}


/*
 * Get the current working directory of the specified process.
 * This is expensive and so is only gotten if the column is actually in use.
 * The permissions only allow this information to be gotten for the same user
 * id or if you are running as root.
 */
static void
GetProcessCurrentDirectory(PROC * proc)
{
	char	name[PROC_FILE_LEN];

	if (!useCurrentDirectory)
		return;

	/*
	 * If we are a thread process and have an owner then copy the
	 * current directory path from the main process.
	 */
	if (IsCopyAllowed(proc))
	{
		SetSharedString(&proc->cwdPath, &proc->cwdPathLength,
			proc->owner->cwdPath, proc->owner->cwdPathLength);

		return;
	}

	sprintf(name, "%s/%ld/cwd", PROCDIR, (long) proc->pid);

	ReadLinkPath(name, &proc->cwdPath, &proc->cwdPathLength);
}


/*
 * Get the current root directory of the specified process.
 * This is expensive and so is only gotten if the column is actually in use.
 * The permissions only allow this information to be gotten for the same user
 * id or if you are running as root.
 */
static void
GetProcessRootDirectory(PROC * proc)
{
	char	name[PROC_FILE_LEN];

	if (!useRootDirectory)
		return;

	/*
	 * If we are a thread process and have an owner then copy the
	 * root directory path from the main process.
	 */
	if (IsCopyAllowed(proc))
	{
		SetSharedString(&proc->rootPath, &proc->rootPathLength,
			proc->owner->rootPath, proc->owner->rootPathLength);

		return;
	}

	sprintf(name, "%s/%ld/root", PROCDIR, (long) proc->pid);

	ReadLinkPath(name, &proc->rootPath, &proc->rootPathLength);
}


/*
 * Get the device and inode of the executable file for specified process.
 * This is expensive and so is only gotten if the column is actually in use.
 * The permissions only allow this information to be gotten for the same user
 * id or if you are running as root.
 */
static void
GetProcessExecInode(PROC * proc)
{
	char	name[PROC_FILE_LEN];

	if (!useExecInode)
		return;

	/*
	 * If we are a thread process and have an owner then copy the
	 * executable path from the main process.
	 */
	if (IsCopyAllowed(proc))
	{
		SetSharedString(&proc->execPath, &proc->execPathLength,
			proc->owner->execPath, proc->owner->execPathLength);

		return;
	}

	sprintf(name, "%s/%ld/exe", PROCDIR, (long) proc->pid);

	ReadLinkPath(name, &proc->execPath, &proc->execPathLength);
}


/*
 * Get information about processes three standard file descriptors.
 * This is expensive and so is only gotten if the columns are actually in use.
 * The permissions only allow this information to be gotten for the same user
 * id or if you are running as root.
 */
static void
GetProcessStdioDescriptors(PROC * proc)
{
	int	fd;
	int	dummy;
	char	name[PROC_FILE_LEN];

	for (fd = 0; fd <= 2; fd++)
	{
		if (!useStdioTable[fd])
			continue;

		/*
		 * If we are a thread process and have an owner then copy the
		 * stdio path from the main process.
		 */
		if (IsCopyAllowed(proc))
		{
			SetSharedString(&proc->stdioPaths[fd], &dummy,
				proc->owner->stdioPaths[fd],
				strlen(proc->owner->stdioPaths[fd]));

			continue;
		}

		sprintf(name, "%s/%ld/fd/%d", PROCDIR, (long) proc->pid, fd);

		ReadLinkPath(name, &proc->stdioPaths[fd], &dummy);
	}
}


/*
 * Get the destination path and length for the specified symbolic link.
 * The path is returned into the indicated variables, which are updated.
 * The returned string is allocated within a shared pool of strings and
 * so can only be freed by calling the appropriate routine.  Returns TRUE
 * if the information was able to be obtained.
 */
static BOOL
ReadLinkPath(const char * name, char ** retpath, int * retpathlength)
{
	char *	newPath;
	char *	oldPath;
	int	len;
	int	oldLength;
	char	buffer[MAX_PATH_LEN];

	/*
	 * Save the values for the old path.
	 */
	oldPath = *retpath;
	oldLength = *retpathlength;

	/*
	 * Read the value of the symbolic link.
	 */
	len = readlink(name, buffer, sizeof(buffer));

	if ((len <= 0) || (len == sizeof(buffer)))
	{
		FreeSharedString(oldPath);

		*retpath = emptyString;
		*retpathlength = 0;

		return FALSE;
	}

	buffer[len] = '\0';

	/*
	 * If the path is the same as the existing one then do nothing.
	 */
	if ((len == oldLength) && (strcmp(oldPath, buffer) == 0))
		return TRUE;

	/*
	 * The value has changed.  Delete the old string.
	 */
	FreeSharedString(oldPath);

	*retpath = emptyString;
	*retpathlength = 0;

	/*
	 * Allocate a new shared string to store the new value.
	 */
	newPath = AllocateSharedString(buffer, len);

	if (newPath == NULL)
		return FALSE;

	/*
	 * Return the new string value.
	 */
	*retpath = newPath;
	*retpathlength = len;

	return TRUE;
}


/*
 * Return whether or now we are a thread process that is allowed to
 * copy data from the owning main process.
 */
static BOOL
IsCopyAllowed(const PROC * proc)
{
	if (noCopy)
		return FALSE;

	return (proc->isThread && (proc->owner != 0));
}


/*
 * Pick the best state character of the two states which most indicates
 * what the multiple threads of a process are doing.
 */
int
PickBestState(int state1, int state2)
{
	if ((state1 == 'R') || (state2 == 'R'))
		return 'R';

	if ((state1 == 'D') || (state2 == 'D'))
		return 'D';

	if ((state1 == 'S') || (state2 == 'S'))
		return 'S';

	return state1;
}


/* END CODE */
