/*-
 * Copyright (c) 2003 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: ipa_time.c,v 1.2.2.3 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>

#include "ipa_mod.h"

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

#include "ipa_time.h"

#include "ipa_log.h"
#include "ipa_main.h"

/*
 * This file contains different time manipulation functions.  I don't
 * use standard library time manipulation functions, they can't be used
 * directly and should be wrapped to fix time zone and summer time related
 * errors, since ipa(8) doesn't need information about time zone and
 * summer time, I found that implementing own functions is better,
 * than writing wrappers for standard library time functions.
 *
 * May be for(;;) loops are not optimal, but for most cases
 * they are not executed and the ideas of algorithms are easy.
 */

u_int		wakeup_time;		/* wakeup_time parameter. */
u_int		freeze_time;		/* freeze_time parameter. */
u_int		sleep_after_dump;	/* sleep_after_dump parameter. */
u_int		sensitive_time;		/* sensitive_time parameter. */
int		debug_time;		/* debug_time parameter. */
int		debug_worktime;		/* debug_worktime parameter. */

u_int		worktimes_check_sec;	/* When to call worktimes_check(). */

const struct worktime *global_worktime;	/* global { worktime } */

struct worktime worktime_default;	/* 00:00-24:00 for all days. */
static struct tint_set tint_set_default;

const struct tevent *global_update_tevent; /* global { update_time } */
const struct tevent *global_append_tevent; /* global { append_time } */

const char *const wdays[] = {
	"Sunday", "Monday", "Tuesday", "Wednesday",
	"Thursday", "Friday", "Saturday"
};

const char *const active_msg[] = {
	"inactive",			/* 0 */
	"active"			/* 1 */
};

ipa_mzone	*tevent_mzone;		/* Mzone for all struct tevent{}. */
struct tevents_list tevents_list;	/* List of all tevents. */

ipa_mzone	*tint_mzone;		/* Mzone for all struct tint{}. */
struct tint_sets tint_sets;		/* List of all tint_sets. */

ipa_mzone	*worktime_mzone;	/* Mzone for all worktimes. */
struct worktimes_list worktimes_list;	/* List of all worktimes. */

ipa_tm		curdate;		/* Current human localdate. */
u_int		cursec;			/* Current time in seconds since midnight. */
u_int		curwday;		/* Current week day. */
int		newday_flag = 0;	/* Non-zero if new day came. */

#ifdef WITH_ANY_LIMITS
char		*ipa_tm_buf = NULL;	/* Pointer to buffer used in ipa_tm_to_buf(). */
#endif

char		*tdiff_buf = NULL;	/* Pointer to buffer used in tdiff_to_buf(). */

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 };

#ifdef WITH_ANY_LIMITS
/*
 * Non-reentrant function for convert ipa_tm{} value to human readable
 * value and return a pointer to allocated buffer with this value.
 * If an error occurred, then a pointer to static string with error
 * message is returned.
 */
const char *
ipa_tm_to_buf(const ipa_tm *tm)
{
	mem_free(ipa_tm_buf, m_anon);
	if (mem_asprintf(m_anon, &ipa_tm_buf, "%d.%02d.%02d/%02d:%02d:%02d",
	    tm->tm_year, tm->tm_mon, tm->tm_mday,
	    tm->tm_hour, tm->tm_min, tm->tm_sec) < 0)
		return "(ipa_tm_to_buf: mem_asprintf: no memory)";
	return ipa_tm_buf;
}

/*
 * 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];
}

/*
 * Check ipa_tm date for correct values, ignore nonexistent
 * time (for example when time zone is changed as +1h, then some
 * time does not exist).
 */
int
check_ipa_tm(const ipa_tm *tm)
{
	int	mon = tm->tm_mon;
	int	mday = tm->tm_mday;

	if (mon == 0 || mon > 12 || mday > 31 || mday == 0 ||
	    mday > last_mday(tm->tm_year, mon) || tm->tm_hour > 23 ||
	    tm->tm_min > 59 || tm->tm_sec > 59)
		return -1;
	return 0;
}

