/*-
* Copyright (C) 2005-2007 Nick Withers. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include "downtime.h"

#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include <strings.h>
#include <sys/param.h>
#include <sys/stat.h>

#ifdef BSD
#include <sysexits.h>
#endif

#define	VERSION_TAG	"version"
#define	TIMEOUT_TAG	"timeout"
#define	TIMEOUT_UNIT_TAG	"timeout_unit"
#define	SHUTDOWN_TYPE_TAG	"shutdown_type"
#define	MAIN_WINDOW_ON_TOP_TAG	"main_on_top"
#define	PROGRESS_WINDOW_ON_TOP_TAG	"progress_on_top"

#define	MINUTES_VALUE	"minutes"
#define	HOURS_VALUE	"hours"
#define	DAYS_VALUE	"days"
#define	POWEROFF_VALUE	"power-off"
#define	RESTART_VALUE	"restart"
#define	HALT_VALUE	"halt"
#define	SINGLE_USER_VALUE	"single user"
#define	TRUE_VALUE	"TRUE"
#define	FALSE_VALUE	"FALSE"

typedef struct
{
	char *value_string;
	int value;
} value_mapping;

#define	TIMEOUT_UNIT_PARAM_MAPPINGS	{ \
        {"minutes", MINUTES}, \
	{"hours", HOURS}, \
	{"days", DAYS}}
#define	SHUTDOWN_TYPE_PARAM_MAPPINGS	{ \
        {"power-off", POWEROFF}, \
	{"restart", RESTART}, \
	{"halt", HALT}, \
	{"single user", SINGLE_USER}}
#define	TRUTH_PARAM_MAPPINGS	{ \
        {"TRUE", TRUE}, \
        {"FALSE", FALSE}}

static char * get_param(char *line);
static char * get_value(char *line);

/* Returns NULL on failure, with errno == 0 if there was no passwd entry for the user */
char *
get_config_path(void)
{
	char *user_home, *config_path;
	struct passwd *pw;

	errno = 0; /* Have to set errno = 0 in order to be able to distinguish between there being no passwd entry for the user and there being an error */

	if ((pw = getpwuid(geteuid())) == NULL)
		return (NULL);

	if ((user_home = cat_strings(pw->pw_dir, DIR_SEPARATOR_STRING)) == NULL || (config_path = cat_strings(user_home, CONFIG_FILE_NAME)) == NULL) /* Note that if pw->pw_dir == NULL, the user's home directory will be DIR_SEPARATOR */
	{
		free(user_home); /* If we're here, either user_home == NULL, in which case this free() is a no-op, or it was allocated (and this free() is necessary) but config_path == NULL (and doesn't need to be free()d) */

		return (NULL);
	}

	free(user_home);

	return (config_path);
}

static char *
get_param(char *line)
{
	char *temp, *param;

	if ((line = index(line, '<')) != NULL) /* Find the first occurrence of '<' in line, then truncate the '<' and everything before it */
		line++;
	else
		return (NULL);

	if ((temp = (char *) malloc(sizeof (char) * (strlen(line) + 1))) == NULL)
		show_error_dialog(EX_OSERR, "malloc() returned NULL in get_param(). Error: \"%s\" (%d)", strerror(errno), errno);

	strcpy(temp, line);

	if ((param = index(temp, '>')) != NULL) /* Find the first occurrence of '>' in param, then truncate the '>' and everything after it from param */
		*param = '\0';

	if ((param = (char *) malloc(sizeof (char) * (strlen(temp) + 1))) == NULL)
		show_error_dialog(EX_OSERR, "malloc() returned NULL in get_param(). Error: \"%s\" (%d)", strerror(errno), errno);

	strcpy(param, temp);

	free(temp);

	return (param);
}

