1 /*-
2  * Copyright (c) 2005 Andrey Simonenko
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 
29 #ifndef lint
30 static const char rcsid[] ATTR_UNUSED =
31   "@(#)$Id: ipastat_time.c,v 1.2 2011/01/23 18:42:35 simon Exp $";
32 #endif /* !lint */
33 
34 #include <ctype.h>
35 #include <errno.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <strings.h>
40 #include <time.h>
41 
42 #include "ipa_mod.h"
43 
44 #include "queue.h"
45 
46 #include "dlapi.h"
47 #include "confcommon.h"
48 #include "memfunc.h"
49 
50 #include "ipastat_time.h"
51 
52 #include "ipastat_log.h"
53 #include "ipastat_main.h"
54 
55 /* List of all opt_tint structures. */
56 struct opt_tint_list opt_tint_list = STAILQ_HEAD_INITIALIZER(opt_tint_list);
57 
58 /* Default opt_tint. */
59 static struct opt_tint opt_tint_default;
60 
61 /* Regular expressions for checking time interval. */
62 #define PAT_TINT_FORM "^[^-]+(-[^-]+)?$"
63 #define PAT_TINT_PART "^\
64 (\
65 [[:digit:]]{4,}(\\.([[:digit:]]{1,2}|[[:alpha:]]{3}))?(\\.[[:digit:]]{1,2})?|\
66 ([[:digit:]]{1,2}|[[:alpha:]]{3})?(\\.[[:digit:]]{1,2})?\
67 )?\
68 (\
69 /[[:digit:]]{1,2}(:[[:digit:]]{1,2})?(:[[:digit:]]{1,2})?\
70 )?$"
71 
72 static regex_t	re_tint_form;		/* Compiled PAT_TINT_FORM. */
73 static regex_t	re_tint_part;		/* Compiled PAT_TINT_PART. */
74 
75 static ipa_tm	curdate;		/* Current date. */
76 
77 static const char month_name[MONTHES_IN_YEAR][3] = {
78     "jan", "feb", "mar", "apr", "may", "jun",
79     "jul", "aug", "sep", "oct", "nov", "dec"
80 };
81 
82 /*
83  * Initialized various local date related variables.
84  */
85 int
init_time_data(void)86 init_time_data(void)
87 {
88 	time_t t;
89 
90 	/* Get current date. */
91 	if (time(&t) == (time_t)-1) {
92 		logmsg(IPA_LOG_ERR, "init_time_data: time failed");
93 		return (-1);
94 	}
95 	if (localtime_r(&t, (struct tm *)&curdate) == NULL) {
96 		logmsg(IPA_LOG_ERR, "init_time_data: localtime_r failed");
97 		return (-1);
98 	}
99 	curdate.tm_year += 1900;
100 	curdate.tm_mon++;
101 	return (0);
102 }
103 
104 /*
105  * Compare two ipa_tm tm1 and tm2 structures:
106  * if (tm1 == tm2)
107  *	return (0);
108  * if (tm1 > tm2)
109  *	return (1);
110  * if (tm1 < tm2)
111  *	return (-1);
112  */
113 static int
cmp_ipa_tm(const ipa_tm * tm1,const ipa_tm * tm2)114 cmp_ipa_tm(const ipa_tm *tm1, const ipa_tm *tm2)
115 {
116 	/*
117 	 * We do not use mktime(3), because there can be problem
118 	 * with time zone and summer time.
119 	 */
120 	if (tm1->tm_year > tm2->tm_year)
121 		return (1);
122 	if (tm1->tm_year == tm2->tm_year) {
123 		if (tm1->tm_mon > tm2->tm_mon)
124 			return (1);
125 		if (tm1->tm_mon == tm2->tm_mon) {
126 			if (tm1->tm_mday > tm2->tm_mday)
127 				return (1);
128 			if (tm1->tm_mday == tm2->tm_mday) {
129 				if (tm1->tm_hour > tm2->tm_hour)
130 					return (1);
131 				if (tm1->tm_hour == tm2->tm_hour) {
132 					if (tm1->tm_min > tm2->tm_min)
133 						return (1);
134 					if (tm1->tm_min == tm2->tm_min) {
135 						if (tm1->tm_sec > tm2->tm_sec)
136 							return (1);
137 						if (tm1->tm_sec == tm2->tm_sec)
138 							return (0);
139 					}
140 				}
141 			}
142 		}
143 	}
144 	return (-1);
145 }
146 
147 /*
148  * Return last month day in the given year/mon.
149  */
150 static int
last_mday(int year,int mon)151 last_mday(int year, int mon)
152 {
153 	static int const last_mday_arr[MONTHES_IN_YEAR + 1] =
154 	    /*   Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
155 	    {  0, 31,  0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
156 
157 	if (mon == 2) {
158 		/* February. */
159 		return ((year % 4 == 0 &&
160 		    (year % 100 != 0 || year % 400 == 0)) ? 29 : 28);
161 	}
162 	return (last_mday_arr[mon]);
163 }
164 
165 /*
166  * Output error message, that month, day, hours, minutes or seconds are
167  * specified incorrect in some part of time interval.
168  */
169 static void
wrong_tint_part(const char * what,int right)170 wrong_tint_part(const char *what, int right)
171 {
172 	logmsgx(IPA_LOG_ERR, "wrong format of time interval's %s part: "
173 	    "incorrect %s value", right ? "right" : "left", what);
174 }
175 
176 static int
parse_tint_date(char * s,ipa_tm * tm,int right)177 parse_tint_date(char *s, ipa_tm *tm, int right)
178 {
179 	char *ptr;
180 	unsigned int n, val1, val2, val3;
181 
182 	if (*s == '.') {
183 		/* .DD */
184 		errno = 0;
185 		if (sscanf(++s, "%d", &tm->tm_mday) != 1) {
186 			logmsgx(IPA_LOG_ERR, "parse_tint_date: "
187 			    "sscanf(\"%s\", %%u): failed", s);
188 			return (-1);
189 		}
190 		goto done;
191 	}
192 
193 	/* Check if there is named month. */
194 	for (ptr = s; *ptr != '\0'; ++ptr)
195 		if (isalpha((unsigned char)*ptr)) {
196 			for (n = 0; n < MONTHES_IN_YEAR; ++n)
197 				if (strncasecmp(ptr, month_name[n], 3) == 0)
198 					break;
199 			if (n == MONTHES_IN_YEAR) {
200 				logmsgx(IPA_LOG_ERR, "parse_tint_date: wrong "
201 				    "format of time interval: unknown month "
202 				    "name");
203 				return (-1);
204 			}
205 			if (sprintf(ptr, "%02u", n + 1) != 2) {
206 				logmsgx(IPA_LOG_ERR, "parse_tint_date: "
207 				    "sprintf failed");
208 				return (-1);
209 			}
210 			ptr += 2;
211 			do {
212 				*ptr = *(ptr + 1);
213 			} while (*++ptr != '\0');
214 			break;
215 		}
216 
217 	errno = 0;
218 	n = sscanf(s, "%u.%u.%u", &val1, &val2, &val3);
219 	if (n < 1) {
220 		logmsg(IPA_LOG_ERR, "parse_tint_date: "
221 		    "sscanf(\"%s\", %%u.%%u.%%u) failed", s);
222 		return (-1);
223 	}
224 	ptr = strchr(s, '.');
225 	if (ptr == NULL) {
226 		ptr = strchr(s, '/');
227 		if (ptr == NULL)
228 			ptr = strchr(s, '\0');
229 	}
230 	switch (n) {
231 	case 3:
232 		/* YYYY.MM.DD */
233 		tm->tm_year = val1;
234 		tm->tm_mon = val2;
235 		tm->tm_mday = val3;
236 		break;
237 	case 2:
238 		if (ptr - s >= 4) {
239 			/* YYYY.MM */
240 			tm->tm_year = val1;
241 			tm->tm_mon = val2;
242 			tm->tm_mday = right ? last_mday(val1, val2) : 1;
243 		} else {
244 			/* MM.DD */
245 			tm->tm_mon = val1;
246 			tm->tm_mday = val2;
247 		}
248 		break;
249 	default: /* 1 */
250 		if (ptr - s >= 4) {
251 			/* YYYY */
252 			tm->tm_year = val1;
253 			if (right) {
254 				tm->tm_mon = 12;
255 				tm->tm_mday = last_mday(val1, 12);
256 			} else
257 				tm->tm_mon = tm->tm_mday = 1;
258 		} else {
259 			/* MM */
260 			tm->tm_mon = val1;
261 			tm->tm_mday = right ? last_mday(tm->tm_year, val1) : 1;
262 		}
263 	}
264 
265 done:
266 	/* Validate month and month day. */
267 	if (tm->tm_mon == 0 || tm->tm_mon > 12) {
268 		logmsgx(IPA_LOG_ERR, "parse_tint_part: wrong month %d",
269 		    tm->tm_mon);
270 		return (-1);
271 	}
272 	if (tm->tm_mday == 0 || tm->tm_mday > 31) {
273 		logmsgx(IPA_LOG_ERR, "parse_tint_part: wrong month day %d",
274 		    tm->tm_mday);
275 		return (-1);
276 	}
277 	return (0);
278 }
279 
280 static int
parse_tint_time(const char * s,ipa_tm * tm,int right)281 parse_tint_time(const char *s, ipa_tm *tm, int right)
282 {
283 	unsigned int val1, val2, val3;
284 	int n;
285 
286 	errno = 0;
287 	n = sscanf(++s, "%u:%u:%u", &val1, &val2, &val3);
288 	if (n < 1) {
289 		logmsg(IPA_LOG_ERR, "parse_tint_time: "
290 		    "sscanf(\"%s\", %%u:%%u:%%u) failed", s);
291 		return (-1);
292 	}
293 	if (n >= 1)
294 		tm->tm_hour = val1;	/* hh */
295 	if (n >= 2)
296 		tm->tm_min = val2;	/* hh:mm */
297 	if (n == 3)
298 		tm->tm_sec = val3;	/* hh:mm:ss */
299 
300 	/* Validate month, day, hours, minutes and seconds. */
301 	if (tm->tm_mon == 0 || tm->tm_mon > 12) {
302 		wrong_tint_part("month", right);
303 		return (-1);
304 	}
305 	if (tm->tm_mday == 0 ||
306 	    tm->tm_mday > last_mday(tm->tm_year, tm->tm_mon)) {
307 		wrong_tint_part("day", right);
308 		return (-1);
309 	}
310 	if (tm->tm_hour > 23 && !(tm->tm_hour == HOURS_IN_DAY &&
311 	    tm->tm_min == 0 && tm->tm_sec == 0)) {
312 		wrong_tint_part("hours", right);
313 		return (-1);
314 	}
315 	if (tm->tm_min > 59) {
316 		wrong_tint_part("minutes", right);
317 		return (-1);
318 	}
319 	if (tm->tm_sec > 59) {
320 		wrong_tint_part("seconds", right);
321 		return (-1);
322 	}
323 	return (n);
324 }
325 
326 static int
parse_tint_part(char * s,ipa_tm * tm,int right)327 parse_tint_part(char *s, ipa_tm *tm, int right)
328 {
329 	int n;
330 
331 	/*
332 	 * Initial value for time in the left part, for the right
333 	 * part these values are completed at the end of this function.
334 	 */
335 	tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
336 
337 	if (*s != '/') {
338 		/* Date is specified. */
339 		if (parse_tint_date(s, tm, right) < 0)
340 			return (-1);
341 	}
342 
343 	n = 0;
344 	s = strchr(s, '/');
345 	if (s != NULL) {
346 		/* Time is specified. */
347 		n = parse_tint_time(s, tm, right);
348 		if (n < 0)
349 			return (-1);
350 	}
351 
352 	/* Complete value of time for the right part, if needed. */
353 	if (right)
354 		switch (n) {
355 		case 2:
356 			/* hh:mm */
357 			if (++tm->tm_min > 59)
358 				tm->tm_min = 0;
359 			else
360 				break;
361 			/* FALLTHROUGH */
362 		case 1:
363 			/* hh */
364 			if (++tm->tm_hour <= 23)
365 				break;
366 			/* FALLTHROUGH */
367 		case 0:
368 			tm->tm_hour = HOURS_IN_DAY;
369 		}
370 
371 	return (0);
372 }
373 
374 /*
375  * Add optional time interval "-q -i|I interval" and parse this
376  * interval.
377  */
378 int
opt_tint_add(char * s,int exact)379 opt_tint_add(char *s, int exact)
380 {
381 	static char inited = 0;
382 
383 	struct tm *tm1, *tm2;
384 	struct opt_tint *tint;
385 	char *right_part;
386 
387 	if (!inited) {
388 		int error;
389 
390 		/* Build regular expressions. */
391 		error = regcomp(&re_tint_form, PAT_TINT_FORM,
392 		    REG_EXTENDED|REG_NOSUB);
393 		if (error != 0) {
394 			logmsgx(IPA_LOG_ERR, "opt_tint_add: regcomp(%s): %s",
395 			    PAT_TINT_FORM, regerrbuf(error));
396 			return (-1);
397 		}
398 		error = regcomp(&re_tint_part, PAT_TINT_PART,
399 		    REG_EXTENDED|REG_NOSUB);
400 		if (error != 0) {
401 			logmsgx(IPA_LOG_ERR, "opt_tint_add: regcomp(%s): %s",
402 			    PAT_TINT_PART, regerrbuf(error));
403 			return (-1);
404 		}
405 		inited = 1;
406 	}
407 
408 	/* Validate time interval format. */
409 	if (regexec_simple(&re_tint_form, s) != 0) {
410 		logmsgx(IPA_LOG_ERR, "opt_tint_add: wrong format of time "
411 		    "interval \"%s\"", s);
412 		return (-1);
413 	}
414 
415 	/* Split time interval into parts. */
416 	right_part = strchr(s, '-');
417 	if (right_part != NULL)
418 		/* Split time interval into left and right parts. */
419 		*right_part++ = '\0';
420 	else
421 		/* Left and right parts are the same as whole time interval. */
422 		right_part = s;
423 
424 	if (regexec_simple(&re_tint_part, s) != 0) {
425 		logmsgx(IPA_LOG_ERR, "opt_tint_add: cannot recognize format "
426 		    "of time interval's left part \"%s\"", s);
427 		return (-1);
428 	}
429 	if (right_part != s)
430 		if (regexec_simple(&re_tint_part, right_part) != 0) {
431 			logmsgx(IPA_LOG_ERR, "opt_tint_add: cannot recognize "
432 			    "format of time interval's right part \"%s\"",
433 			    right_part);
434 			return (-1);
435 		}
436 
437 	tint = mem_malloc(sizeof(*tint), m_anon);
438 	if (tint == NULL) {
439 		logmsgx(IPA_LOG_ERR, "opt_tint_add: mem_malloc failed");
440 		return (-1);
441 	}
442 
443 	tint->tm1 = tint->tm2 = curdate;
444 
445 	tm1 = &tint->tm1;
446 	tm2 = &tint->tm2;
447 	if (parse_tint_part(s, tm1, 0) < 0 ||
448 	    parse_tint_part(right_part, tm2, 1) < 0)
449 		return (-1);
450 
451 	if (cmp_ipa_tm(&tint->tm1, &tint->tm2) > 0) {
452 		logmsgx(IPA_LOG_ERR, "opt_tint_add: first timestamp "
453 		    "(%d.%02d.%02d/%02d:%02d:%02d) should be less than or "
454 		    "equal to second timestamp (%d.%02d.%02d/%02d:%02d:%02d)",
455 		    tm1->tm_year, tm1->tm_mon, tm1->tm_mday,
456 		    tm1->tm_hour, tm1->tm_min, tm1->tm_sec,
457 		    tm2->tm_year, tm2->tm_mon, tm2->tm_mday,
458 		    tm2->tm_hour, tm2->tm_min, tm2->tm_sec);
459 		return (-1);
460 	}
461 
462 	tint->exact = exact;
463 	STAILQ_INSERT_TAIL(&opt_tint_list, tint, link);
464 	return (0);
465 }
466 
467 /*
468  * Release memory previously allocated by all opt_tint_add() calls.
469  */
470 void
opt_tint_free(void)471 opt_tint_free(void)
472 {
473 	if (STAILQ_EMPTY(&opt_tint_list) ||
474 	    STAILQ_FIRST(&opt_tint_list) != &opt_tint_default) {
475 		struct opt_tint *tint, *tint_next;
476 
477 		STAILQ_FOREACH_SAFE(tint, &opt_tint_list, link, tint_next)
478 			mem_free(tint, m_anon);
479 		regfree(&re_tint_form);
480 		regfree(&re_tint_part);
481 	}
482 }
483 
484 /*
485  * If there are not any -i options, than create default one,
486  * which is equal to the current month.
487  */
488 void
opt_tint_init(void)489 opt_tint_init(void)
490 {
491 #define tm1 opt_tint_default.tm1
492 #define tm2 opt_tint_default.tm2
493 	tm1.tm_year = tm2.tm_year = curdate.tm_year;
494 	tm1.tm_mon = tm2.tm_mon = curdate.tm_mon;
495 	tm1.tm_mday = 1;
496 	tm2.tm_mday = last_mday(curdate.tm_year, curdate.tm_mon);
497 	tm1.tm_hour = tm1.tm_min = tm1.tm_sec = tm2.tm_min = tm2.tm_sec = 0;
498 	tm2.tm_hour = HOURS_IN_DAY;
499 	STAILQ_INSERT_HEAD(&opt_tint_list, &opt_tint_default, link);
500 #undef tm1
501 #undef tm2
502 }
503