/*
 * If time in tm is 24:00:00, then convert it to 00:00:00 of the next day.
 */
void
fix_240000(ipa_tm *tm)
{
	/* When tm_hour is 24, then tm_min and tm_sec == 0. */
	if (tm->tm_hour == HOURS_IN_DAY) {
		/* 24:00:00 --> next day 00:00:00 */
		tm->tm_hour = 0;
		if (tm->tm_mday == last_mday(tm->tm_year, tm->tm_mon)) {
			tm->tm_mday = 1;
			if (tm->tm_mon == MONTHES_IN_YEAR) {
				tm->tm_mon = 1;
				tm->tm_year++;
			} else
				tm->tm_mon++;
		} else
			tm->tm_mday++;
	}
}

/*
 * Compare two ipa_tm tm1 and tm2 structures:
 * if (tm1 == tm2)
 *	return 0;
 * if (tm1 > tm2)
 *	return 1;
 * if (tm1 < tm2)
 *	return -1;
 * This function correctly works with 24:00:00 time.
 */
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 difference in seconds between tm1 and tm0: tm1 - tm0,
 * tm1 is expected to be greater than tm0.
 */
u_int
ipa_tm_diff(const ipa_tm *tm1_arg, const ipa_tm *tm0_arg)
{
	u_int	result, sec1, sec0;
	ipa_tm	tm1, tm0;

	tm1 = *tm1_arg;
	tm0 = *tm0_arg;

	sec1 = tm1.tm_hour * SECONDS_IN_HOUR + tm1.tm_min * SECONDS_IN_MINUTE + tm1.tm_sec;
	sec0 = tm0.tm_hour * SECONDS_IN_HOUR + tm0.tm_min * SECONDS_IN_MINUTE + tm0.tm_sec;

	if (sec1 > sec0)
		result = sec1 - sec0;
	else {
		result = HOURS_IN_DAY * SECONDS_IN_HOUR - sec0;
		result += sec1;
		if (tm0.tm_mday == last_mday(tm0.tm_year, tm0.tm_mon)) {
			tm0.tm_mday = 1;
			if (tm0.tm_mon == MONTHES_IN_YEAR) {
				tm0.tm_mon = 1;
				tm0.tm_year++;
			} else
				tm0.tm_mon++;
		} else
			tm0.tm_mday++;
	}

	for (; tm0.tm_year < tm1.tm_year; ++tm0.tm_year) {
		for (; tm0.tm_mon <= MONTHES_IN_YEAR; tm0.tm_mon++) {
			result += (last_mday(tm0.tm_year, tm0.tm_mon) - tm0.tm_mday + 1) * SECONDS_IN_DAY;
			tm0.tm_mday = 1;
		}
		tm0.tm_mon = 1;
	}

	for (; tm0.tm_mon < tm1.tm_mon; tm0.tm_mon++) {
		result += (last_mday(tm0.tm_year, tm0.tm_mon) - tm0.tm_mday + 1) * SECONDS_IN_DAY;
		tm0.tm_mday = 1;
	}

	result += (tm1.tm_mday - tm0.tm_mday) * SECONDS_IN_DAY;

	return result;
}
#endif /* WITH_ANY_LIMITS */

#ifdef WITH_LIMITS
/*
 * Add some seconds to ipa_tm variable ignoring any time zone issues
 * without using any standard time functions from the library.
 */
