/*-
 * Copyright (c) 2005 Andrey Simonenko
 * 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 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 THE 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 "config.h"

#ifndef lint
static const char rcsid[] ATTR_UNUSED =
  "@(#)$Id: ipastat_time.c,v 1.1.4.3 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <sys/queue.h>

#include "ipa_mod.h"

#include "dlapi.h"
#include "confcommon.h"
#include "memfunc.h"

#include "ipastat_time.h"

#include "ipastat_log.h"
#include "ipastat_main.h"

/* List of all opt_tint structures. */
struct opt_tint_list opt_tint_list = STAILQ_HEAD_INITIALIZER(opt_tint_list);

static struct opt_tint opt_tint_default;	/* Default opt_tint. */

/* Regular expressions for checking time interval. */
#define PAT_TINT_FORM "^[^-]+(-[^-]+)?$"
#define PAT_TINT_PART "^\
(\
[[:digit:]]{4,}(\\.([[:digit:]]{1,2}|[[:alpha:]]{3}))?(\\.[[:digit:]]{1,2})?|\
([[:digit:]]{1,2}|[[:alpha:]]{3})?(\\.[[:digit:]]{1,2})?\
)?\
(\
/[[:digit:]]{1,2}(:[[:digit:]]{1,2})?(:[[:digit:]]{1,2})?\
)?$"

static regex_t	reg_tint_form;		/* Compiled PAT_TINT_FORM. */
static regex_t	reg_tint_part;		/* Compiled PAT_TINT_PART. */

static ipa_tm	curdate;		/* Current date. */

static int const last_mday_arr[] =
    /*   Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
    {  0, 31,  0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static const char month_name[][3] = {
    "jan", "feb", "mar", "apr", "may", "jun",
    "jul", "aug", "sep", "oct", "nov", "dec"
};

/*
 * Initialized various local date related variables.
 */
int
init_time_data(void)
{
	time_t t;

	/* Get current date. */
	if (time(&t) == (time_t)-1) {
		logmsg(IPA_LOG_ERR, "init_time_data: time failed");
		return -1;
	}
	if (localtime_r(&t, (struct tm *)&curdate) == NULL) {
		logmsg(IPA_LOG_ERR, "init_time_data: localtime_r failed");
		return -1;
	}
	curdate.tm_year += 1900;
	curdate.tm_mon++;

	return 0;
}

/*
 * Compare two ipa_tm tm1 and tm2 structures:
 * if (tm1 == tm2)
 *	return 0;
 * if (tm1 > tm2)
 *	return 1;
 * if (tm1 < tm2)
 *	return -1;
 */
static int
cmp_ipa_tm(const ipa_tm *tm1, const ipa_tm *tm2)
{
	/*
	 * We don't use mktime(3), because there can be problem
	 * with time zone and summer time.
	 */
	if (tm1->tm_year > tm2->tm_year)
		return 1;
	if (tm1->tm_year == tm2->tm_year) {
		if (tm1->tm_mon > tm2->tm_mon)
			return 1;
		if (tm1->tm_mon == tm2->tm_mon) {
			if (tm1->tm_mday > tm2->tm_mday)
				return 1;
			if (tm1->tm_mday == tm2->tm_mday) {
				if (tm1->tm_hour > tm2->tm_hour)
					return 1;
				if (tm1->tm_hour == tm2->tm_hour) {
					if (tm1->tm_min > tm2->tm_min)
						return 1;
					if (tm1->tm_min == tm2->tm_min) {
						if (tm1->tm_sec > tm2->tm_sec)
							return 1;
						if (tm1->tm_sec == tm2->tm_sec)
							return 0;
					}
				}
			}
		}
	}
	return -1;
}

/*
 * Return last month day in the given year/mon.
 */
static int
last_mday(int year, int mon)
{
	if (mon == 2) /* February */
		return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ?
		    29 : 28;

	return last_mday_arr[mon];
}

/*
 * Output error message, that month, day, hours, minutes or seconds are
 * specified incorrect in some part of time interval.
 */
static void
wrong_tint_part(const char *what, int right)
{
	logmsgx(IPA_LOG_ERR, "parse_tint_part: wrong format of time interval's %s part: incorrect %s value",
	    right ? "right" : "left", what);
}

