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

#include "ips.h"


/*
 * Static variables.
 */
static	long	elapsedMilliseconds;		/* time between CPU percentage samples */
static	struct	timeval	currentTimeval;		/* current accurate time */
static	struct	timeval	baseTimeval;		/* accurate beginning time of current sample */
static	struct	timeval	cpuSampleTimes[PERCENT_SAMPLES];	/* times of cpu samples */


/*
 * Static procedures.
 */
static	void	ScanDeadProcesses(BOOL isThread);
static	BOOL	IsProcessRemovable(PROC * proc);
static	void	FreeProcess(PROC * proc);
static	void	RemoveOwnerThread(PROC * proc);
static	BOOL	AppendState(PROC * proc, short * countTable, int state);


/*
 * Perform the initial process scan if required, and sleep after it.
 * This is required if some columns are selected which require more than
 * one sample to calculate the value (e.g., cpu percentage), or if the
 * user wanted to only show active processes.
 */
void
InitialProcessScan(void)
{
	if (initSleepTime <= 0)
		return;

	if (activeOnly || useInitSleep)
	{
		ScanProcesses();

		sleep(initSleepTime);
	}
}


/*
 * Find the process structure with the specified pid and tid.
 * A main process has a tid value of NO_TID_ID.
 * If the process is new, then it is malloc'd and flagged as such.
 * A new process structure has mostly rubbish values.
 * The process structure is linked into the process list.
 * This deliberately does not find dead processes since a pid
 * could be immediately reused but we still want to see the
 * old process data for a while.
 */
PROC *
FindProcess(pid_t pid, pthread_t tid)
{
	PROC *	proc;
	int	index;

	/*
	 * See if the process is already in the process list.
	 * If so, return the found structure.
	 */
	for (proc = processList; proc; proc = proc->next)
	{
		if ((proc->pid == pid) && (proc->tid == tid) &&
/*			(proc->deathTime == 0) */ 1)
		{
			return proc;
		}
	}

	/*
	 * Nope, so allocate a new structure either from the free
	 * list or else using malloc.
	 */
	proc = freeProcessList;

	if (proc)
	{
		freeProcessList = proc->next;
	}
	else
	{
		proc = AllocMemory(sizeof(PROC));

		procAllocCount++;
	}

	/*
	 * Initialise some of its columns.
	 */
	proc->next = NULL;
	proc->nextThread = NULL;
	proc->owner = NULL;
	proc->pid = pid;
	proc->tid = tid;
	proc->isThread = (tid != NO_THREAD_ID);
	proc->isNew = TRUE;
	proc->isValid = FALSE;
	proc->isActive = FALSE;
	proc->isShown = FALSE;
	proc->hasCommand = FALSE;
	proc->isChanged = TRUE;
	proc->isAncient = ancientFlag;
	proc->state = ' ';
	proc->states[0] = '\0';
	proc->deathTime = 0;
	proc->startTimeTicks = 0;
	proc->startTimeClock = 0;
	proc->firstCpuTime = 0;
	proc->lastSavedTime = 0;
	proc->lastActiveTime = 0;
	proc->lastSyncTime = 0;
	proc->liveCounter = 0;
	proc->percentCpu = 0;
	proc->percentMemory = 0;
	proc->threadCount = 0;
	proc->runOrder = 0;
	proc->openFiles = 0;
	proc->endCode = 0;
	proc->oldSystemRunTime = 0;
	proc->oldUserRunTime = 0;
	proc->policy = 0;
	proc->realTimePriority = 0;
	proc->commandLength = 0;
	proc->environmentLength = 0;
	proc->rootPathLength = 0;
	proc->cwdPathLength = 0;
	proc->execPathLength = 0;
	proc->command = proc->commandBuffer;
	proc->environment = emptyString;
	proc->rootPath = emptyString;
	proc->cwdPath = emptyString;
	proc->execPath = emptyString;
	proc->stdioPaths[0] = emptyString;
	proc->stdioPaths[1] = emptyString;
	proc->stdioPaths[2] = emptyString;
	proc->program[0] = '\0';
	proc->waitChanSymbol[0] = '\0';

	/*
	 * Initialize all the cpu samples to zero.
	 */
	for (index = 0; index < PERCENT_SAMPLES; index++)
	{
		proc->cpuTable[index] = 0;
	}

	/*
	 * Add it to the process list.
	 */
	proc->next = processList;
	processList = proc;

	/*
	 * If this is a thread process then link it into the thread
	 * list of its main process.
	 */
	if (tid != NO_THREAD_ID)
	{
		PROC * mainProc = FindProcess(pid, NO_THREAD_ID);

		proc->owner = mainProc;
		proc->nextThread = mainProc->nextThread;
		mainProc->nextThread = proc;
	}

	return proc;
}