void
ipa_tm_addtime(ipa_tm *tm, u_int addsec)
{
	int t, lmday, year, mon, mday, hour, min, sec;

	year = tm->tm_year;
	mon = tm->tm_mon;
	mday = tm->tm_mday;
	hour = tm->tm_hour;
	min = tm->tm_min;
	sec = tm->tm_sec;

	t = addsec / SECONDS_IN_DAY;
	addsec -= t * SECONDS_IN_DAY;
	if (t != 0) {
		for (;;) {
			lmday = last_mday(year, mon);
			if (mday + t > lmday) {
				t -= lmday - mday;
				if (mon == MONTHES_IN_YEAR) {
					mon = 1;
					year++;
				} else
					mon++;
				mday = 1;
				if (--t == 0)
					break;
			} else {
				mday += t;
				break;
			}
		}
	}
	if (addsec != 0) {
		t = hour * SECONDS_IN_HOUR + min * SECONDS_IN_MINUTE + sec;
		if (t + addsec >= SECONDS_IN_DAY) {
			t += addsec - SECONDS_IN_DAY;
			lmday = last_mday(year, mon);
			if (++mday > lmday) {
				if (mon == MONTHES_IN_YEAR) {
					mon = 1;
					year++;
				} else
					mon++;
				mday = 1;
			}
		} else
			t += addsec;
		hour = t / SECONDS_IN_HOUR;
		t -= hour * SECONDS_IN_HOUR;
		min = t / SECONDS_IN_MINUTE;
		sec = t - min * SECONDS_IN_MINUTE;
	}

	tm->tm_year = year;
	tm->tm_mon = mon;
	tm->tm_mday = mday;
	tm->tm_hour = hour;
	tm->tm_min = min;
	tm->tm_sec = sec;
}

/*
 * Convert given time in tm according to +upto.
 */
static void
ipa_tm_upto(ipa_tm *tm, char upto)
{
	if (upto == TEXP_UPTO_SIMPLE)
		return;

	/* +M or +m -> +h -> +D -> +W */

	if (upto == TEXP_UPTO_MONTH) {	/* +M */
		/* Up to the end of month. */
		if (tm->tm_mon == MONTHES_IN_YEAR) {
			tm->tm_mon = 1;
			tm->tm_year++;
		} else
			tm->tm_mon++;
		tm->tm_mday = 1;
		tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
		return;
	}

	/* Up to the end of minute. */
	ipa_tm_addtime(tm, SECONDS_IN_MINUTE - tm->tm_sec);
	if (upto == TEXP_UPTO_MINUTE)	/* +m */
		return;
	tm->tm_min++;


	/* Up to the end of hour. */
	ipa_tm_addtime(tm, SECONDS_IN_MINUTE * (MINUTES_IN_HOUR - tm->tm_min));
	if (upto == TEXP_UPTO_HOUR)	/* +h */
		return;
	tm->tm_hour++;

	/* Up to the end of day. */
	ipa_tm_addtime(tm, SECONDS_IN_HOUR * (HOURS_IN_DAY - tm->tm_hour));
	if (upto == TEXP_UPTO_DAY)	/* +D */
		return;
	tm->tm_wday++;

	/* Up to the end of week. */
	if (tm->tm_wday != 1)		/* +W */
		ipa_tm_addtime(tm, SECONDS_IN_DAY * (DAYS_IN_WEEK - tm->tm_wday));
}

/*
 * Convert given time in tm according to texp.
 */
void
ipa_tm_texp(ipa_tm *tm, const struct texp *texp)
{
	int right_side = texp->side;

	if (!right_side)
		ipa_tm_upto(tm, texp->upto);
	ipa_tm_addtime(tm, texp->seconds);
	if (right_side)
		ipa_tm_upto(tm, texp->upto);
}

/*
 * Set tm_wday in the given structure.  mktime(3) ignores tm_wday
 * and tm_wday is set on success.  It is assumed, that 00:00:01 time
 * exists in all time zones.
 */
int
set_wday(ipa_tm *tm_ptr)
{
	struct tm tm;

	tm = *tm_ptr;
	tm.tm_year -= 1900;
	tm.tm_mon--;
	tm.tm_hour = tm.tm_min = 0;
	tm.tm_sec = 1;
	tm.tm_isdst = -1;
	if (mktime(&tm) == (time_t)-1) {
		logmsg(IPA_LOG_ERR, "set_wday: mktime failed");
		return -1;
	}
	tm_ptr->tm_wday = tm.tm_wday;
	return 0;
}
#endif /* WITH_LIMITS */

