/*
 * configuration.c: logapp configuration handling
 *
 *
 * Copyright (C) 2007-2016 Michael Brunner <mibru@gmx.de>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * General Public License for more details.
 */

#include "logapp.h"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <termios.h>
#ifndef TIOCGWINSIZE
#include <sys/ioctl.h>
#endif

#include "configuration.h"
#include "logfile.h"


int show_usage = 0;
int show_version = 0;
int show_config = 0;

/* general configuration */
config_t config = {
	.argprefix 		= "logapp_",
	.strip_prefix		= "log",
	.executable		= EXECUTABLE,
	.dumbterm		= 0,
	.detectescape		= 0,
	.usepty			= 1,
	.ptyremovecr		= 1,
	.appendlog		= 0,
	.printsummary		= 0,
	.printlogname		= 0,
	.disable		= 0,
	.disable_keywords	= NULL,
	.logname		= "./logapp.log",
	.logtime		= 0,
	.logtimerstregexp	= NULL,
	.logreltime		= 0,
	.logenv			= 0,
	.circularlog		= 0,
	.maxlogsize		= 0,
	.locklogfile		= 1,
	.warnlogfilelock	= 1,
	.maxlogfiles		= 10,
	.logrename		= 1,
	.alignlog		= 1,
	.alignlinebreaks	= 1,
	.jointimeout		= 0,
	.configfile		= NULL,
	.configsection		= NULL,
	.custconfigfile		= NULL,
	.preexeccmd		= NULL,
	.postexeccmd		= NULL,
	.exitonexecfail		= 0,
	.extregexp		= 0,
};

/* default search path list for configuration file */
char* configsearchpath[]		= {
	"~/.logapprc",
	"/etc/logapp.conf",
	"/etc/logapp/logapp.conf",
};
#define CONFIGSEARCHPATH_SIZE (sizeof(configsearchpath)/sizeof(char*))

/* configuration for stdout */
pipe_t pstdout = {
	.fh		= 0,
	.cfhno		= STDOUT_FILENO,
	.name		= "stdout",
	.buf		= NULL,
	.charbased	= 0,
	.detectescape	= 0,
	.switchtocbmode = 0,
	.blen		= 65540,
	.bfill		= 0,
	.memcr		= 0,
	.ptyremovecr	= 0,
	.recalclen	= 0,
	.fgcol		= -1,
	.bgcol		= -1,
	.bold		= 0,
	.clip		= -2,
	.eclip  	= 2,
	.escreset	= "\033[0m",
	.esccolor	= NULL,
	.linecount	= 0,
	.lineprefix	= NULL,
	.linebreakpos	= {0},
	.regexp		= NULL,
	.regbgcol	= 4,
	.bgesccolor	= NULL,
	.execregexp	= NULL,
	.execcommand	= NULL,
};

/* configuration for stderr */
pipe_t pstderr = {
	.fh		= 0,
	.cfhno		= STDERR_FILENO,
	.name		= "stderr",
	.buf		= NULL,
	.charbased	= 0,
	.detectescape	= 0,
	.switchtocbmode = 0,
	.blen		= 65540,
	.bfill		= 0,
	.memcr		= 0,
	.ptyremovecr	= 0,
	.recalclen	= 0,
	.fgcol		= 1,
	.bgcol		= -1,
	.bold		= 1,
	.clip		= -1,
	.eclip		= 0,
	.escreset	= "\033[0m",
	.esccolor	= NULL,
	.linecount	= 0,
	.lineprefix	= "STDERR: ",
	.linebreakpos	= {0},
	.regexp		= NULL,
	.regbgcol	= 4,
	.bgesccolor	= NULL,
	.execregexp	= NULL,
	.execcommand	= NULL,
};

/* logfile configuration */
logfile_t logfile = {
	.fh		= 0,
	.name		= NULL,
	.sizelimit	= 0,
	.oldext		= ".old",
	.appendnr	= 0,
	.head		= ">> logapp:",
	.split		= "== logapp split:",
	.foot		= "<< logapp end:",
	.addnewline	= 0,
};

struct winsize ptysize;
struct termios ptytermios, ptytermios_bak;

/* application specific properties */
app_t app = {
	.exe		= NULL,
	.argv		= NULL,
	.argc		= 0,
	.pid		= 0,
	.pstdout	= &pstdout,
	.pstderr	= &pstderr,
	.ptysize	= &ptysize,
	.ptytermios	= &ptytermios,
	.ptytermios_bak = &ptytermios_bak,
	.active		= 0,
	.doexit		= 0,
	.exit_state	= 0,
	.starttime	= 0,
	.logfile	= &logfile,
};

const stringvalue_t boolvalues[] = {
	{ "true",	1 },
	{ "false",	0 },
	{ "on",		1 },
	{ "off",	0 },
	{ NULL,		0 }
};