static char *
get_value(char *line)
{
	char *temp;
	char *value;

	if ((line = index(line, '>')) != NULL)
		line++;
	else
		return (NULL);

	if ((temp = (char *) malloc(sizeof (char) * (strlen(line) + 1))) == NULL)
		show_error_dialog(EX_OSERR, "malloc() returned NULL in get_value(). Error: \"%s\" (%d)", strerror(errno), errno);

	strcpy(temp, line);

	if ((value = index(temp, '<')) != NULL)
		*value = '\0';

	if ((value = (char *) malloc(sizeof (char) * (strlen(line) + 1))) == NULL)
		show_error_dialog(EX_OSERR, "malloc() returned NULL in get_value(). Error: \"%s\" (%d)", strerror(errno), errno);

	strcpy(value, temp);

	free(temp);

	return (value);
}

/* Loads settings from the file at path config_path into the structure pointed to by specs */
char
load_user_specs(user_specs_t *specs, char *config_path)
{
	FILE *config;
	user_specs_t new_specs;
	char *line, *param, *value, c, *endptr;
	size_t line_number;
	char found;

	new_specs = *specs; /* We're going to update new_specs, then set *specs = new_specs iff we're able to read the configuration file properly */

	if ((config = fopen(config_path, "r")) == NULL)
		return (FALSE); /* fopen() will have set errno as appropriate */

	new_specs.config_path = config_path;

	line = NULL;
	line_number = 1; /* We start reading from line 1 */

	while ((c = getc(config)) != EOF)
	{
		found = FALSE;

		if (c != '\n') /* If we're not at the end of a line... */
		{
			if (!isprint(c)) /* Found a non-printable character in the file */
			{
				show_stderr("Found a non-printable character in the configuration file \"%s\"", config_path);

				fclose(config);

				errno = EFTYPE; /* "Inappropriate file type or format" */

				return (FALSE);
			}

			if (!cat_strchar(&line, c))
				show_error_dialog(EX_OSERR, "Couldn't store configuration file input. Error: \"%s\" (%d)", strerror(errno), errno);
		}
		else if (((param = get_param(line)) != NULL) && ((value = get_value(line)) != NULL))
		{
			if (strcmp(param, VERSION_TAG) == 0) /* Do nothing... The version tag is there for possible future use */
				found = TRUE;
			else if (strcmp(param, TIMEOUT_TAG) == 0)
			{
				new_specs.timeout = (unsigned long) strtol(value, &endptr, 10);

				if (!(*value != '\0' && *endptr == '\0'))
					show_stderr("An unusable parameter - \"%s\" - was found for the option \"%s\" on line %lu of the saved configuration file \"%s\". This might mean that this instance of DownTime is out of date, the file itself is out of date, or that the file is corrupt", value, param, (unsigned long) line_number, config_path);

				found = TRUE;
			}
			else if (strcmp(param, TIMEOUT_UNIT_TAG) == 0)
			{
				value_mapping timeout_mappings[] = TIMEOUT_UNIT_PARAM_MAPPINGS;
				int i;

				for (i = 0; i < (sizeof (timeout_mappings) / sizeof (*timeout_mappings)); i++)
				{
					if (strcasecmp(value, (*(timeout_mappings + i)).value_string) == 0)
					{
						new_specs.timeout_unit = (*(timeout_mappings + i)).value;

						found = TRUE;
						break;
					}
				}
			}
			else if (strcmp(param, SHUTDOWN_TYPE_TAG) == 0)
			{
				value_mapping shutdown_type_mappings[] = SHUTDOWN_TYPE_PARAM_MAPPINGS;
				int i;

				for (i = 0; i < (sizeof (shutdown_type_mappings) / sizeof (*shutdown_type_mappings)); i++)
				{
					if (strcasecmp(value, (*(shutdown_type_mappings + i)).value_string) == 0)
					{
						new_specs.shutdown_type = (*(shutdown_type_mappings + i)).value;

						found = TRUE;
						break;
					}
				}
			}
			else if (strcmp(param, MAIN_WINDOW_ON_TOP_TAG) == 0)
			{
				value_mapping truth_mappings[] = TRUTH_PARAM_MAPPINGS;
				int i;

				for (i = 0; i < (sizeof (truth_mappings) / sizeof (*truth_mappings)); i++)
				{
					if (strcasecmp(value, (*(truth_mappings + i)).value_string) == 0)
					{
						new_specs.main_window_always_on_top = (*(truth_mappings + i)).value;

						found = TRUE;
						break;
					}
				}
			}
			else if (strcmp(param, PROGRESS_WINDOW_ON_TOP_TAG) == 0)
			{
				value_mapping truth_mappings[] = TRUTH_PARAM_MAPPINGS;
				int i;

				for (i = 0; i < (sizeof (truth_mappings) / sizeof (*truth_mappings)); i++)
				{
					if (strcasecmp(value, (*(truth_mappings + i)).value_string) == 0)
					{
						new_specs.progress_window_always_on_top = (*(truth_mappings + i)).value;

						found = TRUE;
						break;
					}
				}
			}
			else
			{
				show_stderr("An unrecognised option - \"%s\" - was found in the saved configuration file \"%s\". This might mean that this instance of DownTime is out of date, the file itself is out of date, or that the file is corrupt", param, config_path);

				found = TRUE;
			}

			if (!found)
				show_stderr("An unrecognised parameter - \"%s\" - was found for the option \"%s\" on line %lu of the saved configuration file \"%s\". This might mean that this instance of DownTime is out of date, the file itself is out of date, or that the file is corrupt", value, param, (unsigned long) line_number, config_path);

			line_number++;

			free(param);
			free(value);
			free(line);
			line = NULL;
		}
		else
		{
			line_number++;

			free(param); /* If the above else if conditional isn't executed, we can be sure that either param == NULL, or param points to a valid location that needs to be free()d and value == NULL. In the first case, this free() will be a no-op, in the latter it will be necessary and a free() of value would be a no-op (so we don't do it) */
			free(line);
			line = NULL;
		}
	}

	fclose(config);

	*specs = new_specs;

	return (TRUE);
}