#ifdef WITH_THRESHOLDS
/*
 * Subtract some seconds from ipa_tm variable ignoring any time zone issues
 * without using any standard time functions from the library.
 */
void
ipa_tm_subtime(ipa_tm *tm, u_int subsec)
{
	int t, year, mon, mday, hour, min, sec;

	year = tm->tm_year;
	mon = tm->tm_mon;
	mday = tm->tm_mday;
	hour = tm->tm_hour;
	min = tm->tm_min;
	sec = tm->tm_sec;

	t = subsec / SECONDS_IN_DAY;
	subsec -= t * SECONDS_IN_DAY;
	if (t != 0) {
		for (;;) {
			if (mday > t) {
				mday -= t;
				break;
			} else {
				if (mon == 1) {
					mon = MONTHES_IN_YEAR;
					year--;
				} else
					mon--;
				t -= mday;
				mday = last_mday(year, mon);
				if (t == 0)
					break;
			}
		}
	}
	if (subsec != 0) {
		t = hour * SECONDS_IN_HOUR + min * SECONDS_IN_MINUTE + sec;
		if (t >= subsec)
			t -= subsec;
		else {
			t = SECONDS_IN_DAY - (subsec - t);
			if (--mday == 0) {
				if (mon == 1) {
					mon = 12;
					year--;
				} else
					mon--;
				mday = last_mday(year, mon);
			}
		}
		hour = t / SECONDS_IN_HOUR;
		t -= hour * SECONDS_IN_HOUR;
		min = t / SECONDS_IN_MINUTE;
		sec = t - min * SECONDS_IN_MINUTE;
	}

	tm->tm_year = year;
	tm->tm_mon = mon;
	tm->tm_mday = mday;
	tm->tm_hour = hour;
	tm->tm_min = min;
	tm->tm_sec = sec;
}
#endif /* WITH_THRESHOLDS */

/*
 * Non-reentrant function for converting number of seconds to hours,
 * minutes and seconds and return a pointer to allocated buffer with
 * this value.  If an error occurred, then static string with error
 * message is returned.
 */
const char *
time_to_buf(u_int t)
{
	static char buf[BUF_TIME_SIZE];

	int	rv;
	u_int	h, m, s;

	h = t / SECONDS_IN_HOUR;
	t -= h * SECONDS_IN_HOUR;
	m = t / SECONDS_IN_MINUTE;
	s = t - m * SECONDS_IN_MINUTE;
	if (h == 0) {
		if (m == 0)
			rv = snprintf(buf, sizeof buf, "%us", s);
		else {
			if (s == 0)
				rv = snprintf(buf, sizeof buf, "%um", m);
			else
				rv = snprintf(buf, sizeof buf, "%um %02us", m, s);
		}
	} else
		rv = snprintf(buf, sizeof buf, "%uh %02um %02us", h, m, s);
	return rv < 0 ? "(time_to_buf: snprintf failed)" : buf;
}

/*
 * The same as time_to_buf(), but argument is double and can be
 * negative.
 */
const char *
tdiff_to_buf(double t)
{
	const char *sign;

	mem_free(tdiff_buf, m_anon);

	if (t > UINT_MAX)
		return mem_asprintf(m_anon, &tdiff_buf, "%.0f seconds", t) < 0 ?
		    "(tdiff_to_buf: mem_asprintf failed)" : tdiff_buf;

	if (t < 0.0) {
		t = -t;
		sign = "-";
	} else
		sign = "";

	return mem_asprintf(m_anon, &tdiff_buf, "%s%s", sign, time_to_buf((u_int)t)) < 0 ?
	    "(tdiff_to_buf: mem_asprintf failed)" : tdiff_buf;
}