/*
 * Remove all dead processes from the process list that are older than
 * the preserve death time.  Such a delay gives the user a chance to
 * see transient processes which only appear for one process scan.
 * This is done with two passes through the process list.
 * First threads are removed, and then main processes are removed.
 */
void
RemoveDeadProcesses(void)
{
	ScanDeadProcesses(TRUE);
	ScanDeadProcesses(FALSE);
}


/*
 * Scan the process list for either main processes or thread processes
 * and check them for being dead.  If they are dead long enough then
 * they are removed.
 */
static void
ScanDeadProcesses(BOOL isThread)
{
	PROC *	proc;
	PROC *	prevProc;
	PROC *	nextProc;

	prevProc = NULL;

	for (proc = processList; proc; proc = nextProc)
	{
		/*
		 * Save the next process locally since if this process is
		 * freed its next pointer is modified.
		 */
		nextProc = proc->next;

		/*
		 * If this process is not the specified type then ignore it.
		 */
		if (proc->isThread != isThread)
		{
			prevProc = proc;

			continue;
		}

		/*
		 * Check to see if the process is dead and removable.
		 * If not then leave it in the process list.
		 */
		if (!IsProcessRemovable(proc))
		{
			prevProc = proc;

			continue;
		}

		/*
		 * The process is completely finished.
		 * Remove it from the process list by linking the previous
		 * process to the next one and leaving prevproc alone.
		 */
		if (prevProc)
			prevProc->next = nextProc;
		else
			processList = nextProc;

		/*
		 * If this is a thread process then remove it from its
		 * owner's thread list.
		 */
		RemoveOwnerThread(proc);

		/*
		 * Clear out the process structure and move it to the
		 * free list for reuse.
		 */
		FreeProcess(proc);
	}
}


/*
 * Check whether the process can be removed.
 * This is true if it is known to be dead, and if it has been
 * dead long enough.
 */
static BOOL
IsProcessRemovable(PROC * proc)
{
	/*
	 * If the process is still alive then it isn't removable.
	 */
	if (proc->liveCounter == liveCounter)
		return FALSE;

	/*
	 * The process is dead.
	 * Clear some of its state to look good in status displays.
	 * It's unclear how much of this should be cleaned out...
	 */
	proc->state = ' ';
	proc->oldState = ' ';
	proc->states[0] = '\0';
	proc->oldUserRunTime = proc->userRunTime;
	proc->oldSystemRunTime = proc->systemRunTime;
	proc->percentCpu = 0;
	proc->percentMemory = 0;
	proc->pagesSwapped = 0;
	proc->virtualSize = 0;
	proc->rss = 0;

	/*
	 * Update the cpu percentage used for the process since it
	 * is still useful to see that change for a dead process.
	 */
	CalculateCpuPercentage(proc);

	/*
	 * If the process has just been noticed as being dead
	 * then remember that time, and also the fact that it was
	 * last active then (since it had to run to disappear).
	 */
	if (proc->deathTime == 0)
	{
		proc->deathTime = currentTime;
		proc->lastActiveTime = currentTime;
		proc->runOrder = liveCounter;
	}

	/*
	 * If the process is dead recently enough, then leave it
	 * alone for a while so that the user can still see it.
	 */
	if ((deathTime > 0) &&
		(currentTime <= proc->deathTime + deathTime))
	{
		return FALSE;
	}

	/*
	 * If this is a main process which still has some thread
	 * processes then leave it (but this should not happen!).
	 */
	if (!proc->isThread && (proc->nextThread != 0))
		return FALSE;

	/*
	 * The process can be removed.
	 */
	return TRUE;
}


/*
 * Remove the specified thread (if applicable) from its owner thread list.
 */