const stringvalue_t colorvalues[] = {
	{ "disable",	-1 },
	{ "default",	-1 },
	{ "black",	0 },
	{ "red",	1 },
	{ "green",	2 },
	{ "brown",	3 },
	{ "blue",	4 },
	{ "magenta",	5 },
	{ "cyan",	6 },
	{ "white",	7 },
	{ NULL,		0 }
};

const stringvalue_t clipvalues[] = {
	{ "disable",	-1 },
	{ "auto",	-2 },
	{ NULL,		0 }
};

const stringvalue_t multprefixes[] = {
	{ "",		1 },
	{ "k",		1000 },
	{ "M",		1000000 },
	{ "G",		1000000000 },
	{ "ki",		1024 },
	{ "Mi",		1048576 },
	{ "Gi",		1073741824 },
	{ NULL,		0 }
};

/* all possible configuration parameters with help message */
arglist_t arglist[] = {
	/* General options */
	{ '\0', NULL,		NULL,		TNONE,	NULL,
		"General options:", 0 },
	{ '?',	"help",		NULL,		TNONE,	&show_usage,
		"show this help", 0 },
	{ '\0',	"version",	NULL,		TNONE,	&show_version,
		"show version information", 0 },
	{ '\0', "configfile",   "FILE",		TSTRING,
		&config.custconfigfile,	"configuration file", 0 },
	{ '\0',	"showconfig",	NULL,		TNONE,	&show_config,
		"print current configuration on the screen and exit", 0 },
	{ '\0', "configsection",   "NAME",	TSTRING,
		&config.configsection, "select configuration section", 0 },
	{ '\0', "disable",	NULL,		TNONE,  &config.disable,
		"disable output handling", 0 },
	{ '\0', "disable_keywords", "STRING",	TSTRING,
		&config.disable_keywords, "comma separated keywords to "
		"disable output handling when found in argument list", 0 },
	{ '\0', "detectescape",	"BOOL",		TBOOL,	&config.detectescape,
		"switch to charbased mode if escape sequence is detected", 0 },
#if CONFIG_SUPPORT_PTY
	{ '\0', "usepty",	"BOOL",		TBOOL,	&config.usepty,
		"use PTY for stream redirection", 0 },
	{ '\0', "ptyremovecr",  "BOOL",		TBOOL,  &config.ptyremovecr,
		"translate CR-LF to LF when capturing PTY", 0 },
#else
	{ '\0', "usepty",	"BOOL",		TBOOL,	&config.usepty,
		"<not supported> use PTY for stream redirection", 0 },
	{ '\0', "ptyremovecr",  "BOOL",		TBOOL,  &config.ptyremovecr,
		"<not supported> translate CR-LF to LF when capturing PTY",
		0 },
#endif
	{ '\0', "stdout_blen",	"SIZE",		TUINT,	&pstdout.blen,
		"stdout buffer size", 0 },
	{ '\0', "stderr_blen",	"SIZE",		TUINT,	&pstderr.blen,
		"stderr buffer size", 0 },
	{ '\0', "stdout_charbased",	"BOOL",	TBOOL,  &pstdout.charbased,
		"handle stdout char- instead of line-based", 0 },
	{ '\0', "stderr_charbased",	"BOOL",	TBOOL,  &pstderr.charbased,
		"handle stderr char- instead of line-based", 0 },
	{ '\0', "extended-regexp",	"BOOL", TBOOL,	&config.extregexp,
		"interpret regexp patterns as extended regular expressions",
		0 },
	/* Logging options */
	{ '\0', NULL,		NULL,		TNONE,	NULL,
		"Logging options:", 0 },
	{ 'l',  "logfile",	"NAME",		TSTRING, &config.logname,
		"application logfile", 0 },
	{ 'a',  "appendlog",	"BOOL",		TBOOL, &config.appendlog,
		"append to existing logfile", 0 },
	{ '\0', "maxlogsize",	"SIZE",	TUINT, &config.maxlogsize,
		"max. logfile size in KiB (0=no limit, 10-4000000) the file "
		"will be truncated if logrename isn't set", 0 },
	{ '\0', "logrename",	"BOOL",	TBOOL, &config.logrename,
		"rename logfile before replacing it", 0 },
	{ '\0', "circularlog",	"BOOL",	TBOOL, &config.circularlog,
		"write log in a circular way, keeping the max. size", 0 },
	{ '\0', "oldlogext",	"STRING",	TSTRING, &logfile.oldext,
		"extension for old logfile", 0 },
	{ '\0', "locklogfile",	"BOOL",	TBOOL, &config.locklogfile,
		"lock logfile when opening it", 0 },
	{ '\0', "warnlogfilelock", "BOOL",	TBOOL, &config.warnlogfilelock,
		"print a warning if current logfile is locked", 0 },
	{ 'n',  "print_logname", "BOOL",	TBOOL, &config.printlogname,
		"print logfile name after execution", 0 },
	{ '\0', "maxaltlogfiles", "COUNT",	TUINT, &config.maxlogfiles,
		"max # of alternate logfiles on lock", 0 },
	{ '\0', "alignlog",	"BOOL",	TBOOL, &config.alignlog,
		"use one line for every write in charbased mode", 0 },
	{ '\0', "jointimeout",	"TIME",	TUINT, &config.jointimeout,
		"join timeout for packets with alignlog active", 0 },
	{ '\0', "alignlinebreaks",	"BOOL",	TBOOL, &config.alignlinebreaks,
		"align logged line breaks in charbased mode", 0 },
	{ 't',  "logtime",	"BOOL",	TBOOL, &config.logtime,
		"add timestamp to each logged line", 0 },
	{ '\0', "logtime_rstregexp",	"STRING", TSTRING,
		&config.logtimerstregexp,
		"regular expression to reset log time on match", 0 },
	{ '\0',  "logreltime",	"BOOL",	TBOOL, &config.logreltime,
		"log relative time with --logtime", 0 },
	{ '\0',  "logenv",	"BOOL",	TBOOL, &config.logenv,
		"add environment variables to logfile", 0 },
	{ 'p',  "stdout_lineprefix",   "STRING",	TSTRING,
		&pstdout.lineprefix, "logfile line prefix for stdout", 0 },
	{ 'P',  "stderr_lineprefix",   "STRING",	TSTRING,
		&pstderr.lineprefix, "logfile line prefix for stderr", 0 },
	/* Console output options */
	{ '\0', NULL,		NULL,		TNONE,	NULL,
		"Console output options:", 0 },
	{ '\0', "dumbterm",	"BOOL",		TBOOL,	&config.dumbterm,
		"disable colors for use with dumb terminals", 0 },
	{ 's',  "print_summary", "BOOL",	TBOOL, &config.printsummary,
		"print execution summary", 0 },
	{ 'f',  "stdout_fgcol",	"COLOR",	TCOLOR, &pstdout.fgcol,
		"stdout console foreground color (disable=-1, 0-7)", 0 },
	{ 'F',  "stderr_fgcol",	"COLOR",	TCOLOR,   &pstderr.fgcol,
		"stderr console foreground color (disable=-1, 0-7)", 0 },
	{ 'b',  "stdout_bold",	"BOOL",		TBOOL,	&pstdout.bold,
		"bold stdout console font", 0 },
	{ 'B',  "stderr_bold",	"BOOL",		TBOOL,	&pstderr.bold,
		"bold stderr console font", 0 },
	{ 'r',  "stdout_regexp", "STRING", TSTRING, &pstdout.regexp,
		"regular expression to change stdout background color", 0 },
	{ 'R',	"stderr_regexp",   "STRING",	TSTRING, &pstderr.regexp,
		"regular expression to change stderr background color", 0 },
	{ '\0', "stdout_regexp_bgcol",	"COLOR", TCOLOR, &pstdout.regbgcol,
		"stdout console background color on regexp match (disable=-1, "
		"0-7)", 0 },
	{ '\0', "stderr_regexp_bgcol",	"COLOR", TCOLOR, &pstderr.regbgcol,
		"stderr console background color on regexp match (disable=-1, "
		"0-7)", 0 },
	{ 'c',  "stdout_clip",	"LENGTH",	TCLIP,	&pstdout.clip,
		"clip stdout console at column LENGTH (disable=-1, auto=-2)",
		0 },
	{ 'C',  "stderr_clip",	"LENGTH",	TCLIP,	&pstderr.clip,
		"clip stderr console at column LENGTH (disable=-1, auto=-2)",
		0 },
	/* Command execution options */
	{ '\0', NULL,		NULL,		TNONE,	NULL,
		"Command execution options:", 0 },
	{ '\0', "exitonexecfail", "BOOL",	TBOOL,	&config.exitonexecfail,
		"exit if execution of command fails", 0 },
	{ '\0', "preexec",	"STRING",	TSTRING, &config.preexeccmd,
		"command executed before application start", 0 },
	{ '\0', "postexec",	"STRING",	TSTRING, &config.postexeccmd,
		"command executed after application exit", 0 },
	{ 'e',  "stdout_execregexp", "STRING", TSTRING, &pstdout.execregexp,
		"regular expression to execute command", 0 },
	{ 'E',	"stderr_execregexp",   "STRING", TSTRING, &pstderr.execregexp,
		"regular expression to execute command", 0 },
	{ 'x',	"stdout_execcommand", "STRING", TSTRING, &pstdout.execcommand,
		"command to be executed after stdout regexp match", 0 },
	{ 'X',	"stderr_execregexp",   "STRING", TSTRING, &pstderr.execcommand,
		"command to be executed after stderr regexp match", 0 },
};