/*
 * Non-reentrant function for converting number of seconds to time.
 */
const char *
sec_to_buf(u_int sec)
{
	static char buf[BUF_SEC_SIZE];

	u_int	h, m, s;

	if (sec == SECONDS_IN_WEEK)
		return "xx:xx:xx";

	if (sec <= SECONDS_IN_DAY) {
		h = sec / SECONDS_IN_HOUR;
		sec -= h * SECONDS_IN_HOUR;
		m = sec / SECONDS_IN_MINUTE;
		s = sec - m * SECONDS_IN_MINUTE;
		return snprintf(buf, sizeof buf, "%02u:%02u:%02u", h, m, s) < 0 ?
		    "(sec_to_buf: snprintf failed)" : buf;
	}

	return "??:??:??";
}

/*
 * Non-reentrant function for converting time interval to string.
 */
const char *
tint_to_buf(const struct tint *tint)
{
	static char buf[12]; /* xx:xx-xx:xx */

	u_int h1, m1, h2, m2;

	h1 = tint->sec1 / SECONDS_IN_HOUR;
	m1 = (tint->sec1 - h1 * SECONDS_IN_HOUR) / SECONDS_IN_MINUTE;
	h2 = tint->sec2 / SECONDS_IN_HOUR;
	m2 = (tint->sec2 - h2 * SECONDS_IN_HOUR) / SECONDS_IN_MINUTE;
	return snprintf(buf, sizeof buf, "%02u:%02u-%02u:%02u", h1, m1, h2, m2) < 0 ?
	    "(tint_to_buf: snprintf failed)" : buf;
}

/*
 * Convert seconds since midnight to hours, minutes and seconds.
 */
void
sec_to_time(u_int sec, ipa_tm *tm)
{
	int h, m, s;

	h = sec / SECONDS_IN_HOUR;
	sec -= h * SECONDS_IN_HOUR;
	m = sec / SECONDS_IN_MINUTE;
	s = sec - m * SECONDS_IN_MINUTE;

	tm->tm_hour = h;
	tm->tm_min = m;
	tm->tm_sec = s;
}

void
init_worktime_default(void)
{
	static struct tint tint;

	u_int	wday;
	struct tint_list *list = &tint_set_default.list;

	tint.sec1 = 0;
	tint.sec2 = SECONDS_IN_DAY;

	STAILQ_INIT(list);
	STAILQ_INSERT_HEAD(list, &tint, link);

	STAILQ_INSERT_HEAD(&tint_sets, &tint_set_default, link);

	for (wday = 0; wday < DAYS_IN_WEEK; ++wday)
		worktime_default.tint_list[wday] = list;
}

/*
 * Release memory help by tint_set, including struct tint_set{}.
 */
void
free_tint_set(struct tint_set *set)
{
	struct tint *tint, *tint_next;

	for (tint = STAILQ_FIRST(&set->list); tint != NULL; tint = tint_next) {
		tint_next = STAILQ_NEXT(tint, link);
		mzone_free(tint_mzone, tint);
	}
	mem_free(set, m_anon);
}

/*
 * Free all worktimes and worktime mzone.
 */
void
free_worktimes(void)
{
	struct tint_set *set, *set_next;

	/* Skip first tint, since it is tint_set_default. */
	STAILQ_REMOVE_HEAD(&tint_sets, link);

	/* Free other tint_sets. */
	for (set = STAILQ_FIRST(&tint_sets); set != NULL; set = set_next) {
		set_next = STAILQ_NEXT(set, link);
		free_tint_set(set);
	}

	mzone_deinit(tint_mzone);
	mzone_deinit(worktime_mzone);
}

/*
 * Find next time interval for worktime starting from tint_start.
 */