static void
RemoveOwnerThread(PROC * proc)
{
	PROC *	testProc;

	/*
	 * If this isn't a thread then do nothing.
	 */
	if (!proc->isThread || (proc->owner == 0))
		return;

	/*
	 * Remove this process from the owner's thread list.
	 */
	testProc = proc->owner->nextThread;

	if (testProc == proc)
	{
		proc->owner->nextThread = proc->nextThread;

		return;
	}

	while (testProc)
	{
		if (testProc->nextThread == proc)
		{
			testProc->nextThread = proc->nextThread;

			return;
		}

		testProc = testProc->nextThread;
	}
}


/*
 * Free any storage associated with a process structure and move it
 * onto the process free list.  Any unlinking of the structure from
 * other processes or the process list has to be done first.
 */
static void
FreeProcess(PROC * proc)
{
	/*
	 * Clear a bit of state just in case.
	 */
	proc->next = 0;
	proc->owner = 0;
	proc->nextThread = 0;
	proc->isValid = FALSE;
	proc->pid = 0;
	proc->tid = 0;
	proc->state = ' ';
	proc->states[0] = '\0';

	/*
	 * Free any dynamic storage used by the process.
	 */
	if (proc->command != proc->commandBuffer)
	{
		free(proc->command);
		proc->command = proc->commandBuffer;
	}

	proc->commandLength = 0;

	FreeSharedString(proc->environment);
	proc->environment = emptyString;
	proc->environmentLength = 0;

	FreeSharedString(proc->cwdPath);
	proc->cwdPath = emptyString;
	proc->cwdPathLength = 0;

	FreeSharedString(proc->rootPath);
	proc->rootPath = emptyString;
	proc->rootPathLength = 0;

	FreeSharedString(proc->execPath);
	proc->execPath = emptyString;
	proc->execPathLength = 0;

	FreeSharedString(proc->stdioPaths[0]);
	FreeSharedString(proc->stdioPaths[1]);
	FreeSharedString(proc->stdioPaths[2]);

	proc->stdioPaths[0] = emptyString;
	proc->stdioPaths[1] = emptyString;
	proc->stdioPaths[2] = emptyString;

	/*
	 * Add the process structure to the free process list.
	 */
	proc->next = freeProcessList;
	freeProcessList = proc;
}


/*
 * Calculate how long it has been since the process has been active.
 * Part of this calculation compares previously gathered state with
 * the newest state.  In this way, even transiently running processes
 * can still be detected as not being idle.
 */
void
CheckActiveProcess(PROC * proc)
{
	BOOL	isActive;

	/*
	 * The process is definitely active if it is currently runnable
	 * or is in a short term wait like disk I/O.
	 */
	isActive = ((proc->state == 'R') || (proc->state == 'D'));

	if (isActive)
	{
		proc->lastActiveTime = currentTime;
		proc->runOrder = liveCounter;
	}

	/*
	 * Recalculate the CPU and memory percentages.
	 */
	CalculateCpuPercentage(proc);

	if (totalMemoryClicks > 0)
	{
		proc->percentMemory =
			(proc->rss * MEMORY_SCALE) / totalMemoryClicks;
	}

	proc->isChanged = FALSE;

	/*
	 * If some of its state has changed since the last check,
	 * then it is also considered active.  Save the new values
	 * of that state for future checks.
	 */
	if (proc->isNew ||
		(proc->userRunTime != proc->oldUserRunTime) ||
		(proc->systemRunTime != proc->oldSystemRunTime) ||
		(proc->state != proc->oldState) ||
		(proc->flags != proc->oldFlags) ||
		(proc->minorFaults != proc->oldMinorFaults) ||
		(proc->majorFaults != proc->oldMajorFaults) ||
		(proc->startTimeTicks != proc->oldStartTimeTicks) ||
		(proc->endCode != proc->oldEndCode) ||
		(proc->esp != proc->oldEsp) ||
		(proc->eip != proc->oldEip) ||
		(proc->waitChan != proc->oldWaitChan))
	{
		proc->isChanged = TRUE;
		proc->isNew = FALSE;
		proc->isAncient = ancientFlag;
		proc->lastActiveTime = currentTime;
		proc->lastSavedTime = currentTime;
		proc->runOrder = liveCounter;
		proc->oldState = proc->state;
		proc->oldFlags = proc->flags;
		proc->oldMinorFaults = proc->minorFaults;
		proc->oldMajorFaults = proc->majorFaults;
		proc->oldUserRunTime = proc->userRunTime;
		proc->oldSystemRunTime = proc->systemRunTime;
		proc->oldStartTimeTicks = proc->startTimeTicks;
		proc->oldEndCode = proc->endCode;
		proc->oldEsp = proc->esp;
		proc->oldEip = proc->eip;
		proc->oldWaitChan = proc->waitChan;
	}

	/*
	 * If the state changed recently, then the process is still active.
	 * Don't do this check for ancient processes that were there
	 * before we knew about their idleness.
	 */
	if ((!proc->isAncient) &&
		(currentTime <= (proc->lastSavedTime + activeTime)))
	{
		isActive = TRUE;
	}

 	if (isActive)
		proc->isAncient = FALSE;

	proc->isActive = isActive;
}