static int
parse_tint_part(char *s, ipa_tm *tm, int right)
{
	int	n, val1, val2, val3;
	char	*ptr;

	/*
	 * Initial value for time in the left part, for the right
	 * part these values are completed at the end of this function.
	 */
	tm->tm_hour = tm->tm_min = tm->tm_sec = 0;

	if (*s != '/') {
		/* Date is specified. */
		if (*s == '.') {
			/* .DD */
			errno = 0;
			if (sscanf(++s, "%u", &tm->tm_mday) != 1) {
				logmsgx(IPA_LOG_ERR, "parse_tint_part: sscanf(\"%s\", %%u): failed", s);
				return -1;
			}
		} else {
			/* Check if there is named month. */
			for (ptr = s; *ptr != '\0'; ++ptr)
				if (isalpha(*ptr)) {
					for (n = 0; n < 12; ++n)
						if (strncasecmp(ptr, month_name[n], 3) == 0) {
							errno = 0;
							if (sprintf(ptr, "%02d", n + 1) != 2) {
								logmsgx(IPA_LOG_ERR, "parse_tint_part: sprintf failed");
								return -1;
							}
							ptr += 2;
							do {
								*ptr = *(ptr + 1);
							} while (*++ptr != '\0');
							break;
						}
					if (n == 12) {
						logmsgx(IPA_LOG_ERR, "parse_tint_part: wrong format of time interval: unknown month name");
						return -1;
					}
					break;
				}

			errno = 0;
			if ( (n = sscanf(s, "%u.%u.%u", &val1, &val2, &val3)) < 1) {
				logmsgx(IPA_LOG_ERR, "parse_tint_part: sscanf(\"%s\", %%u.%%u.%%u) failed",
				    s);
				return -1;
			}
			if ( (ptr = strchr(s, '.')) == NULL)
				if ( (ptr = strchr(s, '/')) == NULL)
					ptr = strchr(s, '\0');
			switch (n) {
			case 3:
				/* YYYY.MM.DD */
				tm->tm_year = val1;
				tm->tm_mon = val2;
				tm->tm_mday = val3;
				break;
			case 2:
				if (ptr - s >= 4) {
					/* YYYY.MM */
					tm->tm_year = val1;
					tm->tm_mon = val2;
					tm->tm_mday = right ? last_mday(val1, val2) : 1;
				} else {
					/* MM.DD */
					tm->tm_mon = val1;
					tm->tm_mday = val2;
				}
				break;
			default: /* 1 */
				if (ptr - s >= 4) {
					/* YYYY */
					tm->tm_year = val1;
					if (right) {
						tm->tm_mon = 12;
						tm->tm_mday = last_mday(val1, 12);
					} else
						tm->tm_mon = tm->tm_mday = 1;
				} else {
					/* MM */
					tm->tm_mon = val1;
					tm->tm_mday = right ? last_mday(tm->tm_year, val1) : 1;
				}
			}
		}
		if (tm->tm_mon == 0 || tm->tm_mon > MONTHES_IN_YEAR) {
			logmsgx(IPA_LOG_ERR, "parse_tint_part: wrong month %d", tm->tm_mon);
			return -1;
		}
		if (tm->tm_mday == 0 || tm->tm_mday > 31) {
			logmsgx(IPA_LOG_ERR, "parse_tint_part: wrong month day %d", tm->tm_mday);
			return -1;
		}
	}

	n = 0;
	if ( (s = strchr(s, '/')) != NULL) {
		/* Time is specified. */
		errno = 0;
		if ( (n = sscanf(++s, "%u:%u:%u", &val1, &val2, &val3)) < 1) {
			logmsgx(IPA_LOG_ERR, "parse_tint_part: sscanf(\"%s\", %%u:%%u:%%u) failed",
			    s);
			return -1;
		}
		if (n >= 1)
			tm->tm_hour = val1;	/* hh */
		if (n >= 2)
			tm->tm_min = val2;	/* hh:mm */
		if (n == 3)
			tm->tm_sec = val3;	/* hh:mm:ss */

		/* Validate month, day, hours, minutes and seconds. */
		if (tm->tm_mon == 0 || tm->tm_mon > 12) {
			wrong_tint_part("month", right);
			return -1;
		}
		if (tm->tm_mday == 0 || tm->tm_mday > last_mday(tm->tm_year, tm->tm_mon)) {
			wrong_tint_part("day", right);
			return -1;
		}
		if (tm->tm_hour > 23 && !(tm->tm_hour == HOURS_IN_DAY && tm->tm_min == 0 && tm->tm_sec == 0)) {
			wrong_tint_part("hours", right);
			return -1;
		}
		if (tm->tm_min > 59) {
			wrong_tint_part("minutes", right);
			return -1;
		}
		if (tm->tm_sec > 59) {
			wrong_tint_part("seconds", right);
			return -1;
		}
	}

	/* Complete value of time for the right part, if needed. */
	if (right)
		switch (n) {
		case 2:
			/* hh:mm */
			if (++tm->tm_min > 59)
				tm->tm_min = 0;
			else
				break;
			/* FALLTHROUGH */
		case 1:
			/* hh */
			if (++tm->tm_hour <= 23)
				break;
			/* FALLTHROUGH */
		case 0:
			tm->tm_hour = HOURS_IN_DAY;
		}

	return 0;
}

/*
 * Add optional time interval (-q -i|I <name>) and parse this
 * interval.
 */