static void
find_next_tint(struct worktime *wt, const struct tint *tint_start,
    int report_skipping)
{
	const struct tint *tint;

	for (tint = tint_start; tint != NULL; tint = STAILQ_NEXT(tint, link)) {
		if (cursec < tint->sec1) {
			/* x [   ] */
			wt->is_active = INACTIVE_FLAG;
			if (worktimes_check_sec > tint->sec1)
				worktimes_check_sec = tint->sec1;
			if (debug_worktime)
				logmsgx(IPA_LOG_INFO, "find_next_tint: %s left before start of %s %s worktime interval",
				    time_to_buf(tint->sec1 - cursec), wdays[curwday], tint_to_buf(tint));
			break;
		}
		if (tint->sec1 <= cursec && cursec < tint->sec2) {
			/* [ x ] */
			wt->is_active = ACTIVE_FLAG;
			if (worktimes_check_sec > tint->sec2)
				worktimes_check_sec = tint->sec2;
			if (debug_worktime)
				logmsgx(IPA_LOG_INFO, "find_next_tint: %s left before end of %s %s worktime interval",
				    time_to_buf(tint->sec2 - cursec), wdays[curwday], tint_to_buf(tint));
			break;
		}
		/* [   ] x */
		if (report_skipping || debug_worktime)
			logmsgx(IPA_LOG_WARNING, "find_next_tint: skipping %s %s worktime interval",
			    wdays[curwday], tint_to_buf(tint));
	}
	if (tint == NULL) {
		/* There is no more time intervals for current week day. */
		wt->is_active = INACTIVE_FLAG;
		wt->active_sec = wt->inactive_sec = SECONDS_IN_WEEK;
		if (debug_worktime)
			logmsgx(IPA_LOG_INFO, "find_next_tint: there is no more worktime intervals for %s (current week day)",
			    wdays[curwday]);
	} else {
		wt->active_sec = tint->sec1;
		wt->inactive_sec = tint->sec2;
	}

	/* Real tint or NULL for debugging purpose. */
	wt->curtint = tint;
}

/*
 * Check if current week day is set in worktime, find current
 * active or inactive time interval.
 */
void
worktimes_newday(int report_skipping)
{
	struct worktime *wt;
	const struct tint_list *list;

	worktimes_check_sec = SECONDS_IN_WEEK; /* Hint. */

	SLIST_FOREACH(wt, &worktimes_list, link) {
		list = wt->tint_list[curwday];
		if (!STAILQ_EMPTY(list)) {
			/* Current week day is set in worktime. */
			find_next_tint(wt, STAILQ_FIRST(list), report_skipping);
		} else {
			/* Current week day is not set in worktime. */
			wt->is_active = INACTIVE_FLAG;
			/* Set NULL for debugging purpose. */
			wt->curtint = NULL;
			wt->active_sec = wt->inactive_sec = SECONDS_IN_WEEK;
			if (debug_worktime)
				logmsgx(IPA_LOG_INFO, "worktimes_newday: %s (current week day) is not set in worktime",
				    wdays[curwday]);
		}
	}

	if (debug_worktime)
		logmsgx(IPA_LOG_INFO, "worktimes_checks: worktimes_check_sec %s",
		    sec_to_buf(worktimes_check_sec));
}

/*
 * Check if current time interval is still active, if it is
 * time to make inactive time interval active.
 */
