ptimes.c revision 120046
1/*-
2 * ------+---------+---------+---------+---------+---------+---------+---------*
3 * Initial version of parse8601 was originally added to newsyslog.c in
4 *     FreeBSD on Jan 22, 1999 by Garrett Wollman <wollman@FreeBSD.org>.
5 * Initial version of parseDWM was originally added to newsyslog.c in
6 *     FreeBSD on Apr  4, 2000 by Hellmuth Michaelis <hm@FreeBSD.org>.
7 *
8 * Copyright (c) 2003  - Garance Alistair Drosehn <gad@FreeBSD.org>.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *   1. Redistributions of source code must retain the above copyright
15 *      notice, this list of conditions and the following disclaimer.
16 *   2. Redistributions in binary form must reproduce the above copyright
17 *      notice, this list of conditions and the following disclaimer in the
18 *      documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 * The views and conclusions contained in the software and documentation
33 * are those of the authors and should not be interpreted as representing
34 * official policies, either expressed or implied, of the FreeBSD Project.
35 *
36 * ------+---------+---------+---------+---------+---------+---------+---------*
37 */
38
39#include <sys/cdefs.h>
40__FBSDID("$FreeBSD: head/usr.sbin/newsyslog/ptimes.c 120046 2003-09-14 00:56:50Z gad $");
41
42#include <ctype.h>
43#include <limits.h>
44#include <stdio.h>
45#include <stdint.h>
46#include <stdlib.h>
47#include <time.h>
48
49#include "extern.h"
50
51static int	 days_pmonth(int month, int year);
52
53/*
54 * Simple routine to calculate the number of days in a given month.
55 */
56static int
57days_pmonth(int month, int year)
58{
59	static const int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31,
60	    30, 31, 30, 31};
61	int ndays;
62
63	ndays = mtab[month];
64
65	if (month == 1) {
66		/*
67		 * We are usually called with a 'tm-year' value
68		 * (ie, the value = the number of years past 1900).
69		 */
70		if (year < 1900)
71			year += 1900;
72		if (year % 4 == 0) {
73			/*
74			 * This is a leap year, as long as it is not a
75			 * multiple of 100, or if it is a multiple of
76			 * both 100 and 400.
77			 */
78			if (year % 100 != 0)
79				ndays++;	/* not multiple of 100 */
80			else if (year % 400 == 0)
81				ndays++;	/* is multiple of 100 and 400 */
82		}
83	}
84	return (ndays);
85}
86
87/*-
88 * Parse a limited subset of ISO 8601. The specific format is as follows:
89 *
90 * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
91 *
92 * We don't accept a timezone specification; missing fields (including timezone)
93 * are defaulted to the current date but time zero.
94 */
95time_t
96parse8601(const char *s, time_t *next_time)
97{
98	char *t;
99	time_t tsecs;
100	struct tm tm, *tmp;
101	long l;
102
103	tmp = localtime(&timenow);
104	tm = *tmp;
105	if (next_time != NULL)
106		*next_time = (time_t)-1;
107
108	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
109
110	l = strtol(s, &t, 10);
111	if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
112		return (-1);
113
114	/*
115	 * Now t points either to the end of the string (if no time was
116	 * provided) or to the letter `T' which separates date and time in
117	 * ISO 8601.  The pointer arithmetic is the same for either case.
118	 */
119	switch (t - s) {
120	case 8:
121		tm.tm_year = ((l / 1000000) - 19) * 100;
122		l = l % 1000000;
123	case 6:
124		tm.tm_year -= tm.tm_year % 100;
125		tm.tm_year += l / 10000;
126		l = l % 10000;
127	case 4:
128		tm.tm_mon = (l / 100) - 1;
129		l = l % 100;
130	case 2:
131		tm.tm_mday = l;
132	case 0:
133		break;
134	default:
135		return (-1);
136	}
137
138	/* sanity check */
139	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
140	    || tm.tm_mday < 1 || tm.tm_mday > 31)
141		return (-1);
142
143	if (*t != '\0') {
144		s = ++t;
145		l = strtol(s, &t, 10);
146		if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
147			return (-1);
148
149		switch (t - s) {
150		case 6:
151			tm.tm_sec = l % 100;
152			l /= 100;
153		case 4:
154			tm.tm_min = l % 100;
155			l /= 100;
156		case 2:
157			tm.tm_hour = l;
158		case 0:
159			break;
160		default:
161			return (-1);
162		}
163
164		/* sanity check */
165		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
166		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
167			return (-1);
168	}
169
170	tsecs = mktime(&tm);
171	/*
172	 * Check for invalid times, including things like the missing
173	 * hour when switching from "standard time" to "daylight saving".
174	 */
175	if (tsecs == (time_t)-1)
176		tsecs = (time_t)-2;
177	return (tsecs);
178}
179
180/*-
181 * Parse a cyclic time specification, the format is as follows:
182 *
183 *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
184 *
185 * to rotate a logfile cyclic at
186 *
187 *	- every day (D) within a specific hour (hh)	(hh = 0...23)
188 *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
189 *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
190 *
191 * We don't accept a timezone specification; missing fields
192 * are defaulted to the current date but time zero.
193 */
194time_t
195parseDWM(char *s, time_t *next_time)
196{
197	int daysmon;
198	char *t;
199	time_t tsecs;
200	struct tm tm, *tmp;
201	long l;
202	int WMseen = 0;
203	int Dseen = 0;
204
205	tmp = localtime(&timenow);
206	tm = *tmp;
207	if (next_time != NULL)
208		*next_time = (time_t)-1;
209
210	/* Save away the number of days in this month */
211	daysmon = days_pmonth(tm.tm_mon, tm.tm_year);
212	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
213
214	for (;;) {
215		switch (*s) {
216		case 'D':
217			if (Dseen)
218				return (-1);
219			Dseen++;
220			s++;
221			l = strtol(s, &t, 10);
222			if (l < 0 || l > 23)
223				return (-1);
224			tm.tm_hour = l;
225			break;
226
227		case 'W':
228			if (WMseen)
229				return (-1);
230			WMseen++;
231			s++;
232			l = strtol(s, &t, 10);
233			if (l < 0 || l > 6)
234				return (-1);
235			if (l != tm.tm_wday) {
236				int save;
237
238				if (l < tm.tm_wday) {
239					save = 6 - tm.tm_wday;
240					save += (l + 1);
241				} else {
242					save = l - tm.tm_wday;
243				}
244
245				tm.tm_mday += save;
246
247				if (tm.tm_mday > daysmon) {
248					tm.tm_mon++;
249					tm.tm_mday = tm.tm_mday - daysmon;
250				}
251			}
252			break;
253
254		case 'M':
255			if (WMseen)
256				return (-1);
257			WMseen++;
258			s++;
259			if (tolower(*s) == 'l') {
260				tm.tm_mday = daysmon;
261				s++;
262				t = s;
263			} else {
264				l = strtol(s, &t, 10);
265				if (l < 1 || l > 31)
266					return (-1);
267
268				if (l > daysmon)
269					return (-1);
270				tm.tm_mday = l;
271			}
272			break;
273
274		default:
275			return (-1);
276			break;
277		}
278
279		if (*t == '\0' || isspace(*t))
280			break;
281		else
282			s = t;
283	}
284
285	tsecs = mktime(&tm);
286	/*
287	 * Check for invalid times, including things like the missing
288	 * hour when switching from "standard time" to "daylight saving".
289	 */
290	if (tsecs == (time_t)-1)
291		tsecs = (time_t)-2;
292	return (tsecs);
293}
294