int
add_opt_tint(char *tint_str, int exact)
{
	static int inited = 0;

	char	*right_part;
	struct tm *tm1, *tm2;
	struct opt_tint *opt_tint;

	if (!inited) {
		/* Build regular expressions. */
		if ( (re_errcode = regcomp(&reg_tint_form, PAT_TINT_FORM, REG_EXTENDED|REG_NOSUB)) != 0) {
			re_form_errbuf();
			logmsgx(IPA_LOG_ERR, "add_opt_tint: regcomp(%s): %s", PAT_TINT_FORM, re_errbuf);
			return -1;
		}
		if ( (re_errcode = regcomp(&reg_tint_part, PAT_TINT_PART, REG_EXTENDED|REG_NOSUB)) != 0) {
			re_form_errbuf();
			logmsgx(IPA_LOG_ERR, "add_opt_tint: regcomp(%s): %s", PAT_TINT_PART, re_errbuf);
			return -1;
		}

		inited = 1;
	}

	/* Validate time interval format. */
	if (regexec(&reg_tint_form, tint_str, 0, (regmatch_t *)NULL, 0) != 0) {
		logmsgx(IPA_LOG_ERR, "add_opt_tint: wrong format of time interval \"%s\"",
		    tint_str);
		return -1;
	}

	/* Split time interval into parts. */
	if ( (right_part = strchr(tint_str, '-')) != NULL)
		/* Split time interval into left and right parts. */
		*right_part++ = '\0';
	else
		/* Left and right parts are the same as whole time interval. */
		right_part = tint_str;

	if (regexec(&reg_tint_part, tint_str, 0, (regmatch_t *)NULL, 0) != 0) {
		logmsgx(IPA_LOG_ERR, "add_opt_tint: cannot recognize format of time interval's left part \"%s\"",
		    tint_str);
		return -1;
	}
	if (right_part != tint_str)
		if (regexec(&reg_tint_part, right_part, 0, (regmatch_t *)NULL, 0) != 0) {
			logmsgx(IPA_LOG_ERR, "add_opt_tint: cannot recognize format of time interval's right part \"%s\"",
			    right_part);
			return -1;
		}

	if ( (opt_tint = mem_malloc(sizeof *opt_tint, m_anon)) == NULL) {
		logmsgx(IPA_LOG_ERR, "add_opt_tint: mem_malloc failed");
		return -1;
	}

	opt_tint->tm1 = opt_tint->tm2 = curdate;

	tm1 = &opt_tint->tm1;
	tm2 = &opt_tint->tm2;
	if (parse_tint_part(tint_str, tm1, 0) < 0 ||
	    parse_tint_part(right_part, tm2, 1) < 0)
		return -1;

	if (cmp_ipa_tm(&opt_tint->tm1, &opt_tint->tm2) > 0) {
		logmsgx(IPA_LOG_ERR, "add_opt_tint: first timestamp (%d.%02d.%02d/%02d:%02d:%02d) should be less than or equal to second timestamp (%d.%02d.%02d/%02d:%02d:%02d)",
		    tm1->tm_year, tm1->tm_mon, tm1->tm_mday,
		    tm1->tm_hour, tm1->tm_min, tm1->tm_sec,
		    tm2->tm_year, tm2->tm_mon, tm2->tm_mday,
		    tm2->tm_hour, tm2->tm_min, tm2->tm_sec);
		return -1;
	}

	opt_tint->exact = exact;

	STAILQ_INSERT_TAIL(&opt_tint_list, opt_tint, link);

	return 0;
}

/*
 * Release memory previously allocated by add_opt_tint().
 */
void
free_opt_tint_list(void)
{
	struct opt_tint *opt_tint, *opt_tint_next;

	if (!STAILQ_EMPTY(&opt_tint_list) &&
	    STAILQ_FIRST(&opt_tint_list) != &opt_tint_default) {
		for (opt_tint = STAILQ_FIRST(&opt_tint_list); opt_tint != NULL; opt_tint = opt_tint_next) {
			opt_tint_next = STAILQ_NEXT(opt_tint, link);
			mem_free(opt_tint, m_anon);
		}
		regfree(&reg_tint_form);
		regfree(&reg_tint_part);
	}
}

/*
 * If there are not any -i options, than create default one,
 * which is equal to the current month.
 */
void
init_opt_tint_default(void)
{
	ipa_tm *tm1, *tm2;

	tm1 = &opt_tint_default.tm1;
	tm2 = &opt_tint_default.tm2;

	tm1->tm_year = tm2->tm_year = curdate.tm_year;
	tm1->tm_mon = tm2->tm_mon = curdate.tm_mon;
	tm1->tm_mday = 1;
	tm2->tm_mday = last_mday(curdate.tm_year, curdate.tm_mon);
	tm1->tm_hour = tm1->tm_min = tm1->tm_sec = tm2->tm_min = tm2->tm_sec = 0;
	tm2->tm_hour = HOURS_IN_DAY;
	STAILQ_INSERT_HEAD(&opt_tint_list, &opt_tint_default, link);
}


syntax highlighted by Code2HTML, v. 0.9.1