/*-
* 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 <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>

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

char
add_to_unique_string_array(char ***string_array, size_t *array_length, char *string_to_add)
{
	if ((*array_length == 0 || !is_in_string_array(*string_array, *array_length, string_to_add)) && add_to_string_array(string_array, array_length, string_to_add))
		return (TRUE);

	return (FALSE);
}

/* 
* Adds the string_to_add to a string array pointed to by string_array of
* length *array_length.
*
* NOTE:
*  - *string_array holds a pointer to malloc()-ed space and should be freed
*    by the calling function when it is no longer required.
*  - add_to_string_array() attempts to free() the memory pointed to by
*    *string_array when *array_length > 0. For this reason, if the string
*    array pointed to by *string_array has been touched by anything other
*    than add_to_string_array(), please ensure that the space it points to was
*    obtained through malloc()
*/
char
add_to_string_array(char ***string_array, size_t *array_length, char *string_to_add)
{
	return (array_add((void **) string_array, array_length, (void *) &string_to_add, sizeof(string_to_add)));
}

/*
* Adds *add_me to an array of length *length with elements of elem_size pointed to by array
*
* NOTE: The memory referenced by *array should be free()d when no longer required
*/
char
array_add(void **array, size_t *length, const void *add_me, size_t elem_size)
{
	if (*length == 0)
		*array = NULL;

	if ((*array = realloc(*array, (*length + 1) * elem_size)) == NULL)
		return (FALSE);

	memmove((*((char **) array) + *length * elem_size), add_me, elem_size);

	(*length)++;

	return (TRUE);
}

/*
*
* Appends the character append_me to the malloc()-ed original_string
* string, which may either be NULL-terminated or be a pointer to NULL
*
* NOTE:
*  - *original_string holds a pointer to malloc()-ed space and should be freed 
*    by the calling function when it is no longer required.
*  - cat_strchar() attempts to free() the memory pointed to by
*    original_string when it doesn't point to NULL. For this reason, if the string
*    array pointed to by original_string has been touched by anything other
*    than add_to_string_array(), please ensure that the space it points to was
*    obtained through malloc()
*
*/
char
cat_strchar(char **original_string, const char append_me)
{
	size_t string_length;

	if (*original_string == NULL)
		string_length = 0;
	else
		string_length = strlen(*original_string);

	if (!array_add((void **) original_string, &string_length, &append_me, sizeof (append_me)) || !array_add((void **) original_string, &string_length, "", sizeof (char))) /* Append the NULL character, too */
		return (FALSE);

	return (TRUE);
}

/*
*
* Appends the string append_me to original_string in a new, malloc()-ed, string
*
* NOTE:
*  - new_string holds a pointer to malloc()ed space and should be freed 
*    by the calling function when it is no longer required.
*  - original_string is NOT free()d in cat_strings. Therefore, code like
*     string = cat_strings(string, "-bam");
*    where "string" is a pointer to malloc()ed space, causes a memork leak
*  - trying to append a blank string to a blank string is not treated as a
*    special case: You'll get a blank string in malloc()ed space back.
*    This is so that we can fulfill our promise to return a pointer to
*    malloc()ed space - after all, there will be free()s performed on the
*    return values of this function!
*/
char *
cat_strings(const char *original_string, const char *append_me)
{
	char *new_string;
	size_t string_length;

	string_length = ((original_string == NULL) ? 0 : strlen(original_string)) + ((append_me == NULL) ? 0 : strlen(append_me));

	if ((new_string = (char *) malloc(sizeof(char) * (string_length + 1))) == NULL)
		return (NULL);

	if (original_string != NULL)
	{
		strcpy(new_string, original_string);
		strcat(new_string, append_me);
	}
	else
		strcpy(new_string, append_me);

	*(new_string + string_length) = '\0'; /* NULL terminate the string */

	return (new_string);
}

char *
find_executable_in_path(char *filename)
{
	size_t i, path_len, path_elements, path_offset;
	char **paths, *path, *location;

	if ((path = cat_strings(geteuid() == getuid() ? getenv("PATH") : NULL, _PATH_STDPATH)) == NULL) /* Check if the program is running setuid as another user. If it is, don't add user path environment variables for security reasons */
		show_error_dialog(EX_OSERR, "Couldn't store environment PATH. Error: \"%s\" (%d)", strerror(errno), errno);

	path_len = strlen(path);

	for (i = 0; i < path_len; i++)	/* Replace PATH_SEPARATOR characters in path with NUL character */
	{
		if (*(path + i) == PATH_SEPARATOR)
			*(path + i) = '\0';
	}

	path_offset = 0;
	path_elements = 0;

	while (path_offset < path_len)	/* Add each substring of path to paths */
	{
		char *new_path_element = malloc((strlen(path + path_offset) + 1) * sizeof (char));	/* Copy the string to be added into a freshly malloc()ed string. This allows us to free() path, but note that we must now free() each element in paths */
		strcpy(new_path_element, (path + path_offset));

		if (!add_to_unique_string_array(&paths, &path_elements, new_path_element))	/* If new_path_element isn't added to the array, free() it */
			free(new_path_element);

		path_offset += strlen(path + path_offset) + 1;	/* Increment path_offset so that (path + path_offset) will point past the terminating NUL character of the current string on the next run (if any) */
	}

	free(path);

	for (i = 0; i < path_elements; i++) /* Iterate through each element of paths */
	{
		location = malloc(sizeof (char *) * (strlen(*(paths + i)) + strlen(filename) + 2)); /* + 2: 1 for '/', 1 for '\0' */
		strcpy(location, *(paths + i));
		strcat(location, "/");
		strcat(location, filename);

		if (access(location, F_OK) == 0 && access(location, X_OK) == 0)
		{
			size_t j;

			for (j = 0; j < path_elements; j++)
				free(*(paths + j));

			free(paths);

			return (location);
		}

		free(location);
	}

	for (i = 0; i < path_elements; i++)
		free(*(paths + i));

	free(paths);

	return (NULL);
}