const int arglistsize = ((int)(sizeof(arglist)/sizeof(arglist_t)));

void show_configuration(void)
{
	unsigned int i;

	message("%s %s\n\n", EXECUTABLE, VERSION);
	message("current configuration:\n");
	message("  argument prefix         %s\n", config.argprefix);
	message("  executable prefix       %s\n", config.strip_prefix);
	message("  active config file      %s\n",
		config.configfile?config.configfile:"<none>");
	message("  custom configfile path  %s\n",
		config.custconfigfile?config.custconfigfile:"<none>");
	for (i=0; i<CONFIGSEARCHPATH_SIZE; i++) {
		message("  config search path (%d)  %s\n", i,
			configsearchpath[i]);
	};
	message("  activate section        %s\n",
		config.configsection?config.configsection:"<none>");
	message("  application logfile     %s\n",
		config.logname?config.logname:"<none>");
	message("  append to logfile       %i\n", config.appendlog);
	message("  log timestamps          %i\n", config.logtime);
	message("  log time reset regexp   %s\n", config.logtimerstregexp);
	message("  log relative time       %i\n", config.logreltime);
	message("  log environment         %i\n", config.logenv);
	message("  align log writes left   %i\n", config.alignlog);
	message("  join timeout            %i\n", config.jointimeout);
	message("  align line breaks       %i\n", config.alignlinebreaks);
	message("  lock logfiles           %i\n", config.locklogfile);
	message("  warning on logfile lock %i\n", config.warnlogfilelock);
	message("  max. alternate logfiles %u\n", config.maxlogfiles - 1);
	message("  maximum logsize         %u\n", logfile.sizelimit);
	message("  rename logfiles         %i\n", config.logrename);
	message("  circular logfile        %i\n", config.circularlog);
	message("  extension for old logs  %s\n", logfile.oldext);
	message("  exit on execution fail  %i\n", config.exitonexecfail);
	
	message("\n  application executable  %s\n",
		app.exe?app.exe:"<none>");
	message("  application argc        %d\n", app.argc);
	for (i=0; i<(unsigned int)app.argc; i++) {
		message("  application argv[%d]     %s\n", i,
			app.argv[i]);
	}

	message("\n");
	message("  preexec command         %s\n",
		config.preexeccmd?config.preexeccmd:"<none>");
	message("  postexec command        %s\n",
		config.postexeccmd?config.postexeccmd:"<none>");

	message("\n");
	if (CONFIG_USE_THREADS) {
		message("  use threads             %i\n", 1);
	} else {
		message("  use threads             <not supported>\n");
	}
	if (CONFIG_SUPPORT_PTY) {
		message("  use PTY                 %i\n", config.usepty);
		message("  remove CR for PTY       %i\n", config.ptyremovecr);
	} else {
		message("  use PTY                 <not supported>\n");
		message("  remove CR for PTY       <not supported>\n");
	}

	message("\n  ext. regexp patterns    %i\n", config.extregexp);

	message("\n  dumb terminal mode      %i\n", config.dumbterm);
	message("  print_summary           %i\n", config.printsummary);
	message("  print_logname           %i\n", config.printlogname);
	message("  disable output handling %i\n", config.disable);
	message("  disable keywords        %s\n",
		config.disable_keywords?config.disable_keywords:"<none>");

	message("\n  stdout buffer length    %i\n", app.pstdout->blen);
	message("  stdout foreground color %i\n", app.pstdout->fgcol);
	message("  stdout background color %i\n", (app.pstdout->bgcol == 9)?
		-1:app.pstdout->bgcol);
	message("  stdout regexp bg color  %i\n", (app.pstdout->regbgcol == 9)?
		-1:app.pstdout->regbgcol);
	message("  stdout bold font        %i\n", app.pstdout->bold);
	message("  stdout clip at column   ");
	if (app.pstdout->eclip==1) {
		message("%d\n", app.pstdout->clip);
	} else {
		message("%s\n", app.pstdout->eclip?"auto":"disable");
	}
	message("  stdout line prefix      %s\n", app.pstdout->lineprefix?
		app.pstdout->lineprefix:"<none>");
	message("  stdout regexp           %s\n", app.pstdout->regexp?
		app.pstdout->regexp:"<disabled>");
	message("  stdout exec regexp      %s\n", app.pstdout->execregexp?
		app.pstdout->execregexp:"<disabled>");
	message("  stdout regexp command   %s\n", app.pstdout->execcommand?
		app.pstdout->execcommand:"<none>");
	message("  stdout charbased        %i\n", app.pstdout->charbased);
	message("  stdout esc detection    %i\n", app.pstdout->detectescape);

	message("\n  stderr buffer length    %i\n", app.pstderr->blen);
	message("  stderr foreground color %i\n", app.pstderr->fgcol);
	message("  stderr background color %i\n", (app.pstderr->bgcol == 9)?
		-1:app.pstderr->bgcol);
	message("  stderr regexp bg color  %i\n", (app.pstderr->regbgcol == 9)?
		-1:app.pstderr->regbgcol);
	message("  stderr bold font        %i\n", app.pstderr->bold);
	message("  stderr clip at column   ");
	if (app.pstderr->eclip==1) {
		message("%d\n", app.pstderr->clip);
	} else {
		message("%s\n", app.pstderr->eclip?"auto":"disable");
	}
	message("  stderr line prefix      %s\n", app.pstderr->lineprefix?
		app.pstderr->lineprefix:"<none>");
	message("  stderr regexp           %s\n", app.pstderr->regexp?
		app.pstderr->regexp:"<disabled>");
	message("  stderr exec regexp      %s\n", app.pstderr->execregexp?
		app.pstderr->execregexp:"<disabled>");
	message("  stderr regexp command   %s\n", app.pstderr->execcommand?
		app.pstderr->execcommand:"<none>");
	message("  stderr charbased        %i\n", app.pstderr->charbased);
	message("  stderr esc detection    %i\n", app.pstderr->detectescape);
}

