xref: /illumos-gate/usr/src/cmd/auditreduce/time.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1987-2000 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 /*
28  * Time management functions for auditreduce.
29  */
30 
31 #include "auditr.h"
32 #include <locale.h>
33 #include <libintl.h>
34 
35 int	derive_date(char *, struct tm *);
36 void	derive_str(time_t, char *);
37 int	parse_time(char *, int);
38 time_t	tm_to_secs(struct tm *);
39 
40 static int	check_time(struct tm *);
41 static int	days_in_year(int);
42 static char *do_invalid(void);
43 static time_t	local_to_gm(struct tm *);
44 
45 static char *invalid_inter = NULL;
46 
47 /*
48  * Array of days per month.
49  */
50 static int	days_month[] = {
51 		31, 28, 31, 30, 31, 30,
52 		31, 31, 30, 31, 30, 31 };
53 
54 char *
55 do_invalid(void)
56 {
57 	if (invalid_inter == NULL)
58 		invalid_inter = gettext("invalid date/time format -");
59 	return (invalid_inter);
60 }
61 
62 /*
63  * .func	local_to_gm - local time to gm time.
64  * .desc	Convert a local time to Greenwhich Mean Time.
65  *	The local time is in the struct tm (time.h) format, which
66  *	is easily got from an ASCII input format (10:30:33 Jan 3, 1983).
67  *	It works by assuming that the given local time is a GMT time and
68  *	then asking the system for the corresponding local time. It then
69  *	takes the difference between those two as the correction for
70  * 	time zones and daylight savings time. This is accurate unless
71  *	the time the user asked for is near a DST switch. Then a
72  *	correction is applied - it is assumed that if we can produce
73  *	a GMT that, when run through localtime(), is equivalent to the
74  *	user's original input, we have an accurate GMT. The applied
75  *	correction simply adjusts the GMT by the amount that the derived
76  *	localtime was off. See?
77  *	It should be noted that when there is DST there is one local hour
78  *	a year when time occurs twice (in the fall) and one local hour a
79  *	year when time never occurs (in the spring).
80  *	memcpy() is used because the calls to gmtime() and localtime()
81  *	return pointers to static structures that are overwritten at each
82  *	call.
83  * .call	ret = local_to_gm(tme).
84  * .arg	tme	- ptr to struct tm (see time.h) containing local time.
85  * .ret	time_t	- seconds since epoch of equivalent GMT.
86  */
87 time_t
88 local_to_gm(struct tm *tme)
89 {
90 	time_t secs, gsecs, lsecs, save_gsecs;
91 	time_t r1secs, r2secs;
92 	struct tm ltime, gtime;
93 
94 	/*
95 	 * Get the input time in local and gmtime assuming the input
96 	 * was GMT (which it probably wasn't).
97 	 */
98 	r1secs = secs = tm_to_secs(tme);
99 	(void) memcpy((void *)&gtime, (void *)gmtime(&secs), sizeof (gtime));
100 	(void) memcpy((void *)&ltime, (void *)localtime(&secs), sizeof (ltime));
101 
102 	/*
103 	 * Get the local and gmtime in seconds, from the above tm structures.
104 	 * Calculate difference between local and GMT.
105 	 */
106 	gsecs = tm_to_secs(&gtime);
107 	lsecs = tm_to_secs(&ltime);
108 	secs = lsecs - gsecs;
109 	gsecs -= secs;
110 	(void) memcpy((void *)&ltime, (void *)localtime(&gsecs),
111 	    sizeof (ltime));
112 
113 	/*
114 	 * Now get a computed local time from the computed gmtime.
115 	 */
116 	save_gsecs = gsecs;
117 	r2secs = tm_to_secs(&ltime);
118 
119 	/*
120 	 * If the user given local time is != computed local time then
121 	 * we need to try a correction.
122 	 */
123 	if (r1secs != r2secs) {
124 		/*
125 		 * Use the difference between give localtime and computed
126 		 * localtime as our correction.
127 		 */
128 		if (r2secs > r1secs) {
129 			gsecs -= r2secs - r1secs;
130 		} else {
131 			gsecs += r1secs - r2secs;
132 		}
133 		/*
134 		 * And try the comparison again...
135 		 */
136 		(void) memcpy((void *)&ltime, (void *)localtime(&gsecs),
137 		    sizeof (ltime));
138 		r2secs = tm_to_secs(&ltime);
139 		/*
140 		 * If the correction fails then we are on a DST line
141 		 * and the user-given local time never happened.
142 		 * Do the best we can.
143 		 */
144 		if (r1secs != r2secs) {
145 			gsecs = save_gsecs;
146 		}
147 	}
148 	return (gsecs);
149 }
150 
151 
152 /*
153  * .func	tm_to_secs - convert to seconds.
154  * .desc	Convert a tm time structure (time.h) into seconds since
155  *	Jan 1, 1970 00:00:00. The time is assumed to be GMT and
156  *	so no daylight savings time correction is applied. That
157  *	is left up to the system calls (localtime(), gmtime()).
158  * .call	ret = tm_to_secs(tme).
159  * .arg	tme	- ptr to tm structure.
160  * .ret	time_t	- number of seconds.
161  */
162 time_t
163 tm_to_secs(struct tm *tme)
164 {
165 	int	leap_year = FALSE;
166 	int	days = 0;
167 	time_t num_sec = 0;
168 
169 	int	sec = tme->tm_sec;
170 	int	min = tme->tm_min;
171 	int	hour = tme->tm_hour;
172 	int	day = tme->tm_mday;
173 	int	month = tme->tm_mon;
174 	int	year = tme->tm_year + 1900;
175 
176 	if (days_in_year(year) == 366)
177 		leap_year = TRUE;
178 
179 	while (year > 1970) {
180 		num_sec += days_in_year(--year) * 24 * 60 * 60;
181 	}
182 	while (month > 0) {
183 		days = days_month[--month];
184 		if (leap_year && month == 1) {	/* 1 is February */
185 			days++;
186 		}
187 		num_sec += days * 24 * 60 * 60;
188 	}
189 	num_sec += --day * 24 * 60 * 60;
190 	num_sec += hour * 60 * 60;
191 	num_sec += min * 60;
192 	num_sec += sec;
193 
194 	return (num_sec);
195 }
196 
197 
198 /*
199  * .func	check_time - check tm structure.
200  * .desc	Check the time in a tm structure to see if all of the fields
201  *	are within range.
202  * .call	err = check_time(tme).
203  * .arg	tme	- ptr to struct tm (see time.h).
204  * .ret	0	- time is ok.
205  * .ret	-1	- time had a problem (description in error_str).
206  */
207 int
208 check_time(struct tm *tme)
209 {
210 	error_str = NULL;
211 
212 	if (tme->tm_sec < 0 || tme->tm_sec > 59) {
213 		(void) sprintf(errbuf,
214 		    gettext("seconds out of range (%d)"), tme->tm_sec + 1);
215 		error_str = errbuf;
216 	} else if (tme->tm_min < 0 || tme->tm_min > 59) {
217 		(void) sprintf(errbuf,
218 		    gettext("minutes out of range (%d)"), tme->tm_min + 1);
219 		error_str = errbuf;
220 	} else if (tme->tm_hour < 0 || tme->tm_hour > 23) {
221 		(void) sprintf(errbuf,
222 		    gettext("hours out of range (%d)"), tme->tm_hour + 1);
223 		error_str = errbuf;
224 	} else if (tme->tm_mon < 0 || tme->tm_mon > 11) {
225 		(void) sprintf(errbuf,
226 		    gettext("months out of range (%d)"), tme->tm_mon + 1);
227 		error_str = errbuf;
228 	} else if (tme->tm_year < 0) {
229 		(void) sprintf(errbuf,
230 		    gettext("years out of range (%d)"), tme->tm_year);
231 		error_str = errbuf;
232 	} else if (tme->tm_mday < 1 || tme->tm_mday > days_month[tme->tm_mon]) {
233 		if (!(days_in_year(tme->tm_year + 1900) == 366 &&
234 			tme->tm_mon == 1 &&
235 			tme->tm_mday == 29)) { /* leap year and February */
236 			(void) sprintf(errbuf,
237 			    gettext("days out of range (%d)"), tme->tm_mday);
238 			error_str = errbuf;
239 		}
240 	} else if (tme->tm_wday < 0 || tme->tm_wday > 6) {
241 		(void) sprintf(errbuf,
242 		    gettext("weekday out of range (%d)"), tme->tm_wday);
243 		error_str = errbuf;
244 	} else if (tme->tm_yday < 0 || tme->tm_yday > 365) {
245 		(void) sprintf(errbuf,
246 		    gettext("day of year out of range (%d)"), tme->tm_yday);
247 		error_str = errbuf;
248 	}
249 
250 	if (error_str == NULL)
251 		return (0);
252 	else
253 		return (-1);
254 }
255 
256 
257 /*
258  * .func parse_time.
259  * .desc Parse a user time from the command line. The user time is assumed
260  *	to be local time.
261  *	Supported formats currently are:
262  *	1. 	+xt	- where x is a number and t is a type.
263  *		types are - 's' second, 'm' minute, 'h' hour, and 'd' day.
264  *	2. 	yymmdd - yyyymmdd.
265  *		yymmddhh - yyyymmddhh.
266  *		yymmddhhmm - yyyymmddhhmm.
267  *		yymmddhhmmss - yyyymmddhhmmss.
268  * .call	err = parse_time(str, opt).
269  * .arg	str	- ptr to user input string.
270  * .arg	opt	- time option being processed.
271  * .ret	0	- succesful.
272  * .ret	-1	- failure (error message in error_str).
273  */
274 int
275 parse_time(char *str, int opt)
276 {
277 	int	ret, len, factor;
278 	char	*strxx;
279 	long	lnum;
280 	struct tm thentime;
281 
282 	len = strlen(str);
283 	/*
284 	 * If the strlen < 6 then in the "-b +2d" type of format.
285 	 */
286 	if (len < 6) {
287 		if (*str++ != '+') {
288 			(void) sprintf(errbuf, gettext("%s needs '+' (%s)"),
289 			    do_invalid(), str);
290 			error_str = errbuf;
291 			return (-1);
292 		}
293 		if (opt != 'b') {
294 			(void) sprintf(errbuf,
295 			    gettext("%s only allowed with 'b' option (%s)"),
296 			    do_invalid(), str);
297 			error_str = errbuf;
298 			return (-1);
299 		}
300 		if (m_after == 0) {
301 			(void) sprintf(errbuf,
302 			    gettext("must have -a to use -b +nx form (%s)"),
303 			    str);
304 			error_str = errbuf;
305 			return (-1);
306 		}
307 		/*
308 		 * Find out what type of offset it is - 's' 'm' 'h' or 'd'.
309 		 * Make sure that the offset is all numbers.
310 		 */
311 		if ((strxx = strpbrk(str, "dhms")) == NULL) {
312 			(void) sprintf(errbuf,
313 			    gettext("%s needs 'd', 'h', 'm', or 's' (%s)"),
314 			    do_invalid(), str);
315 			error_str = errbuf;
316 			return (-1);
317 		} else {
318 			ret = *strxx;
319 			*strxx = '\0';
320 		}
321 		if (strlen(str) != strspn(str, "0123456789")) {
322 			(void) sprintf(errbuf,
323 			    gettext("%s non-numeric offset (%s)"),
324 			    do_invalid(), str);
325 			error_str = errbuf;
326 			return (-1);
327 		}
328 		factor = 1;			/* seconds is default */
329 		if (ret == 'd')			/* days */
330 			factor = 24 * 60 * 60;
331 		else if (ret == 'h')		/* hours */
332 			factor = 60 * 60;
333 		else if (ret == 'm')		/* minutes */
334 			factor = 60;
335 		lnum = atol(str);
336 		m_before = m_after + (lnum * factor);
337 		return (0);
338 	}
339 	/*
340 	 * Must be a specific date/time format.
341 	 */
342 	if (derive_date(str, &thentime))
343 		return (-1);
344 	/*
345 	 * For 'd' option clear out the hh:mm:ss to get to the start of the day.
346 	 * Then add one day's worth of seconds to get the 'b' time.
347 	 */
348 	if (opt == 'd') {
349 		thentime.tm_sec = 0;
350 		thentime.tm_min = 0;
351 		thentime.tm_hour = 0;
352 		m_after = local_to_gm(&thentime);
353 		m_before = m_after + (24 * 60 * 60);
354 	} else if (opt == 'a') {
355 		m_after = local_to_gm(&thentime);
356 	} else if (opt == 'b') {
357 		m_before = local_to_gm(&thentime);
358 	}
359 	return (0);
360 }
361 
362 
363 /*
364  * .func	derive_date.
365  * .desc	Derive a date/time structure (tm) from a string.
366  *	String is in one of these formats:
367  *	[yy]yymmddhhmmss
368  *	[yy]yymmddhhmm
369  *	[yy]yymmddhh
370  *	[yy]yymmdd
371  * .call	ret = derive_date(str, tme).
372  * .arg	str	- ptr to input string.
373  * .arg	tme	- ptr to tm structure (time.h).
374  * .ret	0	- no errors in string.
375  * .ret	-1	- errors in string (description in error_str).
376  */
377 int
378 derive_date(char *str, struct tm *tme)
379 {
380 	char	*strs;
381 	char	*digits = "0123456789";
382 	size_t	len;
383 	struct tm nowtime;
384 
385 	len = strlen(str);
386 
387 	if (len != strspn(str, digits)) {
388 		(void) sprintf(errbuf, gettext("%s not all digits (%s)"),
389 		    do_invalid(), str);
390 		error_str = errbuf;
391 		return (-1);
392 	}
393 	if (len % 2) {
394 		(void) sprintf(errbuf, gettext("%s odd number of digits (%s)"),
395 		    do_invalid(), str);
396 		error_str = errbuf;
397 		return (-1);
398 	}
399 	/*
400 	 * May need larger string storage to add '19' or '20'.
401 	 */
402 	strs = (char *)a_calloc(1, len + 4);
403 
404 	/*
405 	 * Get current time to see what century it is.
406 	 */
407 	(void) memcpy((char *)&nowtime, (char *)gmtime(&time_now),
408 	    sizeof (nowtime));
409 	/*
410 	 * If the year does not begin with '19' or '20', then report
411 	 * an error and abort.
412 	 */
413 	if ((str[0] != '1' || str[1] != '9') &&		/* 19XX */
414 	    (str[0] != '2' || str[1] != '0')) {		/* 20XX */
415 		(void) sprintf(errbuf, gettext("invalid year (%c%c%c%c)"),
416 		    str[0], str[1], str[2], str[3]);
417 		error_str = errbuf;
418 		free(strs);
419 		return (-1);
420 	}
421 
422 	len = strlen(str);			/* may have changed */
423 	if (len < 8 || len > 14) {
424 		(void) sprintf(errbuf,
425 			gettext("invalid date/time length (%s)"), str);
426 		error_str = errbuf;
427 		free(strs);
428 		return (-1);
429 	}
430 	/* unspecified values go to 0 */
431 	(void) memset((void *) tme, 0, (size_t)sizeof (*tme));
432 	(void) strncpy(strs, str, 4);
433 	strs[4] = '\0';
434 	tme->tm_year = atoi(strs) - 1900;	/* get the year */
435 	(void) strncpy(strs, str + 4, 2);
436 	strs[2] = '\0';
437 	tme->tm_mon = atoi(strs) - 1;		/* get months */
438 	(void) strncpy(strs, str + 6, 2);
439 	strs[2] = '\0';
440 	tme->tm_mday = atoi(strs);		/* get days */
441 	if (len >= 10) {			/* yyyymmddhh */
442 		(void) strncpy(strs, str + 8, 2);
443 		strs[2] = '\0';
444 		tme->tm_hour = atoi(strs);	/* get hours */
445 	}
446 	if (len >= 12) {			/* yyyymmddhhmm */
447 		(void) strncpy(strs, str + 10, 2);
448 		strs[2] = '\0';
449 		tme->tm_min = atoi(strs);	/* get minutes */
450 	}
451 	if (len >= 14) {			/* yyyymmddhhmmss */
452 		(void) strncpy(strs, str + 12, 2);
453 		strs[2] = '\0';
454 		tme->tm_sec = atoi(strs);	/* get seconds */
455 	}
456 	free(strs);
457 	return (check_time(tme));		/* lastly check the ranges */
458 }
459 
460 
461 /*
462  * .func	derive_str - derive string.
463  * .desc	Derive a string representation of a time for a filename.
464  *	The output is in the 14 character format yyyymmddhhmmss.
465  * .call	derive_str(clock, buf).
466  * .arg	clock	- seconds since epoch.
467  * .arg	buf	- place to put resultant string.
468  * .ret	void.
469  */
470 void
471 derive_str(time_t clock, char *buf)
472 {
473 	struct tm gtime;
474 
475 	(void) memcpy((void *) & gtime, (void *)gmtime(&clock), sizeof (gtime));
476 
477 	(void) sprintf(buf, "%4d", gtime.tm_year + 1900);
478 	(void) sprintf(buf + 4,  "%.2d", gtime.tm_mon + 1);
479 	(void) sprintf(buf + 6,  "%.2d", gtime.tm_mday);
480 	(void) sprintf(buf + 8,  "%.2d", gtime.tm_hour);
481 	(void) sprintf(buf + 10, "%.2d", gtime.tm_min);
482 	(void) sprintf(buf + 12, "%.2d", gtime.tm_sec);
483 	buf[14] = '\0';
484 }
485 
486 
487 int
488 days_in_year(int year)
489 {
490 	if (isleap(year))
491 		return (366);
492 
493 	return (365);
494 }
495