char *
get_filename_from_path(const char *path)
{
	char *filename;

	if ((filename = strrchr(path, DIR_SEPARATOR)) != NULL)
		return (++filename);
	else
		return (NULL);
}

char *
get_shutdown_time_left_string(shutdown_t *shutdown)
{
	#define	SECONDS_IN_DAY	86400 /* (60 seconds / minute) * (60 minutes / hour) * (24 hours / day) */

	struct tm *time_to_shutdown;
	time_t seconds_to_shutdown;
	char *str_time_to_shutdown;

	seconds_to_shutdown = shutdown->shutdown_time - time(NULL);
	time_to_shutdown = gmtime(&seconds_to_shutdown);

	if ((time_to_shutdown->tm_year > 70) || (time_to_shutdown->tm_mon > 0) || (time_to_shutdown->tm_mday > 1))
		pasprintf(&str_time_to_shutdown, "%ld day%s, %d:%2.2d:%2.2d", (long) (seconds_to_shutdown / SECONDS_IN_DAY), (long) (seconds_to_shutdown / SECONDS_IN_DAY) > 1 ? "s" : "", time_to_shutdown->tm_hour, time_to_shutdown->tm_min, time_to_shutdown->tm_sec);
	else if (time_to_shutdown->tm_hour > 0)
		pasprintf(&str_time_to_shutdown, "%d:%2.2d:%2.2d", time_to_shutdown->tm_hour, time_to_shutdown->tm_min, time_to_shutdown->tm_sec);
	else
		pasprintf(&str_time_to_shutdown, "%d:%2.2d", time_to_shutdown->tm_min, time_to_shutdown->tm_sec);

	if (str_time_to_shutdown == NULL)
		show_error_dialog(EX_OSERR, "pasprintf() returned -1 in get_shutdown_time_left_string(). Error: \"%s\" (%d)", strerror(errno), errno);

	return (str_time_to_shutdown);

	#undef	SECONDS_IN_DAY
}

void
init_user_specs(user_specs_t *specs)
{
	bzero(specs, sizeof (*specs));

	specs->timeout = DEFAULT_TIMEOUT;
	specs->timeout_unit = DEFAULT_TIMEOUT_UNIT;
	specs->shutdown_type = DEFAULT_SHUTDOWN_TYPE;
	specs->remember = DEFAULT_REMEMBER;
}

char
is_in_string_array(char **string_array, size_t array_length, char *string_to_add)
{
	size_t i;

	for (i = 0; i < array_length; i++)
	{
		if (strcmp(string_to_add, *(string_array + i)) == 0)
			return (TRUE);
	}

	return (FALSE);
}

/* Returns the maximum number of minutes until a scheduled shutdown. So, for instance, if a shutdown was to occur in 6 minutes and 25 seconds, this would return 7 */
long int
max_minutes_to_shutdown(shutdown_t *shutdown)
{
	double minutes;

	minutes = ((double) (shutdown->shutdown_time - time(NULL))) / 60;

	if (minutes > (long int) minutes) /* Round minutes up */
		minutes = (long int) minutes + 1;

	return ((long int) minutes);
}

/*
* Portable asprintf(). Should behave identically to asprintf() on FreeBSD 6.0-RELEASE
*/

int
pasprintf(char **ret, const char *fmt, ...)
{
	va_list ap;
	size_t length;

	va_start(ap, fmt);

	length = vsnprintf(NULL, 0, fmt, ap);

	if ((*ret = malloc((length + 1) * sizeof (char))) == NULL)	/* + 1 for the trailing NUL character */
	{
		va_end(ap);

		ret = NULL;	/* As with pasprintf() on FreeBSD, set ret = NULL in case of error*/
		return (-1);	/* As with pasprintf() on FreeBSD, return -1 in case of error. errno will already have been set by malloc() */
	}

	vsnprintf(*ret, length + 1, fmt, ap);

	va_end(ap);

	return (length);
}

/*
* Portable vasprintf(). Should behave identically to vasprintf() on FreeBSD 6.0-RELEASE
*/

int
pvasprintf(char **ret, const char *fmt, va_list ap)
{
	size_t length;

	length = vsnprintf(NULL, 0, fmt, ap);

	if ((*ret = malloc((length + 1) * sizeof (char))) == NULL)	/* + 1 for the trailing NUL character */
	{
		ret = NULL;	/* As with pasprintf() on FreeBSD, set ret = NULL in case of error*/
		return (-1);	/* As with pasprintf() on FreeBSD, return -1 in case of error. errno will already have been set by malloc() */
	}

	vsnprintf(*ret, length + 1, fmt, ap);

	return (length);
}

/*
*
* Trim white-space characters from the beginning and the end of a
* NULL-terminated string. A pointer to malloc()ed space containing the trimmed
* string is returned which should be freed by the calling function when no
* longer required
*
*/

char *
trim_white(const char *string)
{
	char *trimmed, *temp;
	size_t i;

	while ((*string == ' ' || *string == '\t' || *string == '\n') && *string != '\0')
		string++;

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

	strcpy(temp, string);

	for (i = strlen(temp); *(temp + i) == '\0' || isspace(*(temp + i)); i--)
		;

	if (i < strlen(temp))
		*(temp + i + 1) = '\0';

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

	strcpy(trimmed, temp);

	free(temp);

	return (trimmed);
}


syntax highlighted by Code2HTML, v. 0.9.1