/*
 * Calculate the CPU percentage of a process.  This uses the sample times
 * table and the cpu runtime sample table of the process.  This should be
 * called soon after UpdateTimes has been called and soon after the new
 * runtime of the process has been found.
 */
void
CalculateCpuPercentage(PROC * proc)
{
	long	oldCpuTime;
	long	ticksUsed;
	double	percentage;

	/*
	 * Update the cpu time of the process for the current sample.
	 */
	proc->cpuTable[newCpuIndex] = proc->userRunTime + proc->systemRunTime;

	/*
	 * If we don't have any samples yet then the percentage is zero.
	 */
	proc->percentCpu = 0;

	if (elapsedMilliseconds <= 0)
		return;

	/*
	 * Calculate the ticks used by the process between the old and the
	 * new samples.  If we don't have the runtime for the beginning
	 * sample time then use the process's first seen runtime.
	 */
	oldCpuTime = proc->cpuTable[oldCpuIndex];

	if (oldCpuTime < proc->firstCpuTime)
		oldCpuTime = proc->firstCpuTime;

	ticksUsed = proc->cpuTable[newCpuIndex] - oldCpuTime;

	if (ticksUsed <= 0)
		return;

	/*
	 * Calculate the percentange of the CPU used from the ticks used and
	 * and the elapsed time of the sample.  Do this using floating point
	 * since otherwise it can overflow.
	 */
	percentage = ticksUsed;
	percentage *= 1000;
	percentage *= CPU_SCALE;
	percentage /= ticksPerSecond;
	percentage /= elapsedMilliseconds;

	proc->percentCpu = (int) percentage;
}


/*
 * Update the current time and determine whether or not it is time for a
 * new CPU sample to be taken for the processes.  This provides a rolling
 * CPU percentage feature based on the last few seconds which is much
 * better than an "instantaneous" CPU percentage.
 */
void
UpdateTimes(void)
{
	/*
	 * Increment the live counter and get the current time.
	 */
	liveCounter++;

	GetTimeOfDay(&currentTimeval);

	currentTime = currentTimeval.tv_sec;

	/*
	 * If this is the first call then initialize the times.
	 */
	if (baseTimeval.tv_sec == 0)
	{
		baseTimeval = currentTimeval;
		cpuSampleTimes[0] = currentTimeval;
		cpuSampleTimes[1] = currentTimeval;
		oldCpuIndex = 0;
		newCpuIndex = 1;
	}

	/*
	 * See how much time has elapsed since the start of the current
	 * CPU sample interval.
	 */
	elapsedMilliseconds = ElapsedMilliSeconds(&baseTimeval, &currentTimeval);

	/*
	 * If enough time has gone by then start a new sample.
	 * (But don't do this if we would catch up to the old index.)
	 */
	if (elapsedMilliseconds * PERCENT_RESOLUTION >= 1000)
	{
		int tempIndex = (newCpuIndex + 1) % PERCENT_SAMPLES;

		if (tempIndex != oldCpuIndex)
		{
			newCpuIndex = tempIndex;

			/*
			 * Store the beginning time of the sample.
			 */
			baseTimeval = currentTimeval;
		}
	}

	/*
	 * Update the time of the current sample.
	 */
	cpuSampleTimes[newCpuIndex] = currentTimeval;

	/*
	 * Advance the old CPU index value if we can so that the
	 * elapsed interval is only as large as desired.
	 */
	while (oldCpuIndex != newCpuIndex)
	{
		int tempIndex = (oldCpuIndex + 1) % PERCENT_SAMPLES;

		if (tempIndex == newCpuIndex)
			break;

		elapsedMilliseconds = ElapsedMilliSeconds(
			&cpuSampleTimes[tempIndex], &currentTimeval);

		if (elapsedMilliseconds < percentSeconds * 1000)
			break;

		oldCpuIndex = tempIndex;
	}

	/*
	 * Save the elapsed milliseconds between the old sample time
	 * and the current time.  This is the interval used for the
	 * CPU percentage calculations.
	 */
	elapsedMilliseconds = ElapsedMilliSeconds(
		&cpuSampleTimes[oldCpuIndex], &currentTimeval);
}


