<commit_before>/*This code is part of the Epoch Init System.
* The Epoch Init System is maintained by Subsentient.
* This software is public domain.
* Please read the file UNLICENSE.TXT for more information.*/

/**This file handles the parsing of epoch.conf, our configuration file.
 * It adds everything into the object table.**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <signal.h>
#include <ctype.h>
#include "epoch.h"

/*We want the only interface for this to be LookupObjectInTable().*/
ObjTable *ObjectTable = NULL;

/*Used to allow for things like 'ObjectStartPriority Services', where Services == 3, for example.*/
static struct _PriorityAliasTree
{ /*Start/Stop priority alias support for grouping.*/
	char Alias[MAX_DESCRIPT_SIZE];
	unsigned long Target;
	
	struct _PriorityAliasTree *Next;
	struct _PriorityAliasTree *Prev;
} *PriorityAliasTree = NULL;

/*Used to allow runlevels to be inherited by other runlevels.*/
static struct _RunlevelInheritance
{ /*I __REVILE__ this solution.
Epoch is just a linked list of linked lists anymore.*/
	char Inheriter[MAX_DESCRIPT_SIZE];
	char Inherited[MAX_DESCRIPT_SIZE];
	
	struct _RunlevelInheritance *Next;
	struct _RunlevelInheritance *Prev;
} *RunlevelInheritance = NULL;

/*Holds the system hostname.*/
char Hostname[MAX_LINE_SIZE] = { '\0' };

/*Function forward declarations for all the statics.*/
static ObjTable *AddObjectToTable(const char *ObjectID);
static char *NextLine(const char *InStream);
static rStatus GetLineDelim(const char *InStream, char *OutStream);
static rStatus ScanConfigIntegrity(void);
static void ConfigProblem(short Type, const char *Attribute, const char *AttribVal, unsigned long LineNum);
static unsigned long PriorityAlias_Lookup(const char *Alias);
static void PriorityAlias_Add(const char *Alias, unsigned long Target);
static void PriorityAlias_Shutdown(void);
static void RLInheritance_Add(const char *Inheriter, const char *Inherited);
static Bool RLInheritance_Check(const char *Inheriter, const char *Inherited);
static void RLInheritance_Shutdown(void);

/*Used for error handling in InitConfig() by ConfigProblem().*/
enum { CONFIG_EMISSINGVAL = 1, CONFIG_EBADVAL, CONFIG_ETRUNCATED, CONFIG_EAFTER, CONFIG_EBEFORE, CONFIG_ELARGENUM };

/*Actual functions.*/
static char *NextLine(const char *InStream)
{
	if (!(InStream = strstr(InStream, "\n")))
	{
		return NULL;
	}

	if (*(InStream + 1) == '\0')
	{ /*End of file.*/
		return NULL;
	}

	++InStream; /*Plus one for the newline. We want to skip past it.*/

	return (char*)InStream;
}

/*This function was so useful I gave it external linkage.*/
char *WhitespaceArg(const char *InStream)
{  /*This is used for parsing lines that need to be divided by spaces.*/
	while (*InStream != ' ' && *InStream != '\t' &&
			*InStream != '\n' && *InStream != '\0') ++InStream;
	
	if (*InStream == '\n' || *InStream == '\0')
	{
		return NULL;
	}
	
	while (*InStream == ' ' || *InStream == '\t') ++InStream;
	
	if (*InStream == '\0' || *InStream == '\n')
	{
		return NULL;
	}
	
	return (char*)InStream;
}


static void ConfigProblem(short Type, const char *Attribute, const char *AttribVal, unsigned long LineNum)
{ /*Special little error handler used by InitConfig() to prevent repetitive duplicate errors.*/
	char TmpBuf[1024];
	char LogBuffer[MAX_LINE_SIZE];

	switch (Type)
	{
		case CONFIG_EMISSINGVAL:
			snprintf(TmpBuf, 1024, "Missing or bad value for attribute %s in epoch.conf line %lu.\nIgnoring.",
					Attribute, LineNum);
			break;
		case CONFIG_EBADVAL:
			snprintf(TmpBuf, 1024, "Bad value %s for attribute %s in epoch.conf line %lu.", AttribVal, Attribute, LineNum);
			break;
		case CONFIG_ETRUNCATED:
			snprintf(TmpBuf, 1024, "Attribute %s in epoch.conf line %lu has\n"
					"abnormally long value and may have been truncated.", Attribute, LineNum);
			break;
		case CONFIG_EAFTER:
			snprintf(TmpBuf, 1024, "Attribute %s cannot be set after an ObjectID attribute; "
					"epoch.conf line %lu. Ignoring.", Attribute, LineNum);
			break;
		case CONFIG_EBEFORE:
			snprintf(TmpBuf, 1024, "Attribute %s comes before any ObjectID attribute.\n"
					"epoch.conf line %lu. Ignoring.", Attribute, LineNum);
			break;
		case CONFIG_ELARGENUM:
			snprintf(TmpBuf, 1024, "Attribute %s in epoch.conf line %lu has\n"
					"abnormally high numeric value and may cause malfunctions.", Attribute, LineNum);
			break;
		default:
			return;
	}
	
	snprintf(LogBuffer, MAX_LINE_SIZE, "CONFIG: " CONSOLE_COLOR_YELLOW "WARNING: " CONSOLE_ENDCOLOR "%s\n", TmpBuf);
	
	SpitWarning(TmpBuf);
	WriteLogLine(LogBuffer, true);
}