void
save_user_specs(user_specs_t *specs, char *config_path)
{
	FILE *config;

	if ((config = fopen(config_path, "w")) == NULL)
	{
		show_warning_dialog("Unable to open \"%s\" for writing. Error \"%s\" (%d). Settings will not be saved", config_path, strerror(errno), errno);

		return;
	}

	fprintf(config, "<%s>%s</%s>\n", VERSION_TAG, VERSION, VERSION_TAG);
	fprintf(config, "<%s>%lu</%s>\n", TIMEOUT_TAG, specs->timeout, TIMEOUT_TAG);
	fprintf(config, "<%s>", TIMEOUT_UNIT_TAG);

	switch (specs->timeout_unit)
	{
		case MINUTES:
			fprintf(config, "%s", MINUTES_VALUE);

			break;
		case HOURS:
			fprintf(config, "%s", HOURS_VALUE);

			break;
		case DAYS:
			fprintf(config, "%s", DAYS_VALUE);

			break;
	}

	fprintf(config, "</%s>\n", TIMEOUT_UNIT_TAG);
	fprintf(config, "<%s>", SHUTDOWN_TYPE_TAG);

	switch (specs->shutdown_type)
	{
		case POWEROFF:
			fprintf(config, "%s", POWEROFF_VALUE);

			break;
		case RESTART:
			fprintf(config, "%s", RESTART_VALUE);

			break;
		case HALT:
			fprintf(config, "%s", HALT_VALUE);

			break;
		case SINGLE_USER:
			fprintf(config, "%s", SINGLE_USER_VALUE);

			break;
	}

	fprintf(config, "</%s>\n", SHUTDOWN_TYPE_TAG);

	fprintf(config, "<%s>%s</%s>\n", MAIN_WINDOW_ON_TOP_TAG, (specs->main_window_always_on_top ? TRUE_VALUE : FALSE_VALUE), MAIN_WINDOW_ON_TOP_TAG);
	fprintf(config, "<%s>%s</%s>\n", PROGRESS_WINDOW_ON_TOP_TAG, (specs->progress_window_always_on_top ? TRUE_VALUE : FALSE_VALUE), PROGRESS_WINDOW_ON_TOP_TAG);

	fclose(config);
}


syntax highlighted by Code2HTML, v. 0.9.1