/*
 * Update the total number of processes and threads which are being shown.
 * This should only be called after the status of the processes has been
 * completed for a cycle, so that dead processes are removed and the isShown
 * flags for the processes are accurate.
 * Note: The total number of processes and threads is NOT updated here
 * since we don't necessarily scan all of the processes and so the list
 * of PROC structures doesn't have all of the needed information.
 */
void
UpdateProcessCounts(void)
{
	const PROC *	proc;

	procShowCount = 0;
	threadShowCount = 0;

	/*
	 * Loop over all of the processes (and maybe the threads).
	 */
	for (proc = processList; proc; proc = proc->next)
	{
		/*
		 * If this is a thread then increment the thread count.
		 */
		if (proc->isThread)
		{
			if (proc->isShown)
				threadShowCount++;

			continue;
		}

		/*
		 * This is a main process structure.
		 * Increment the process count.
		 */
		if (proc->isShown)
			procShowCount++;

		/*
		 * If the thread information is not being collected
		 * or if there is only one thread for the process
		 * then we must increment the thread count specially.
		 * (Only the main thread of the process is seen.)
		 */ 
		if ((proc->threadCount <= 1) ||
			(!useThreads && !showThreads))
		{
			if (proc->isShown)
				threadShowCount++;
		}
	}
}


/*
 * Store a command line string into the process structure.
 * The len value is the length of the string without the
 * terminating null character.
 */
void
SetCommandLine(PROC * proc, const char * str, int len)
{
	/*
	 * See if the old and new commands are empty.
	 */
	if ((len == 0) && (proc->commandLength == 0))
		return;

	/*
	 * See if the command line is the same as last time.
	 * If so, then we are done.
	 */
	if ((len == proc->commandLength) &&
		(str[0] == proc->command[0]) &&
		(str[len - 1] == proc->command[len - 1]) &&
		(memcmp(str, proc->command, len) == 0))
	{
		return;
	}

	/*
	 * Free any old command line buffer that was there, and point
	 * back to the space already allocated in the proc structure.
	 */
	if (proc->command != proc->commandBuffer)
		free(proc->command);

	proc->command = proc->commandBuffer;

	/*
	 * If the command line is too large for the proc buffer,
	 * then allocate a new one.  (The buffer has a couple of
	 * extra bytes for the terminating null.)
	 */
	if (len > BUF_COMMAND_LEN)
		proc->command = AllocMemory(len + 1);

	/*
	 * Copy the command line into the command buffer and set its length.
	 */
	memcpy(proc->command, str, len);
	proc->command[len] = '\0';
	proc->commandLength = len;
}


/*
 * Set a shared string value with its length into the specified locations 
 * (generally for one of the values in a PROC structure).  The new string is
 * compared with the existing one, and if it differs, the previous string
 * value is freed and the new one is stored.  Returns TRUE on success or
 * FALSE on an error with an empty string stored.
 */
BOOL
SetSharedString(char ** saveStr, int * saveLen, const char * str, int len)
{
	/*
	 * Get convenient references to the old strings.
	 */
	char * oldStr = *saveStr;
	int oldLen = *saveLen;

	/*
	 * If the old and new strings are both empty then there is
	 * nothing to do.
	 */
	if ((oldLen == 0) && (len == 0))
		return TRUE;

	/*
	 * Compare the new string against what is already there.
	 * If they are the same, then we don't need to do anything.
	 */
	if ((oldLen == len) &&
		(oldStr[0] == str[0]) &&
		(oldStr[len - 1] == str[len - 1]) &&
		(memcmp(oldStr, str, len) == 0))
	{
		return TRUE;
	}

	/*
	 * The value is being changed.
	 * Free the old one and try to allocate the new one.
	 */
	FreeSharedString(oldStr);

	*saveStr = AllocateSharedString(str, len);
	*saveLen = len;

	if (*saveStr != NULL)
		return TRUE;

	/*
	 * The allocation failed.
	 * Set an empty value and return failure.
	 */
	*saveStr = emptyString;
	*saveLen = 0;

	return FALSE;
}