rStatus InitConfig(void)
{ /*Set aside storage for the table.*/
	FILE *Descriptor = NULL;
	struct stat FileStat;
	char *ConfigStream = NULL, *Worker = NULL;
	ObjTable *CurObj = NULL, *ObjWorker = NULL;
	char DelimCurr[MAX_LINE_SIZE] = { '\0' };
	unsigned long LineNum = 1;
	const char *CurrentAttribute = NULL;
	Bool LongComment = false;
	char ErrBuf[MAX_LINE_SIZE];
	
	/*Get the file size of the config file.*/
	if (stat(CONFIGDIR CONF_NAME, &FileStat) != 0)
	{ /*Failure?*/
		SpitError("Failed to obtain information about configuration file epoch.conf.\nDoes it exist?");
		return FAILURE;
	}
	else
	{ /*No? Use the file size to allocate space in memory, since a char is a byte big.
	* If it's not a byte on your platform, your OS is not UNIX, and Epoch was not designed for you.*/
		ConfigStream = malloc(FileStat.st_size + 1);
	}

	Descriptor = fopen(CONFIGDIR CONF_NAME, "r"); /*Open the configuration file.*/

	/*Read the file into memory. I don't really trust fread(), but oh well.
	 * People will whine if I use a loop instead.*/
	fread(ConfigStream, 1, FileStat.st_size, Descriptor);
	
	ConfigStream[FileStat.st_size] = '\0'; /*Null terminate.*/
	
	fclose(Descriptor); /*Close the file.*/

	Worker = ConfigStream;

	/*Empty file?*/
	if ((*Worker == '\n' && *(Worker + 1) == '\0') || *Worker == '\0')
	{
		SpitError("Seems that epoch.conf is empty or corrupted.");
		free(ConfigStream);
		return FAILURE;
	}

	do /*This loop does most of the parsing.*/
	{
		
		/*Allow whitespace to precede a line in case people want to create a block-styled appearance.*/
		while (*Worker == ' ' || *Worker == '\t') ++Worker;
		
		/**Multi-line comment support: Multi-line comments are created in the following way:
		 * >!> stuff
		 * stuff
		 * stuff stuff
		 * stuffy stuff
		 * <!< stuff
		 * stuff
		 * 
		 * It is not recognized to place a multi-line comment beginner or terminator anywhere but the beginning
		 * of the line. As such, one may place do things like "ObjectID >!>" to create an object with ID ">!>". **/

		if (!strncmp(Worker, "<!<", strlen("<!<")))
		{ /*It's probably not good to have stray multi-line comment terminators around.*/
			if (!LongComment)
			{
				snprintf(ErrBuf, MAX_LINE_SIZE, "Stray multi-line comment terminator on line %lu\n", LineNum);
				SpitWarning(ErrBuf);
				continue;
			}
			LongComment = false;
			
			/*Allow next line to begin right ater the terminator on the same line.*/
			Worker += strlen("<!<");
			while (*Worker == ' ' || *Worker == '\t') ++Worker;
		}
		else if (LongComment)
		{
			continue;
		}
		else if (!strncmp(Worker, ">!>", strlen(">!>")))
		{
			LongComment = true;
			continue;
		}
		
		/**Single-line comments are created by placing "#" at the beginning of the line. Placing them
		 * anywhere else has no effect and as such '#' may be used in commands and object IDs and descriptions.**/
		if (*Worker == '\n')
		{ /*Empty line.*/
			continue;
		}
		else if (*Worker == '#')
		{ /*Line is just a comment.*/
			continue;
		}
		
		/**Global configuration begins here.**/
		if (!strncmp(Worker, (CurrentAttribute = "DisableCAD"), strlen("DisableCAD")))
		{ /*Should we disable instant reboots on CTRL-ALT-DEL?*/
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}

			if (!strcmp(DelimCurr, "true"))
			{
				DisableCAD = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				DisableCAD = false;
			}
			else
			{				
				DisableCAD = true;
				
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}

			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "BlankLogOnBoot"), strlen("BlankLogOnBoot")))
		{ /*Should the log only hold the current boot cycle's logs?*/

			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}

			if (!strcmp(DelimCurr, "true"))
			{
				BlankLogOnBoot = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				BlankLogOnBoot = false;
			}
			else
			{				
				BlankLogOnBoot = false;
				
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}

			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ShellEnabled"), strlen("ShellEnabled")))
		{
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);

				continue;
			}
			
			if (!strcmp(DelimCurr, "true"))
			{
				ShellEnabled = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				ShellEnabled = false;
			}
			else
			{
				ShellEnabled = USE_SHELL_BY_DEFAULT;
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}
		}
		else if (!strncmp(Worker, (CurrentAttribute = "EnableLogging"), strlen("EnableLogging")))
		{
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);

				continue;
			}
			
			if (!strcmp(DelimCurr, "true"))
			{
				EnableLogging = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				EnableLogging = false;
			}
			else
			{
				
				EnableLogging = false;
				
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "RunlevelInherits"), strlen("RunlevelInherits")))
		{
			char Inheriter[MAX_DESCRIPT_SIZE], Inherited[MAX_DESCRIPT_SIZE];
			const char *TWorker = DelimCurr;
			unsigned long TInc = 0;
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EAFTER, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			for (; *TWorker != ' ' && *TWorker != '\t' && *TWorker != '\0' && TInc < MAX_DESCRIPT_SIZE - 1; ++TInc, ++TWorker)
			{
				Inheriter[TInc] = *TWorker;
			}
			Inheriter[TInc] = '\0';
			
			if (*TWorker == '\0')
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
				continue;
			}
			
			TWorker = WhitespaceArg(TWorker);
			
			if (strstr(TWorker, " ") || strstr(TWorker, "\t"))
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
				continue;
			}
			
			snprintf(Inherited, MAX_DESCRIPT_SIZE, "%s", TWorker);
			
			RLInheritance_Add(Inheriter, Inherited);
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "DefinePriority"), strlen("DefinePriority")))
		{
			char Alias[MAX_DESCRIPT_SIZE] = { '\0' };
			unsigned long Target = 0, TInc = 0;
			const char *TWorker = DelimCurr;
			
			if (CurObj != NULL)
			{ /*We can't allow this in object-local options, because then this may not be properly defined.*/
				ConfigProblem(CONFIG_EAFTER, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				
				continue;
			}
			
			for (; *TWorker != ' ' && *TWorker != '\t' &&
				*TWorker != '\0' && TInc < MAX_DESCRIPT_SIZE -1; ++TInc, ++TWorker)
			{ /*Copy in the identifier.*/
				Alias[TInc] = *TWorker;
			}
			Alias[TInc] = '\0';
			
			if (*TWorker == '\0')
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
				continue;
			}
			
			TWorker = WhitespaceArg(TWorker); /*I abuse this delightful little function. It was meant for do-while loops.*/
			
			if (!AllNumeric(TWorker))
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
				continue;
			}
			
			Target = atol(TWorker);
			
			PriorityAlias_Add(Alias, Target); /*Now add it to the linked list.*/
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "AlignStatusReports"), strlen("AlignStatusReports")))
		{
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);

				continue;
			}
			
			if (!strcmp(DelimCurr, "true"))
			{
				AlignStatusReports = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				AlignStatusReports = false;
			}
			else
			{
				
				AlignStatusReports = false;
				
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		/*This will mount /dev, /proc, /sys, /dev/pts, and /dev/shm on boot time, upon request.*/
		else if (!strncmp(Worker, (CurrentAttribute = "MountVirtual"), strlen("MountVirtual")))
		{
			const char *TWorker = DelimCurr;
			unsigned long Inc = 0;
			char CurArg[MAX_DESCRIPT_SIZE];
			const char *VirtualID[2][5] = { { "procfs", "sysfs", "devfs", "devpts", "devshm" },
											{ "procfs+", "sysfs+", "devfs+", "devpts+", "devshm+" } };
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);

				continue;
			}
			
			do
			{ /*Load in all the arguments in the line.*/
				Bool FoundSomething = false;
				
				for (Inc = 0; TWorker[Inc] != ' ' && TWorker[Inc] != '\t' && TWorker[Inc] != '\n' &&
					TWorker[Inc] != '\0' && Inc < (MAX_DESCRIPT_SIZE - 1); ++Inc)
				{ /*Copy in the argument for this line.*/
					CurArg[Inc] = TWorker[Inc];
				}
				CurArg[Inc] = '\0';
				
				
				for (Inc = 0; Inc < 5; ++Inc)
				{ /*Search through the argument to see what it matches.*/
					if (!strncmp(VirtualID[0][Inc], CurArg, strlen(VirtualID[0][Inc])))
					{
						AutoMountOpts[Inc] = (!strcmp(VirtualID[1][Inc], CurArg) ? 2 : true);
						FoundSomething = true;
						break;
					}
				}

				if (!FoundSomething)
				{ /*If it doesn't match anything, that's bad.*/
					ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
					
					continue;
				}
					
			} while ((TWorker = WhitespaceArg(TWorker)));
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		/*Now we get into the actual attribute tags.*/
		else if (!strncmp(Worker, (CurrentAttribute = "BootBannerText"), strlen("BootBannerText")))
		{ /*The text shown at boot up as a kind of greeter, before we start executing objects. Can be disabled, off by default.*/
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!strcmp(DelimCurr, "NONE")) /*So, they decided to explicitly opt out of banner display. Ok.*/
			{
				BootBanner.BannerText[0] = '\0';
				BootBanner.BannerColor[0] = '\0';
				BootBanner.ShowBanner = false; /*Should already be false, but to prevent possible bugs...*/
				continue;
			}
			snprintf(BootBanner.BannerText, MAX_LINE_SIZE, "%s", DelimCurr);
			
			BootBanner.ShowBanner = true;
			
			if ((strlen(DelimCurr) + 1) >= MAX_DESCRIPT_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "BootBannerColor"), strlen("BootBannerColor")))
		{ /*Color for boot banner.*/
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!strcmp(DelimCurr, "NONE")) /*They don't want a color.*/
			{
				BootBanner.BannerColor[0] = '\0';
				continue;
			}
			
			SetBannerColor(DelimCurr); /*Function to be found elsewhere will do this for us, otherwise this loop would be even bigger.*/
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "DefaultRunlevel"), strlen("DefaultRunlevel")))
		{
			if (CurRunlevel[0] != 0)
			{ /*If the runlevel has already been set, don't set it again.
				* This prevents a rather nasty bug.*/
				continue;
			}
			
			if (CurObj != NULL)
			{ /*What the warning says. It'd get all weird if we allowed that.*/
				ConfigProblem(CONFIG_EAFTER, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}	
			
			snprintf(CurRunlevel, MAX_DESCRIPT_SIZE, "%s", DelimCurr);
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "Hostname"), strlen("Hostname")))
		{
			if (CurObj != NULL)
			{ /*What the warning says. It'd get all weird if we allowed that.*/
				ConfigProblem(CONFIG_EAFTER, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;

			}
			
			if (!strncmp(DelimCurr, "FILE", strlen("FILE")))
			{
				FILE *TDesc;
				unsigned long Inc = 0;
				short TChar;
				const char *TW = DelimCurr;
				char THostname[MAX_LINE_SIZE];
				
				TW += strlen("FILE");
				
				for (; *TW == ' ' || *TW == '\t'; ++TW);
				
				if (!(TDesc = fopen(TW, "r")))
				{
										snprintf(ErrBuf, sizeof ErrBuf, "Failed to set hostname from file \"%s\".\n", TW);
					SpitWarning(ErrBuf);
					continue;
				}
				
				for (Inc = 0; (TChar = getc(TDesc)) != EOF && Inc < MAX_LINE_SIZE - 1; ++Inc)
				{
					THostname[Inc] = (char)TChar;
				}
				THostname[Inc] = '\0';
				
				/*Skip past spaces, tabs, and newlines.*/
				for (TW = THostname; *TW == '\n' ||
					*TW == ' ' || *TW == '\t'; ++TW);
				
				/*Copy into the real hostname from our new offset.*/
				for (Inc = 0; TW[Inc] != '\0' && TW[Inc] != '\n'; ++Inc)
				{
					Hostname[Inc] = TW[Inc];
				}
				Hostname[Inc] = '\0';
				
				fclose(TDesc);
			}
			else
			{	
				snprintf(Hostname, MAX_LINE_SIZE, "%s", DelimCurr);
			}
			
								
			/*Check for spaces and tabs in the actual hostname.*/
			if (strstr(Hostname, " ") != NULL || strstr(Hostname, "\t") != NULL)
			{
				SpitWarning("Tabs and/or spaces in hostname file. Cannot set hostname.");
				*Hostname = '\0'; /*Set the hostname back to nothing.*/
				continue;
			}
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}		
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectID"), strlen("ObjectID")))
		{ /*ASCII value used to identify this object internally, and also a kind of short name for it.*/
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}

			CurObj = AddObjectToTable(DelimCurr); /*Sets this as our current object.*/

			if ((strlen(DelimCurr) + 1) >= MAX_DESCRIPT_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}

			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectEnabled"), strlen("ObjectEnabled")))
		{
			if (!CurObj)
			{

				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!strcmp(DelimCurr, "true"))
			{
				CurObj->Enabled = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				CurObj->Enabled = false;
			}
			else
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectOptions"), strlen("ObjectOptions")))
		{
			const char *TWorker = DelimCurr;
			unsigned long Inc;
			char CurArg[MAX_DESCRIPT_SIZE];
			
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			do
			{
				
				for (Inc = 0; TWorker[Inc] != ' ' && TWorker[Inc] != '\t' && TWorker[Inc] != '\n'
					&& TWorker[Inc] != '\0' && Inc < (MAX_DESCRIPT_SIZE - 1); ++Inc)
				{
					CurArg[Inc] = TWorker[Inc];
				}
				CurArg[Inc] = '\0';
				
				if (!strcmp(CurArg, "NOWAIT"))
				{
					CurObj->Opts.EmulNoWait = true;
					snprintf(ErrBuf, sizeof ErrBuf, "Option NOWAIT is deprecated and has been partially removed.\n"
							"Emulating NOWAIT for object %s.\nLine %lu in epoch.conf", CurObj->ObjectID, LineNum);
					SpitWarning(ErrBuf);
				}
				else if (!strcmp(CurArg, "HALTONLY"))
				{ /*Allow entries that execute on shutdown only.*/
					CurObj->Started = true;
					CurObj->Opts.CanStop = false;
					CurObj->Opts.HaltCmdOnly = true;
				}
				else if (!strcmp(CurArg, "PERSISTENT"))
				{
					CurObj->Opts.CanStop = false;
				}
				else if (!strcmp(CurArg, "RAWDESCRIPTION"))
				{
					CurObj->Opts.RawDescription = true;
				}
				else if (!strcmp(CurArg, "SERVICE"))
				{
					CurObj->Opts.IsService = true;
				}
				else if (!strcmp(CurArg, "AUTORESTART"))
				{
					CurObj->Opts.AutoRestart = true;
				}
				else if (!strcmp(CurArg, "FORCESHELL"))
				{
					if (!ShellEnabled)
					{
						snprintf(ErrBuf, sizeof ErrBuf, "Object %s has FORCESHELL set, but ShellEnabled is false.\n"
														"Ignoring.\nepoch.conf line %lu", CurObj->ObjectID, LineNum);
						SpitWarning(ErrBuf);
					}
					else
					{
						CurObj->Opts.ForceShell = true;
					}
				}
				else if (!strncmp(CurArg, "TERMSIGNAL", strlen("TERMSIGNAL")))
				{
					const char *TWorker = CurArg + strlen("TERMSIGNAL");
					
					if (*TWorker != '=' || *(TWorker + 1) == '\0')
					{
						ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, CurArg, LineNum);
						continue;
					}
					
					++TWorker;
					
					if (AllNumeric(TWorker))
					{
						if (atoi(TWorker) > 255)
						{
							ConfigProblem(CONFIG_ELARGENUM, CurArg, NULL, LineNum);
						}

						CurObj->TermSignal = atoi(TWorker);
					}
					else if (!strcmp("SIGTERM", TWorker))
					{
						CurObj->TermSignal = SIGTERM;
					}
					else if (!strcmp("SIGKILL", TWorker))
					{
						CurObj->TermSignal = SIGKILL;
					}
					else if (!strcmp("SIGHUP", TWorker))
					{
						CurObj->TermSignal = SIGKILL;
					}
					else if (!strcmp("SIGINT", TWorker))
					{
						CurObj->TermSignal = SIGINT;
					}
					else if (!strcmp("SIGABRT", TWorker))
					{
						CurObj->TermSignal = SIGABRT;
					}
					else if (!strcmp("SIGQUIT", TWorker))
					{
						CurObj->TermSignal = SIGQUIT;
					}
					else if (!strcmp("SIGUSR1", TWorker))
					{
						CurObj->TermSignal = SIGUSR1;
					}
					else if (!strcmp("SIGUSR2", TWorker))
					{
						CurObj->TermSignal = SIGUSR2;
					}
					else
					{
						ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, TWorker, LineNum);
						continue;
					}
				}
				else
				{
					ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, CurArg, LineNum);
					break;
				}
			} while ((TWorker = WhitespaceArg(TWorker)));
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectDescription"), strlen("ObjectDescription")))
		{ /*It's description.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectDescription, MAX_DESCRIPT_SIZE, "%s", DelimCurr);
			
			if ((strlen(DelimCurr) + 1) >= MAX_DESCRIPT_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}

			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectStartCommand"), strlen("ObjectStartCommand")))
		{ /*What we execute to start it.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectStartCommand, MAX_LINE_SIZE, "%s", DelimCurr);

			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectPrestartCommand"), strlen("ObjectPrestartCommand")))
		{
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectPrestartCommand, MAX_LINE_SIZE, "%s", DelimCurr);
			
			if (strlen(DelimCurr) + 1 >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, NULL, LineNum);
				continue;
			}
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectReloadCommand"), strlen("ObjectReloadCommand")))
		{
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectReloadCommand, MAX_LINE_SIZE, "%s", DelimCurr);
			
			if (strlen(DelimCurr) + 1 >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectStopCommand"), strlen("ObjectStopCommand")))
		{ /*If it's "PID", then we know that we need to kill the process ID only. If it's "NONE", well, self explanitory.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}

			if (!strncmp(DelimCurr, "PIDFILE", strlen("PIDFILE")))
			{ /*They want us to kill a PID file on exit.*/
				const char *Worker = DelimCurr;
				
				CurObj->Opts.StopMode = STOP_PIDFILE;
				
				if (*(Worker += strlen("PIDFILE")) != '\0')
				{ /*It used to be that this was the only way to specify a PID file.*/
                                
					while (*Worker == ' ' || *Worker == '\t')
					{ /*Skip past all spaces and tabs.*/
						++Worker;
					}
					
					if (*Worker != '\0')
					{
						snprintf(CurObj->ObjectPIDFile, MAX_LINE_SIZE, "%s", Worker);
						CurObj->Opts.HasPIDFile = true;
					}
				}
			}
			else if (!strncmp(DelimCurr, "PID", strlen("PID")))
			{
				CurObj->Opts.StopMode = STOP_PID;
			}
			else if (!strncmp(DelimCurr, "NONE", strlen("NONE")))
			{
				CurObj->Opts.StopMode = STOP_NONE;
			}
			else
			{
				CurObj->Opts.StopMode = STOP_COMMAND;
				snprintf(CurObj->ObjectStopCommand, MAX_LINE_SIZE, "%s", DelimCurr);
			}
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectStartPriority"), strlen("ObjectStartPriority")))
		{
			/*The order in which this item is started. If it is disabled in this runlevel, the next object in line is executed, IF
			 * and only IF it is enabled. If not, the one after that and so on.*/
			
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!AllNumeric(DelimCurr)) /*Make sure we are getting a number, not Shakespeare.*/
			{ /*No number? We're probably looking at an alias.*/
				unsigned long TmpTarget = 0;
				
				if (!(TmpTarget = PriorityAlias_Lookup(DelimCurr)))
				{
					ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
					continue;
				}
				
				CurObj->ObjectStartPriority = TmpTarget;
				continue;
			}
			
			CurObj->ObjectStartPriority = atoi(DelimCurr);
			
			if (strlen(DelimCurr) >= 8)
			{ /*An eight digit number is too high.*/
				ConfigProblem(CONFIG_ELARGENUM, CurrentAttribute, NULL, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectStopPriority"), strlen("ObjectStopPriority")))
		{
			/*Same as above, but used for when the object is being shut down.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!AllNumeric(DelimCurr))
			{
				unsigned long TmpTarget = 0;
				
				if (!(TmpTarget = PriorityAlias_Lookup(DelimCurr)))
				{
					ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
					continue;
				}
				
				CurObj->ObjectStopPriority = TmpTarget;
				continue;
			}
			
			CurObj->ObjectStopPriority = atoi(DelimCurr);
			
			if (strlen(DelimCurr) >= 8)
			{ /*An eight digit number is too high.*/
				ConfigProblem(CONFIG_ELARGENUM, CurrentAttribute, NULL, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectPIDFile"), strlen("ObjectPIDFile")))
		{ /*This really needs to be specified if Opts.StopMode is STOP_PIDFILE, or we'll reset the object to STOP_PID.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectPIDFile, MAX_LINE_SIZE, "%s", DelimCurr);
			CurObj->Opts.HasPIDFile = true;
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectRunlevels"), strlen("ObjectRunlevels")))
		{ /*Runlevel.*/
			char *TWorker;
			char TRL[MAX_DESCRIPT_SIZE], *TRL2;
			static const ObjTable *LastObject = NULL;
			
			if (!CurObj)
			{<commit_msg>Fix missing truncated warning for ObjectPIDFile attribute.<commit_after>/*This code is part of the Epoch Init System.
* The Epoch Init System is maintained by Subsentient.
* This software is public domain.
* Please read the file UNLICENSE.TXT for more information.*/

/**This file handles the parsing of epoch.conf, our configuration file.
 * It adds everything into the object table.**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <signal.h>
#include <ctype.h>
#include "epoch.h"

/*We want the only interface for this to be LookupObjectInTable().*/
ObjTable *ObjectTable = NULL;

/*Used to allow for things like 'ObjectStartPriority Services', where Services == 3, for example.*/
static struct _PriorityAliasTree
{ /*Start/Stop priority alias support for grouping.*/
	char Alias[MAX_DESCRIPT_SIZE];
	unsigned long Target;
	
	struct _PriorityAliasTree *Next;
	struct _PriorityAliasTree *Prev;
} *PriorityAliasTree = NULL;

/*Used to allow runlevels to be inherited by other runlevels.*/
static struct _RunlevelInheritance
{ /*I __REVILE__ this solution.
Epoch is just a linked list of linked lists anymore.*/
	char Inheriter[MAX_DESCRIPT_SIZE];
	char Inherited[MAX_DESCRIPT_SIZE];
	
	struct _RunlevelInheritance *Next;
	struct _RunlevelInheritance *Prev;
} *RunlevelInheritance = NULL;

/*Holds the system hostname.*/
char Hostname[MAX_LINE_SIZE] = { '\0' };

/*Function forward declarations for all the statics.*/
static ObjTable *AddObjectToTable(const char *ObjectID);
static char *NextLine(const char *InStream);
static rStatus GetLineDelim(const char *InStream, char *OutStream);
static rStatus ScanConfigIntegrity(void);
static void ConfigProblem(short Type, const char *Attribute, const char *AttribVal, unsigned long LineNum);
static unsigned long PriorityAlias_Lookup(const char *Alias);
static void PriorityAlias_Add(const char *Alias, unsigned long Target);
static void PriorityAlias_Shutdown(void);
static void RLInheritance_Add(const char *Inheriter, const char *Inherited);
static Bool RLInheritance_Check(const char *Inheriter, const char *Inherited);
static void RLInheritance_Shutdown(void);

/*Used for error handling in InitConfig() by ConfigProblem().*/
enum { CONFIG_EMISSINGVAL = 1, CONFIG_EBADVAL, CONFIG_ETRUNCATED, CONFIG_EAFTER, CONFIG_EBEFORE, CONFIG_ELARGENUM };

/*Actual functions.*/
static char *NextLine(const char *InStream)
{
	if (!(InStream = strstr(InStream, "\n")))
	{
		return NULL;
	}

	if (*(InStream + 1) == '\0')
	{ /*End of file.*/
		return NULL;
	}

	++InStream; /*Plus one for the newline. We want to skip past it.*/

	return (char*)InStream;
}

/*This function was so useful I gave it external linkage.*/
char *WhitespaceArg(const char *InStream)
{  /*This is used for parsing lines that need to be divided by spaces.*/
	while (*InStream != ' ' && *InStream != '\t' &&
			*InStream != '\n' && *InStream != '\0') ++InStream;
	
	if (*InStream == '\n' || *InStream == '\0')
	{
		return NULL;
	}
	
	while (*InStream == ' ' || *InStream == '\t') ++InStream;
	
	if (*InStream == '\0' || *InStream == '\n')
	{
		return NULL;
	}
	
	return (char*)InStream;
}


static void ConfigProblem(short Type, const char *Attribute, const char *AttribVal, unsigned long LineNum)
{ /*Special little error handler used by InitConfig() to prevent repetitive duplicate errors.*/
	char TmpBuf[1024];
	char LogBuffer[MAX_LINE_SIZE];

	switch (Type)
	{
		case CONFIG_EMISSINGVAL:
			snprintf(TmpBuf, 1024, "Missing or bad value for attribute %s in epoch.conf line %lu.\nIgnoring.",
					Attribute, LineNum);
			break;
		case CONFIG_EBADVAL:
			snprintf(TmpBuf, 1024, "Bad value %s for attribute %s in epoch.conf line %lu.", AttribVal, Attribute, LineNum);
			break;
		case CONFIG_ETRUNCATED:
			snprintf(TmpBuf, 1024, "Attribute %s in epoch.conf line %lu has\n"
					"abnormally long value and may have been truncated.", Attribute, LineNum);
			break;
		case CONFIG_EAFTER:
			snprintf(TmpBuf, 1024, "Attribute %s cannot be set after an ObjectID attribute; "
					"epoch.conf line %lu. Ignoring.", Attribute, LineNum);
			break;
		case CONFIG_EBEFORE:
			snprintf(TmpBuf, 1024, "Attribute %s comes before any ObjectID attribute.\n"
					"epoch.conf line %lu. Ignoring.", Attribute, LineNum);
			break;
		case CONFIG_ELARGENUM:
			snprintf(TmpBuf, 1024, "Attribute %s in epoch.conf line %lu has\n"
					"abnormally high numeric value and may cause malfunctions.", Attribute, LineNum);
			break;
		default:
			return;
	}
	
	snprintf(LogBuffer, MAX_LINE_SIZE, "CONFIG: " CONSOLE_COLOR_YELLOW "WARNING: " CONSOLE_ENDCOLOR "%s\n", TmpBuf);
	
	SpitWarning(TmpBuf);
	WriteLogLine(LogBuffer, true);
}

rStatus InitConfig(void)
{ /*Set aside storage for the table.*/
	FILE *Descriptor = NULL;
	struct stat FileStat;
	char *ConfigStream = NULL, *Worker = NULL;
	ObjTable *CurObj = NULL, *ObjWorker = NULL;
	char DelimCurr[MAX_LINE_SIZE] = { '\0' };
	unsigned long LineNum = 1;
	const char *CurrentAttribute = NULL;
	Bool LongComment = false;
	char ErrBuf[MAX_LINE_SIZE];
	
	/*Get the file size of the config file.*/
	if (stat(CONFIGDIR CONF_NAME, &FileStat) != 0)
	{ /*Failure?*/
		SpitError("Failed to obtain information about configuration file epoch.conf.\nDoes it exist?");
		return FAILURE;
	}
	else
	{ /*No? Use the file size to allocate space in memory, since a char is a byte big.
	* If it's not a byte on your platform, your OS is not UNIX, and Epoch was not designed for you.*/
		ConfigStream = malloc(FileStat.st_size + 1);
	}

	Descriptor = fopen(CONFIGDIR CONF_NAME, "r"); /*Open the configuration file.*/

	/*Read the file into memory. I don't really trust fread(), but oh well.
	 * People will whine if I use a loop instead.*/
	fread(ConfigStream, 1, FileStat.st_size, Descriptor);
	
	ConfigStream[FileStat.st_size] = '\0'; /*Null terminate.*/
	
	fclose(Descriptor); /*Close the file.*/

	Worker = ConfigStream;

	/*Empty file?*/
	if ((*Worker == '\n' && *(Worker + 1) == '\0') || *Worker == '\0')
	{
		SpitError("Seems that epoch.conf is empty or corrupted.");
		free(ConfigStream);
		return FAILURE;
	}

	do /*This loop does most of the parsing.*/
	{
		
		/*Allow whitespace to precede a line in case people want to create a block-styled appearance.*/
		while (*Worker == ' ' || *Worker == '\t') ++Worker;
		
		/**Multi-line comment support: Multi-line comments are created in the following way:
		 * >!> stuff
		 * stuff
		 * stuff stuff
		 * stuffy stuff
		 * <!< stuff
		 * stuff
		 * 
		 * It is not recognized to place a multi-line comment beginner or terminator anywhere but the beginning
		 * of the line. As such, one may place do things like "ObjectID >!>" to create an object with ID ">!>". **/

		if (!strncmp(Worker, "<!<", strlen("<!<")))
		{ /*It's probably not good to have stray multi-line comment terminators around.*/
			if (!LongComment)
			{
				snprintf(ErrBuf, MAX_LINE_SIZE, "Stray multi-line comment terminator on line %lu\n", LineNum);
				SpitWarning(ErrBuf);
				continue;
			}
			LongComment = false;
			
			/*Allow next line to begin right ater the terminator on the same line.*/
			Worker += strlen("<!<");
			while (*Worker == ' ' || *Worker == '\t') ++Worker;
		}
		else if (LongComment)
		{
			continue;
		}
		else if (!strncmp(Worker, ">!>", strlen(">!>")))
		{
			LongComment = true;
			continue;
		}
		
		/**Single-line comments are created by placing "#" at the beginning of the line. Placing them
		 * anywhere else has no effect and as such '#' may be used in commands and object IDs and descriptions.**/
		if (*Worker == '\n')
		{ /*Empty line.*/
			continue;
		}
		else if (*Worker == '#')
		{ /*Line is just a comment.*/
			continue;
		}
		
		/**Global configuration begins here.**/
		if (!strncmp(Worker, (CurrentAttribute = "DisableCAD"), strlen("DisableCAD")))
		{ /*Should we disable instant reboots on CTRL-ALT-DEL?*/
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}

			if (!strcmp(DelimCurr, "true"))
			{
				DisableCAD = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				DisableCAD = false;
			}
			else
			{				
				DisableCAD = true;
				
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}

			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "BlankLogOnBoot"), strlen("BlankLogOnBoot")))
		{ /*Should the log only hold the current boot cycle's logs?*/

			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}

			if (!strcmp(DelimCurr, "true"))
			{
				BlankLogOnBoot = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				BlankLogOnBoot = false;
			}
			else
			{				
				BlankLogOnBoot = false;
				
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}

			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ShellEnabled"), strlen("ShellEnabled")))
		{
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);

				continue;
			}
			
			if (!strcmp(DelimCurr, "true"))
			{
				ShellEnabled = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				ShellEnabled = false;
			}
			else
			{
				ShellEnabled = USE_SHELL_BY_DEFAULT;
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}
		}
		else if (!strncmp(Worker, (CurrentAttribute = "EnableLogging"), strlen("EnableLogging")))
		{
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);

				continue;
			}
			
			if (!strcmp(DelimCurr, "true"))
			{
				EnableLogging = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				EnableLogging = false;
			}
			else
			{
				
				EnableLogging = false;
				
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "RunlevelInherits"), strlen("RunlevelInherits")))
		{
			char Inheriter[MAX_DESCRIPT_SIZE], Inherited[MAX_DESCRIPT_SIZE];
			const char *TWorker = DelimCurr;
			unsigned long TInc = 0;
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EAFTER, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			for (; *TWorker != ' ' && *TWorker != '\t' && *TWorker != '\0' && TInc < MAX_DESCRIPT_SIZE - 1; ++TInc, ++TWorker)
			{
				Inheriter[TInc] = *TWorker;
			}
			Inheriter[TInc] = '\0';
			
			if (*TWorker == '\0')
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
				continue;
			}
			
			TWorker = WhitespaceArg(TWorker);
			
			if (strstr(TWorker, " ") || strstr(TWorker, "\t"))
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
				continue;
			}
			
			snprintf(Inherited, MAX_DESCRIPT_SIZE, "%s", TWorker);
			
			RLInheritance_Add(Inheriter, Inherited);
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "DefinePriority"), strlen("DefinePriority")))
		{
			char Alias[MAX_DESCRIPT_SIZE] = { '\0' };
			unsigned long Target = 0, TInc = 0;
			const char *TWorker = DelimCurr;
			
			if (CurObj != NULL)
			{ /*We can't allow this in object-local options, because then this may not be properly defined.*/
				ConfigProblem(CONFIG_EAFTER, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				
				continue;
			}
			
			for (; *TWorker != ' ' && *TWorker != '\t' &&
				*TWorker != '\0' && TInc < MAX_DESCRIPT_SIZE -1; ++TInc, ++TWorker)
			{ /*Copy in the identifier.*/
				Alias[TInc] = *TWorker;
			}
			Alias[TInc] = '\0';
			
			if (*TWorker == '\0')
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
				continue;
			}
			
			TWorker = WhitespaceArg(TWorker); /*I abuse this delightful little function. It was meant for do-while loops.*/
			
			if (!AllNumeric(TWorker))
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
				continue;
			}
			
			Target = atol(TWorker);
			
			PriorityAlias_Add(Alias, Target); /*Now add it to the linked list.*/
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "AlignStatusReports"), strlen("AlignStatusReports")))
		{
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);

				continue;
			}
			
			if (!strcmp(DelimCurr, "true"))
			{
				AlignStatusReports = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				AlignStatusReports = false;
			}
			else
			{
				
				AlignStatusReports = false;
				
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		/*This will mount /dev, /proc, /sys, /dev/pts, and /dev/shm on boot time, upon request.*/
		else if (!strncmp(Worker, (CurrentAttribute = "MountVirtual"), strlen("MountVirtual")))
		{
			const char *TWorker = DelimCurr;
			unsigned long Inc = 0;
			char CurArg[MAX_DESCRIPT_SIZE];
			const char *VirtualID[2][5] = { { "procfs", "sysfs", "devfs", "devpts", "devshm" },
											{ "procfs+", "sysfs+", "devfs+", "devpts+", "devshm+" } };
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);

				continue;
			}
			
			do
			{ /*Load in all the arguments in the line.*/
				Bool FoundSomething = false;
				
				for (Inc = 0; TWorker[Inc] != ' ' && TWorker[Inc] != '\t' && TWorker[Inc] != '\n' &&
					TWorker[Inc] != '\0' && Inc < (MAX_DESCRIPT_SIZE - 1); ++Inc)
				{ /*Copy in the argument for this line.*/
					CurArg[Inc] = TWorker[Inc];
				}
				CurArg[Inc] = '\0';
				
				
				for (Inc = 0; Inc < 5; ++Inc)
				{ /*Search through the argument to see what it matches.*/
					if (!strncmp(VirtualID[0][Inc], CurArg, strlen(VirtualID[0][Inc])))
					{
						AutoMountOpts[Inc] = (!strcmp(VirtualID[1][Inc], CurArg) ? 2 : true);
						FoundSomething = true;
						break;
					}
				}

				if (!FoundSomething)
				{ /*If it doesn't match anything, that's bad.*/
					ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
					
					continue;
				}
					
			} while ((TWorker = WhitespaceArg(TWorker)));
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		/*Now we get into the actual attribute tags.*/
		else if (!strncmp(Worker, (CurrentAttribute = "BootBannerText"), strlen("BootBannerText")))
		{ /*The text shown at boot up as a kind of greeter, before we start executing objects. Can be disabled, off by default.*/
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!strcmp(DelimCurr, "NONE")) /*So, they decided to explicitly opt out of banner display. Ok.*/
			{
				BootBanner.BannerText[0] = '\0';
				BootBanner.BannerColor[0] = '\0';
				BootBanner.ShowBanner = false; /*Should already be false, but to prevent possible bugs...*/
				continue;
			}
			snprintf(BootBanner.BannerText, MAX_LINE_SIZE, "%s", DelimCurr);
			
			BootBanner.ShowBanner = true;
			
			if ((strlen(DelimCurr) + 1) >= MAX_DESCRIPT_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "BootBannerColor"), strlen("BootBannerColor")))
		{ /*Color for boot banner.*/
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!strcmp(DelimCurr, "NONE")) /*They don't want a color.*/
			{
				BootBanner.BannerColor[0] = '\0';
				continue;
			}
			
			SetBannerColor(DelimCurr); /*Function to be found elsewhere will do this for us, otherwise this loop would be even bigger.*/
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "DefaultRunlevel"), strlen("DefaultRunlevel")))
		{
			if (CurRunlevel[0] != 0)
			{ /*If the runlevel has already been set, don't set it again.
				* This prevents a rather nasty bug.*/
				continue;
			}
			
			if (CurObj != NULL)
			{ /*What the warning says. It'd get all weird if we allowed that.*/
				ConfigProblem(CONFIG_EAFTER, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}	
			
			snprintf(CurRunlevel, MAX_DESCRIPT_SIZE, "%s", DelimCurr);
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "Hostname"), strlen("Hostname")))
		{
			if (CurObj != NULL)
			{ /*What the warning says. It'd get all weird if we allowed that.*/
				ConfigProblem(CONFIG_EAFTER, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;

			}
			
			if (!strncmp(DelimCurr, "FILE", strlen("FILE")))
			{
				FILE *TDesc;
				unsigned long Inc = 0;
				short TChar;
				const char *TW = DelimCurr;
				char THostname[MAX_LINE_SIZE];
				
				TW += strlen("FILE");
				
				for (; *TW == ' ' || *TW == '\t'; ++TW);
				
				if (!(TDesc = fopen(TW, "r")))
				{
										snprintf(ErrBuf, sizeof ErrBuf, "Failed to set hostname from file \"%s\".\n", TW);
					SpitWarning(ErrBuf);
					continue;
				}
				
				for (Inc = 0; (TChar = getc(TDesc)) != EOF && Inc < MAX_LINE_SIZE - 1; ++Inc)
				{
					THostname[Inc] = (char)TChar;
				}
				THostname[Inc] = '\0';
				
				/*Skip past spaces, tabs, and newlines.*/
				for (TW = THostname; *TW == '\n' ||
					*TW == ' ' || *TW == '\t'; ++TW);
				
				/*Copy into the real hostname from our new offset.*/
				for (Inc = 0; TW[Inc] != '\0' && TW[Inc] != '\n'; ++Inc)
				{
					Hostname[Inc] = TW[Inc];
				}
				Hostname[Inc] = '\0';
				
				fclose(TDesc);
			}
			else
			{	
				snprintf(Hostname, MAX_LINE_SIZE, "%s", DelimCurr);
			}
			
								
			/*Check for spaces and tabs in the actual hostname.*/
			if (strstr(Hostname, " ") != NULL || strstr(Hostname, "\t") != NULL)
			{
				SpitWarning("Tabs and/or spaces in hostname file. Cannot set hostname.");
				*Hostname = '\0'; /*Set the hostname back to nothing.*/
				continue;
			}
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}		
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectID"), strlen("ObjectID")))
		{ /*ASCII value used to identify this object internally, and also a kind of short name for it.*/
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}

			CurObj = AddObjectToTable(DelimCurr); /*Sets this as our current object.*/

			if ((strlen(DelimCurr) + 1) >= MAX_DESCRIPT_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}

			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectEnabled"), strlen("ObjectEnabled")))
		{
			if (!CurObj)
			{

				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!strcmp(DelimCurr, "true"))
			{
				CurObj->Enabled = true;
			}
			else if (!strcmp(DelimCurr, "false"))
			{
				CurObj->Enabled = false;
			}
			else
			{
				ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectOptions"), strlen("ObjectOptions")))
		{
			const char *TWorker = DelimCurr;
			unsigned long Inc;
			char CurArg[MAX_DESCRIPT_SIZE];
			
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			do
			{
				
				for (Inc = 0; TWorker[Inc] != ' ' && TWorker[Inc] != '\t' && TWorker[Inc] != '\n'
					&& TWorker[Inc] != '\0' && Inc < (MAX_DESCRIPT_SIZE - 1); ++Inc)
				{
					CurArg[Inc] = TWorker[Inc];
				}
				CurArg[Inc] = '\0';
				
				if (!strcmp(CurArg, "NOWAIT"))
				{
					CurObj->Opts.EmulNoWait = true;
					snprintf(ErrBuf, sizeof ErrBuf, "Option NOWAIT is deprecated and has been partially removed.\n"
							"Emulating NOWAIT for object %s.\nLine %lu in epoch.conf", CurObj->ObjectID, LineNum);
					SpitWarning(ErrBuf);
				}
				else if (!strcmp(CurArg, "HALTONLY"))
				{ /*Allow entries that execute on shutdown only.*/
					CurObj->Started = true;
					CurObj->Opts.CanStop = false;
					CurObj->Opts.HaltCmdOnly = true;
				}
				else if (!strcmp(CurArg, "PERSISTENT"))
				{
					CurObj->Opts.CanStop = false;
				}
				else if (!strcmp(CurArg, "RAWDESCRIPTION"))
				{
					CurObj->Opts.RawDescription = true;
				}
				else if (!strcmp(CurArg, "SERVICE"))
				{
					CurObj->Opts.IsService = true;
				}
				else if (!strcmp(CurArg, "AUTORESTART"))
				{
					CurObj->Opts.AutoRestart = true;
				}
				else if (!strcmp(CurArg, "FORCESHELL"))
				{
					if (!ShellEnabled)
					{
						snprintf(ErrBuf, sizeof ErrBuf, "Object %s has FORCESHELL set, but ShellEnabled is false.\n"
														"Ignoring.\nepoch.conf line %lu", CurObj->ObjectID, LineNum);
						SpitWarning(ErrBuf);
					}
					else
					{
						CurObj->Opts.ForceShell = true;
					}
				}
				else if (!strncmp(CurArg, "TERMSIGNAL", strlen("TERMSIGNAL")))
				{
					const char *TWorker = CurArg + strlen("TERMSIGNAL");
					
					if (*TWorker != '=' || *(TWorker + 1) == '\0')
					{
						ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, CurArg, LineNum);
						continue;
					}
					
					++TWorker;
					
					if (AllNumeric(TWorker))
					{
						if (atoi(TWorker) > 255)
						{
							ConfigProblem(CONFIG_ELARGENUM, CurArg, NULL, LineNum);
						}

						CurObj->TermSignal = atoi(TWorker);
					}
					else if (!strcmp("SIGTERM", TWorker))
					{
						CurObj->TermSignal = SIGTERM;
					}
					else if (!strcmp("SIGKILL", TWorker))
					{
						CurObj->TermSignal = SIGKILL;
					}
					else if (!strcmp("SIGHUP", TWorker))
					{
						CurObj->TermSignal = SIGKILL;
					}
					else if (!strcmp("SIGINT", TWorker))
					{
						CurObj->TermSignal = SIGINT;
					}
					else if (!strcmp("SIGABRT", TWorker))
					{
						CurObj->TermSignal = SIGABRT;
					}
					else if (!strcmp("SIGQUIT", TWorker))
					{
						CurObj->TermSignal = SIGQUIT;
					}
					else if (!strcmp("SIGUSR1", TWorker))
					{
						CurObj->TermSignal = SIGUSR1;
					}
					else if (!strcmp("SIGUSR2", TWorker))
					{
						CurObj->TermSignal = SIGUSR2;
					}
					else
					{
						ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, TWorker, LineNum);
						continue;
					}
				}
				else
				{
					ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, CurArg, LineNum);
					break;
				}
			} while ((TWorker = WhitespaceArg(TWorker)));
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectDescription"), strlen("ObjectDescription")))
		{ /*It's description.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectDescription, MAX_DESCRIPT_SIZE, "%s", DelimCurr);
			
			if ((strlen(DelimCurr) + 1) >= MAX_DESCRIPT_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}

			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectStartCommand"), strlen("ObjectStartCommand")))
		{ /*What we execute to start it.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectStartCommand, MAX_LINE_SIZE, "%s", DelimCurr);

			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectPrestartCommand"), strlen("ObjectPrestartCommand")))
		{
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectPrestartCommand, MAX_LINE_SIZE, "%s", DelimCurr);
			
			if (strlen(DelimCurr) + 1 >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, NULL, LineNum);
				continue;
			}
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectReloadCommand"), strlen("ObjectReloadCommand")))
		{
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectReloadCommand, MAX_LINE_SIZE, "%s", DelimCurr);
			
			if (strlen(DelimCurr) + 1 >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectStopCommand"), strlen("ObjectStopCommand")))
		{ /*If it's "PID", then we know that we need to kill the process ID only. If it's "NONE", well, self explanitory.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}

			if (!strncmp(DelimCurr, "PIDFILE", strlen("PIDFILE")))
			{ /*They want us to kill a PID file on exit.*/
				const char *Worker = DelimCurr;
				
				CurObj->Opts.StopMode = STOP_PIDFILE;
				
				if (*(Worker += strlen("PIDFILE")) != '\0')
				{ /*It used to be that this was the only way to specify a PID file.*/
                                
					while (*Worker == ' ' || *Worker == '\t')
					{ /*Skip past all spaces and tabs.*/
						++Worker;
					}
					
					if (*Worker != '\0')
					{
						snprintf(CurObj->ObjectPIDFile, MAX_LINE_SIZE, "%s", Worker);
						CurObj->Opts.HasPIDFile = true;
					}
				}
			}
			else if (!strncmp(DelimCurr, "PID", strlen("PID")))
			{
				CurObj->Opts.StopMode = STOP_PID;
			}
			else if (!strncmp(DelimCurr, "NONE", strlen("NONE")))
			{
				CurObj->Opts.StopMode = STOP_NONE;
			}
			else
			{
				CurObj->Opts.StopMode = STOP_COMMAND;
				snprintf(CurObj->ObjectStopCommand, MAX_LINE_SIZE, "%s", DelimCurr);
			}
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectStartPriority"), strlen("ObjectStartPriority")))
		{
			/*The order in which this item is started. If it is disabled in this runlevel, the next object in line is executed, IF
			 * and only IF it is enabled. If not, the one after that and so on.*/
			
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!AllNumeric(DelimCurr)) /*Make sure we are getting a number, not Shakespeare.*/
			{ /*No number? We're probably looking at an alias.*/
				unsigned long TmpTarget = 0;
				
				if (!(TmpTarget = PriorityAlias_Lookup(DelimCurr)))
				{
					ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
					continue;
				}
				
				CurObj->ObjectStartPriority = TmpTarget;
				continue;
			}
			
			CurObj->ObjectStartPriority = atoi(DelimCurr);
			
			if (strlen(DelimCurr) >= 8)
			{ /*An eight digit number is too high.*/
				ConfigProblem(CONFIG_ELARGENUM, CurrentAttribute, NULL, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectStopPriority"), strlen("ObjectStopPriority")))
		{
			/*Same as above, but used for when the object is being shut down.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!AllNumeric(DelimCurr))
			{
				unsigned long TmpTarget = 0;
				
				if (!(TmpTarget = PriorityAlias_Lookup(DelimCurr)))
				{
					ConfigProblem(CONFIG_EBADVAL, CurrentAttribute, DelimCurr, LineNum);
					continue;
				}
				
				CurObj->ObjectStopPriority = TmpTarget;
				continue;
			}
			
			CurObj->ObjectStopPriority = atoi(DelimCurr);
			
			if (strlen(DelimCurr) >= 8)
			{ /*An eight digit number is too high.*/
				ConfigProblem(CONFIG_ELARGENUM, CurrentAttribute, NULL, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectPIDFile"), strlen("ObjectPIDFile")))
		{ /*This really needs to be specified if Opts.StopMode is STOP_PIDFILE, or we'll reset the object to STOP_PID.*/
			if (!CurObj)
			{
				ConfigProblem(CONFIG_EBEFORE, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			if (!GetLineDelim(Worker, DelimCurr))
			{
				ConfigProblem(CONFIG_EMISSINGVAL, CurrentAttribute, NULL, LineNum);
				continue;
			}
			
			snprintf(CurObj->ObjectPIDFile, MAX_LINE_SIZE, "%s", DelimCurr);
			CurObj->Opts.HasPIDFile = true;
			
			if ((strlen(DelimCurr) + 1) >= MAX_LINE_SIZE)
			{
				ConfigProblem(CONFIG_ETRUNCATED, CurrentAttribute, DelimCurr, LineNum);
			}
			
			continue;
		}
		else if (!strncmp(Worker, (CurrentAttribute = "ObjectRunlevels"), strlen("ObjectRunlevels")))
		{ /*Runlevel.*/
			char *TWorker;
			char TRL[MAX_DESCRIPT_SIZE], *TRL2;
			static const ObjTable *LastObject = NULL;
			
			if (!CurObj)
			{
{'fraction_non_alphanumeric': 0.134880658436214, 'fraction_numerical': 0.0038353909465020574, 'mean_word_length': 3.2232186305179007, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 140, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '563239', 'n_tokens_mistral': 25709, 'n_tokens_neox': 20303, 'n_words': 5192}