char* get_longpath(const char* filename)
{
	char* home;
	char* longpath;

	if (!strncmp(filename, "~/", 2)) {
		home = getenv("HOME");
		if (home == NULL) {
			home = "./";
		};

		longpath = (char*) malloc(strlen(home) + strlen(filename) + 1);
		if (longpath != NULL) {
			strcpy(longpath, home);
			strcat(longpath, "/");
			strcat(longpath, filename + 2);
		}
	} else {
		longpath = strdup(filename);
	}

	if (longpath == NULL) {
		error_outofmemory();
		return NULL;
	}

	return longpath;
}

static int fixup_pipe(pipe_t *pipe)
{
	if (config.detectescape)
		pipe->detectescape = 1;

	if ((pipe->fgcol < -1) || (pipe->fgcol > 7)) {
		error("%s foreground color out of range\n", pipe->name);
		return -1;
	}

	if ((pipe->bgcol < -1) || (pipe->bgcol > 7)) {
		error("%s background color out of range\n", pipe->name);
		return -1;
	}

	if (pipe->regbgcol == -1)
		pipe->regexp = NULL;

	if ((pipe->regbgcol < -1) || (pipe->regbgcol > 7)) {
		error("%s regexp background color out of range\n", pipe->name);
		return -1;
	} else if (pipe->bgcol != pipe->regbgcol) {
		if (pipe->bgcol == -1)
			pipe->bgcol = 9;

		if (pipe->regbgcol == -1)
			pipe->regbgcol = 9;
	}

	switch (pipe->clip) {
		case -1:	pipe->eclip = 0;
				pipe->clip = 80;
				break;
		case -2:	pipe->eclip = 2;
				pipe->clip = 80;
				break;
		default:	if (pipe->clip < 0)
					pipe->eclip = 0;
				else
					pipe->eclip = 1;
	}

	if (pipe->lineprefix != NULL) {
		pipe->lineprefixlen = strlen(pipe->lineprefix);
		if (pipe->lineprefixlen == 0) {
			free(pipe->lineprefix);
			pipe->lineprefix = NULL;
		}
	}

	/* build console escape string for this pipe */
	int len;
	pipe->escresetlen = strlen(pipe->escreset);
	len = pipe->escresetlen;

	if (pipe->fgcol >= 0)
		len += 5;
	if (pipe->regbgcol >= 0)
		len += 5;
	if (pipe->bold)
		len += 4;

	pipe->esccolor = malloc(len + 1);
	pipe->esccolorlen = 0;
	if (!pipe->esccolor) {
		error_outofmemory();
		return -1;
	}

	memcpy(pipe->esccolor, pipe->escreset, pipe->escresetlen);
	pipe->esccolorlen += pipe->escresetlen;

	if (pipe->fgcol >= 0) {
		snprintf(pipe->esccolor + pipe->esccolorlen, 6, "\033[3%dm",
			pipe->fgcol);
		pipe->esccolorlen += 5;
	}

	if (pipe->regbgcol >= 0) {
		pipe->bgesccolor = pipe->esccolor + pipe->esccolorlen + 3;
		snprintf(pipe->esccolor + pipe->esccolorlen, 6, "\033[4%dm",
			pipe->bgcol); 
		pipe->esccolorlen += 5;
	} else {
		pipe->bgesccolor = NULL;
	}
	
	if (pipe->bold) {
		snprintf(pipe->esccolor + pipe->esccolorlen, 5, "\033[1m");
		pipe->esccolorlen += 4;
	}

	int regexp_cflags = REG_NOSUB|REG_NEWLINE;
	if (config.extregexp)
		regexp_cflags |= REG_EXTENDED;

	if (pipe->regexp) {
		if (strlen(pipe->regexp)) {
			if (regcomp(&pipe->preg, pipe->regexp,
				    regexp_cflags)) {
				error("unable to process regular expression "
				      "for %s\n", pipe->name);
				return -1;
			}
		} else {
			pipe->regexp = NULL;
		}
	}

	if (pipe->execregexp) {
		if (strlen(pipe->execregexp)) {
			if (regcomp(&pipe->pexecreg, pipe->execregexp,
				    regexp_cflags)) {
				error("unable to process regular expression "
				      "for %s\n", pipe->name);
				return -1;
			}
		} else {
			pipe->execregexp = NULL;
		}
		/* disable execregexp if there was no command provided */
		if (pipe->execcommand == NULL) {
			pipe->execregexp = NULL;
		} else {
			if (strlen(pipe->execcommand) == 0) {
			pipe->execregexp = NULL;
			}
		}
	}

	return 0;
}