void
worktimes_check(void)
{
	u_int	delta;
	struct worktime *wt;

	worktimes_check_sec = SECONDS_IN_WEEK; /* Hint. */

	SLIST_FOREACH(wt, &worktimes_list, link)
		if (IS_ACTIVE(wt)) {
			/* Is active. */
			if (wt->inactive_sec <= cursec) {
				/* It's time to make it inactive. */
				delta = cursec - wt->inactive_sec;
				if (delta > sensitive_time)
					logmsgx(IPA_LOG_WARNING, "worktime interval %s %s became inactive too late: delta %s is greater than \"sensitive_time\" %u seconds",
					    wdays[curwday], tint_to_buf(wt->curtint), time_to_buf(delta), sensitive_time);
				find_next_tint(wt, STAILQ_NEXT(wt->curtint, link), 1);
			} else {
				if (worktimes_check_sec > wt->inactive_sec)
					worktimes_check_sec = wt->inactive_sec;
			}
		} else {
			/* Is inactive. */
			if (wt->active_sec <= cursec) {
				/* It's time to make it active. */
				delta = cursec - wt->active_sec;
				if (delta > sensitive_time)
					logmsgx(IPA_LOG_WARNING, "worktime interval %s %s became active too late: delta %s is greater than \"sensitive_time\" %u seconds",
					    wdays[curwday], tint_to_buf(wt->curtint), time_to_buf(delta), sensitive_time);
				find_next_tint(wt, wt->curtint, 1);
			} else {
				if (worktimes_check_sec > wt->active_sec)
					worktimes_check_sec = wt->active_sec;
			}
		}

	if (debug_worktime)
		logmsgx(IPA_LOG_INFO, "worktimes_checks: worktimes_check_sec %s",
		    sec_to_buf(worktimes_check_sec));
}

/*
 * Try to find already registered worktime with the same settings as
 * in wt1.  If such worktime exist, then return its pointer, else
 * return original one.
 */
const struct worktime *
find_worktime(struct worktime *wt1)
{
	u_int	wday;
	struct worktime	*wt2;
	const struct tint_list * const *list1;
	const struct tint_list * const *list2;

	/* Check if there is already the same worktime. */
	SLIST_FOREACH(wt2, &worktimes_list, link) {
		list1 = wt1->tint_list;
		list2 = wt2->tint_list;
		for (wday = 0; wday < DAYS_IN_WEEK; ++wday) {
			if (*list1 != *list2)
				break;
			++list1;
			++list2;
		}
		if (wday == DAYS_IN_WEEK) {
			/* The same worktime was found --> free original one. */
			mzone_free(worktime_mzone, wt1);
			return wt2;
		}
	}

	/* New worktime, add it to the list. */
	SLIST_INSERT_HEAD(&worktimes_list, wt1, link);
	return wt1;
}

#ifdef WITH_ANY_LIMITS
/*
 * Return 0 if wt2 is a subset of wt1, else return -1.
 * Time intervals in limits' or thresholds' worktimes must be
 * subsets of time intervals in their rule's worktime.
 */
int
check_worktime_subset(const struct worktime *wt1, const struct worktime *wt2)
{
	u_int	wday;
	const struct tint *tint1, *tint2;
	const struct tint_list * const *list1;
	const struct tint_list * const *list2;

	if (wt1 == NULL || wt2 == NULL || wt1 == wt2)
		return 0;

	list1 = wt1->tint_list;
	list2 = wt2->tint_list;

	for (wday = 0; wday < DAYS_IN_WEEK; ++list1, ++list2, ++wday) {
		if (*list1 == *list2)
			/* The same tint_list. */
			continue;

		if (STAILQ_EMPTY(*list2))
			/* This day in wt2 is not for accounting. */
			continue;

		if (STAILQ_EMPTY(*list1))
			/* This day in wt1 is not for accounting, but this day in wt2 is for accounting. */
			return -1;

		/* [ ] -- intervals in tint1, { } -- intervals in tint1. */
		tint2 = STAILQ_FIRST(*list2);
		STAILQ_FOREACH(tint1, *list1, link)
			for (; tint2 != NULL; tint2 = STAILQ_NEXT(tint2, link)) {
				if (tint2->sec1 >= tint1->sec1 &&
				    tint2->sec2 <= tint1->sec2)
					/* [ {  } ]*/
					continue;
				if (tint2->sec1 > tint1->sec2)
					/* [   ] {   } */
					break;
				/* {   } [   ] or {   }[   ] */
				return -1;
			}
		if (tint2 != NULL)
			/* [   ] {   } */
			return -1;
	}

	return 0;
}
#endif /* WITH_ANY_LIMITS */


syntax highlighted by Code2HTML, v. 0.9.1