/*
 * Get the run order of the process, taking into account its threads.
 */
ULONG
GetRunOrder(const PROC * proc)
{
	ULONG	runOrder = proc->runOrder;

	if (!useThreads && !showThreads)
		return runOrder;

	if (proc->isThread || (proc->threadCount == 1))
		return runOrder;

	for (proc = proc->nextThread; proc; proc = proc->nextThread)
	{
		if (runOrder < proc->runOrder)
			runOrder = proc->runOrder;
	}

	return runOrder;
}


/*
 * Get the last active time of the process, taking into account its threads.
 */
time_t
GetLastActiveTime(const PROC * proc)
{
	time_t	lastActiveTime = proc->lastActiveTime;

	if (!useThreads && !showThreads)
		return lastActiveTime;

	if (proc->isThread || (proc->threadCount == 1))
		return lastActiveTime;

	for (proc = proc->nextThread; proc; proc = proc->nextThread)
	{
		if (lastActiveTime < proc->lastActiveTime)
			lastActiveTime = proc->lastActiveTime;
	}

	return lastActiveTime;
}


/*
 * Get whether the process is active, taking into account its threads.
 */
BOOL
GetIsActive(const PROC * proc)
{
	if (proc->isActive)
		return TRUE;

	if (!useThreads && !showThreads)
		return FALSE;

	if (proc->isThread || (proc->threadCount == 1))
		return FALSE;

	for (proc = proc->nextThread; proc; proc = proc->nextThread)
	{
		if (proc->isActive)
			return TRUE;
	}

	return FALSE;
}


/*
 * Get the state character of the process, taking into account its threads.
 * We have a hierarchy of states.
 */
int
GetState(const PROC * proc)
{
	int	state = proc->state;

	if (!useThreads && !showThreads)
		return state;

	if (proc->isThread || (proc->threadCount == 1))
		return state;

	for (proc = proc->nextThread; proc; proc = proc->nextThread)
	{
		state = PickBestState(state, proc->state);
	}

	return state;
}


/*
 * Build and save a string describing the list of thread states for the process.
 * The single character states can be followed by a numeric count as in "R4DS3".
 */
void
BuildStates(PROC * proc)
{
	int	state;
	PROC *	threadProc;
	short	countTable[128];

	/*
	 * Get the single process state and use that if there are no
	 * threads or we are a thread.
	 */
	proc->states[0] = proc->state;
	proc->states[1] = '\0';

	if (!useThreads && !showThreads)
		return;

	if (proc->isThread || (proc->threadCount == 1))
		return;

	/*
	 * Calculate a table of counts for the threads indexed by each of
	 * their state letters.
	 */
	memset(countTable, 0, 128 * sizeof(short));

	for (threadProc = proc->nextThread; threadProc;
		threadProc = threadProc->nextThread)
	{
		countTable[threadProc->state & 0x7f]++;
	}

	/*
	 * Format and append the state count values in a nice order.
	 */
	proc->states[0] = '\0';

	AppendState(proc, countTable, 'R');
	AppendState(proc, countTable, 'D');
	AppendState(proc, countTable, 'S');
	AppendState(proc, countTable, 'T');

	/*
	 * Append any other leftover states.
	 */
	for (state = ' '; state < 128; state++)
		AppendState(proc, countTable, state);
}


/*
 * Format and append a state with its count value and clear the value.
 * Returns TRUE if the state could be stored.
 */
static BOOL
AppendState(PROC * proc, short * countTable, int state)
{
	int	count;
	int	len;
	char	buf[20];

	count = countTable[state];

	if (count == 0)
		return TRUE;

	countTable[state] = 0;

	len = strlen(proc->states);

	if (count == 1)
	{
		if (len >= MAX_STATES_LEN)
			return FALSE;

		proc->states[len++] = state;
		proc->states[len] = '\0';

		return TRUE;
	}

	sprintf(buf, "%c%d", state, count);

	if (len + strlen(buf) >= MAX_STATES_LEN)
		return FALSE;

	strcpy(&proc->states[len], buf);

	return TRUE;
}

/* END CODE */