int fixup_config(void)
{
	if (strlen(config.logname) == 0)
		config.logname = NULL;

	/* We have to add the original logfile to maxlogfiles */
	config.maxlogfiles++;

	if (config.maxlogsize == 0) {
		logfile.sizelimit = 0;
	} else if ((config.maxlogsize < 10) || (config.maxlogsize > 4000000)) {
		error("maximum logsize out of range (0=no limit, 10-4000000 "
		      "KiB)\n");
		return -1;
	} else {
		logfile.sizelimit = config.maxlogsize * 1024;
	}

	if (CONFIG_SUPPORT_PTY && config.usepty) {
		app.pstdout->ptyremovecr = config.ptyremovecr;
	}

	if (fixup_pipe(app.pstdout))
		return -1;

	if (fixup_pipe(app.pstderr))
		return -1;

	if (config.logtimerstregexp) {
		if (strlen(config.logtimerstregexp)) {
			int regexp_cflags = REG_NOSUB|REG_NEWLINE;
			if (config.extregexp)
				regexp_cflags |= REG_EXTENDED;

			if (regcomp(&config.plogtimerstreg,
				    config.logtimerstregexp, regexp_cflags)) {
				error("unable to process logtime rst regexp\n");
				return -1;
			}
		} else {
			config.logtimerstregexp = NULL;
		}
	}

	/* Prepare indent string */
	int maxindentlen = 15;
	if (app.pstdout->lineprefixlen > app.pstderr->lineprefixlen)
		maxindentlen+=app.pstdout->lineprefixlen;
	else
		maxindentlen+=app.pstderr->lineprefixlen;

	app.logfile->indent = malloc(maxindentlen+1);
	if (!app.logfile->indent) {
		error_outofmemory();
		return -1;
	}
	memset(app.logfile->indent, ' ', maxindentlen);

	return 0;
}

void cleanup_config(void)
{
	if (app.argv) {
		free(app.argv);
		app.argv = NULL;
	}
}

int get_display_parameters(void)
{
	/* As the terminal settings are not available if the stream is
	 * redirected we try all three standard streams and choose the first
	 * one that works. If none works we continue without the settings... */

	/* Get current terminal window size */
	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, (char*)app.ptysize) < 0) { 
		if (ioctl(STDERR_FILENO, TIOCGWINSZ, (char*)app.ptysize) < 0) {
			if (ioctl(STDIN_FILENO, TIOCGWINSZ,
				  (char*)app.ptysize) < 0) {
				app.ptysize = NULL;
				app.ptytermios = NULL;
				return -1;
			}
		}
	}

	/* Get terminal attributes */
	if (tcgetattr(STDOUT_FILENO, app.ptytermios) < 0) {
		if (tcgetattr(STDERR_FILENO, app.ptytermios) < 0) {
			if (tcgetattr(STDIN_FILENO, app.ptytermios) < 0) {
				error("unable to get window termios\n");
				app.ptytermios = NULL;
				return -1;
			}
		}
	}

	return 0;
}

int adjust_clipping(void)
{
	if (get_display_parameters()) {
		/* We don't seem to have a terminal window and therefore can't
		 * get a size for automatic line clipping - disable it! */
		if (pstdout.eclip == 2) {
			pstdout.eclip = 0;
		}
		if (pstderr.eclip == 2) {
			pstderr.eclip = 0;
		}

		return -1;
	}

	if (pstdout.eclip == 2)
		pstdout.clip = app.ptysize->ws_col;

	if (pstderr.eclip == 2)
		pstderr.clip = app.ptysize->ws_col;

	/* inform the application that window size changed */
	if (app.pid != 0) {
		if (ioctl(app.pstdout->fh, TIOCSWINSZ,
			  (char*)app.ptysize) < 0) {
			error("Unable to set window size");
			return -1;
		}
	}

	return 0;
}

int get_argid(char* arg, int isshort)
{
	int i;

	if (!arg)
		return -1;

	for (i=0; i<arglistsize; i++) {
		if (isshort) {
			if (!arglist[i].shrt)
				continue;
			if (arglist[i].shrt == *arg)
				return(i);
		} else {
			if (!arglist[i].lng)
				continue;
			if (!strcmp(arglist[i].lng, arg))
				return(i);
		}
	}

	return -1;
}

int string2numvalue(char* string, int *value, const stringvalue_t *list,
		    const stringvalue_t *prefixes)
{
	char*	p;
	int tmp_value;

	if (list) {
		while (strcmp(string, list->string)) {
			list++;
			if (list->string == NULL)
				break;
		}
		if (list->string != NULL) {
			*value = list->value;

			return 0;
		}
	}

	errno = 0;
	tmp_value = (int) strtol(string, &p, 0);
	if (errno==ERANGE) {
		error("value out of range\n");
		return -1;
	}
	if (p==string) {
		error("invalid parameter value\n");
		return -1;
	}

	/* check for prefix */
	if (prefixes && *p) {
			error("sdf %d\n", *value);
		while (strcmp(p, prefixes->string)) {
			prefixes++;
			if (prefixes->string == NULL) {
				break;
			}
		}
		if (prefixes->string != NULL) {
			signed long long tmp;
			/* be sure we don't get an integer overflow during
			 * multiplication */
			tmp = (signed long long) tmp_value * prefixes->value;
			if ((tmp<(int)0x80000000)||(tmp>(int)~0x80000000)) {
				error("value including prefix out of range\n");
				return -1;
			}

			tmp_value = (int) tmp;
		} else {
			error("invalid prefix for numerical value\n");
			return -1;
		}
	}

	*value = tmp_value;

	return 0;
}

int get_argvalue(int priority, int argid, int* argp, int argc, char* argv[],
		 char* value)
{
	int tmpint;
	int ret;

	if (!arglist[argid].var) {
		return -1;
	}

	if (argv && value == &argv[*argp][2]) {
		/* Long parameter has no value provided - we assume 'true'
		 * for boolean options */
		if (arglist[argid].type == TBOOL) {
			value = boolvalues[0].string;
		}
	} else if (value && (arglist[argid].type == TNONE)) {
		error("no value expected\n");
		return -1;
	} else if (!value && (arglist[argid].type != TNONE))  {
		if (argp) {
			if (*argp < argc -1) {
				(*argp)++;
				if (argv && argv[*argp][0]!='-') {
					value = argv[*argp];
				}
			}
		}
		if (!value) {
			error("parameter needs value\n");
			return -1;
		}
	} 

	switch (arglist[argid].type) {
		case TNONE:	if (arglist[argid].set > priority)
					break;
				*((int*)arglist[argid].var) = 1;
			   	break;
		case TCLIP:
		case TCOLOR:
		case TINT:
		case TUINT:	switch (arglist[argid].type) {
					case TCLIP:
						ret = string2numvalue(value,
								&tmpint,
								clipvalues,
								multprefixes);
						break;
					case TCOLOR:
						ret = string2numvalue(value,
								&tmpint,
								colorvalues,
								multprefixes);
						break;
					default:
						ret = string2numvalue(value,
								&tmpint,
								NULL,
								multprefixes);
				}
				if (ret) {
					return -1;
				}
				if ((arglist[argid].type==TUINT)&&(tmpint<0)) {
					error("negative value not allowed for "
					      "this parameter\n");
					return -1;
				}
				if (arglist[argid].set > priority)
					break;
				*((int*)arglist[argid].var) = tmpint;
				break;
		case TSTRING:	if (arglist[argid].set > priority)
					break;
				if ((*((char**)arglist[argid].var) != NULL)
				    && (arglist[argid].set > 0)) {
					free(*((char**)arglist[argid].var));
				}
				*((char**)arglist[argid].var) = strdup(value);
				if (*((char**)arglist[argid].var) == NULL) {
					error_outofmemory();
					return -1;
				}
				break;
		case TBOOL:	if (string2numvalue(value, &tmpint,
						    boolvalues, NULL)) {
					return -1;
				}
				if ((tmpint < 0) || (tmpint > 1)) {
					error("invalid boolean value\n");
					return -1;
				}
				if (arglist[argid].set > priority)
					break;
				*((int*)arglist[argid].var) = tmpint;
				break;
		default:	error("unknown argument type\n");
				return -1;
	}

	if (arglist[argid].set < priority) 
		arglist[argid].set = priority;

	return 0;

}

int parse_args(int argc, char* argv[])
{
	int i;
	char* arg;
	char* value;
	int argid;
	int argcount = 1;

	if (argc < 1) {
		error("invalid argument count\n");
		return -1;
	}

	/* prepare argc and argv for our main application */
	/* the new argc is still unknown but the old value should be save */
	app.argv = malloc((argc+1)*sizeof(argv[0]));
	if (!app.argv) {
		error_outofmemory();
		return -1;
	}
	app.argc = 1;

	/* split arguments for applog and our main application */
	for (i=1; i<argc; i++)
	{
		value = NULL;
		if (argv[i][0]=='-') {
			if (argv[i][1]=='-') {
				arg = &argv[i][2];
				/* strip argument prefix */
				if (strstr(arg, config.argprefix)==arg) {
					arg += strlen(config.argprefix);
				} else {
					if (app.exe) {
						app.argv[app.argc]=argv[i];
						app.argc++;
						continue;
					}
				}

				if ((value = strchr(arg, '='))) {
					*value = '\0';
					if (*(value+1)) {
						value++;
					} 
				} else {
					/* mark a long option without provided
					 * value */
					value = &argv[i][2];
				}

				argid = get_argid(arg, 0);
			} else {
				if (app.exe) {
					app.argv[app.argc]=argv[i];
					app.argc++;
					continue;
				}
				arg = &argv[i][1];
				if (argv[i][2])
					return argcount;

				argid = get_argid(arg, 1);
			}

			if (argid<0)
				return(argcount);

			if (get_argvalue(2, argid, &i, argc, argv, value)) {
				return argcount;
			}

			argcount++;
		} else {
			if (app.exe) {
				app.argv[app.argc]=argv[i];
				app.argc++;
				continue;
			}
			arg = &argv[i][0];
			app.exe = arg;
		}

	}

	/* add executable name to new argv list and terminate new argv */
	app.argv[0] = app.exe;
	app.argv[app.argc] = NULL;

	if (app.exe == NULL)
		app.argc = 0;

	return 0;
}

#define LINEBUFSIZE	251

int parse_configline(char* line, char **appconfig_section)
{
	static int lineno;
	char* name;
	char* value;
	char* tmp;
	int argid;

	lineno++;

	/* remove comments and spaces */
	name = line + strspn(line, " \t");

	if ((strlen(name) == 0) || (*name == '#'))
		return 0;

	value = name + strcspn(name, " \t=#");

	*(value) = '\0';

	if (strlen(value+1) > 0)
		value = value + 1 + strspn(value + 1, " \t=");

	if (*value=='\"') {
		value++;
		tmp = strchr(value, '\"');
	} else if (*value=='\'') {
		value++;
		tmp = strchr(value, '\'');
	} else {
		tmp = value + strcspn(value, " \t#");
	}
	if (tmp == NULL) {
		warning("unterminated string in config file (line %d)\n",
			lineno);
		return -1;
	}

	*tmp = '\0';

	/* check if we change to a tagged section */
	if ((strlen(value)==0) && (strlen(name)>1) && (name[0] == '[')
		   && (name[strlen(name)-1] == ']') ) {
		name = name + 1;
		name[strlen(name) - 1] = '\0';
		if (*appconfig_section != NULL)
			free(*appconfig_section);
		*appconfig_section = strdup(name);
		if (!appconfig_section) {
			error_outofmemory();
			return -1;
		}
		return 0;
	}
	
	/* check if the section should be ignored */
	if (*appconfig_section != NULL) {
		if (config.configsection != NULL) {
			char* tmp;
			if (strchr(*appconfig_section, '/') == NULL) {
				tmp = strrchr(config.configsection, '/');
				if (tmp == NULL)
					tmp = config.configsection;
				else
					tmp = tmp + 1;
			} else {
				tmp = config.configsection;
			}
			if (strcmp(*appconfig_section, tmp)) {
				return 0;
			}
		} else {
			return 0;
		}
	}

	/* get the configuration */
	if ((argid = get_argid(name, 0)) >= 0) {
		if ((strlen(value) == 0) && (arglist[argid].type == TNONE)) {
			value = NULL;
		}
		get_argvalue(1, argid, NULL, 0, NULL, value);
	} else {
		warning("unknown parameter in config file (line %d)\n",
			lineno);
		return -1;
	}

	return 0;
}

int get_config(void)
{
	FILE* conffile = NULL;
	char *filename;
	char *appconfig_section = NULL;
	char linebuf[LINEBUFSIZE];

	/* open config file */
	if (config.custconfigfile == NULL) {
		unsigned int i;
		for (i=0 ; i < CONFIGSEARCHPATH_SIZE ; i++) {
			filename = get_longpath(configsearchpath[i]);

			if (filename == NULL)
				return -1;

			conffile = fopen(filename, "r");
			if (conffile != NULL)
				break;
		}
	} else {
		filename = get_longpath(config.custconfigfile);

		if (filename != NULL)
			conffile = fopen(filename, "r");

		if (conffile == NULL) {
			error("unable to open provided configuration file\n");
			return -1;
		}
	}
	
	if (conffile == NULL)
		return 0;

	/* Application executable name is active config section unless
	 * something else is provided later */
	if (config.configsection==NULL)
		config.configsection = app.exe;

	/* parse each line */
	config.configfile = filename;
	while (fgets(linebuf, LINEBUFSIZE, conffile) != NULL) {
		linebuf[strcspn(linebuf, "\n")] = '\0';
		if (parse_configline(linebuf, &appconfig_section))
			return -1;
	}

	if (appconfig_section)
		free(appconfig_section);

	fclose(conffile);

	return 0;
}

#define MAX_KEYWORDS	50
int check_for_disable_keywords(void)
{
	int i,j;
	int key;
	char* toksrc;
	char* tok[MAX_KEYWORDS];

	if (!config.disable_keywords)
		return 0;

	toksrc = strdup(config.disable_keywords);

	key = 0;
	tok[key] = strtok(toksrc, ", ");
	while (tok[key] && (key + 1 < MAX_KEYWORDS)){
		tok[++key] = strtok(NULL, ", ");
	}

	for (i=1; (i<app.argc) && (config.disable==0); i++) {
		for (j=0; (j<key) && (config.disable==0); j++) {
			if (strstr(app.argv[i], tok[j])) {
				config.disable = 1;
			}
		}

	}

	free(toksrc);

	return 0;
}
