xref: /freebsd/usr.sbin/newsyslog/ptimes.c (revision 2f8d7c56da1ce2c5069fab78876cc6a90b343cfa)
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$");
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 
51 /*-
52  * Parse a limited subset of ISO 8601. The specific format is as follows:
53  *
54  * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
55  *
56  * We don't accept a timezone specification; missing fields (including timezone)
57  * are defaulted to the current date but time zero.
58  */
59 time_t
60 parse8601(const char *s, time_t *next_time)
61 {
62 	char *t;
63 	time_t tsecs;
64 	struct tm tm, *tmp;
65 	long l;
66 
67 	tmp = localtime(&timenow);
68 	tm = *tmp;
69 	if (next_time != NULL)
70 		*next_time = (time_t)-1;
71 
72 	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
73 
74 	l = strtol(s, &t, 10);
75 	if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
76 		return (-1);
77 
78 	/*
79 	 * Now t points either to the end of the string (if no time was
80 	 * provided) or to the letter `T' which separates date and time in
81 	 * ISO 8601.  The pointer arithmetic is the same for either case.
82 	 */
83 	switch (t - s) {
84 	case 8:
85 		tm.tm_year = ((l / 1000000) - 19) * 100;
86 		l = l % 1000000;
87 	case 6:
88 		tm.tm_year -= tm.tm_year % 100;
89 		tm.tm_year += l / 10000;
90 		l = l % 10000;
91 	case 4:
92 		tm.tm_mon = (l / 100) - 1;
93 		l = l % 100;
94 	case 2:
95 		tm.tm_mday = l;
96 	case 0:
97 		break;
98 	default:
99 		return (-1);
100 	}
101 
102 	/* sanity check */
103 	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
104 	    || tm.tm_mday < 1 || tm.tm_mday > 31)
105 		return (-1);
106 
107 	if (*t != '\0') {
108 		s = ++t;
109 		l = strtol(s, &t, 10);
110 		if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
111 			return (-1);
112 
113 		switch (t - s) {
114 		case 6:
115 			tm.tm_sec = l % 100;
116 			l /= 100;
117 		case 4:
118 			tm.tm_min = l % 100;
119 			l /= 100;
120 		case 2:
121 			tm.tm_hour = l;
122 		case 0:
123 			break;
124 		default:
125 			return (-1);
126 		}
127 
128 		/* sanity check */
129 		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
130 		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
131 			return (-1);
132 	}
133 
134 	tsecs = mktime(&tm);
135 	/*
136 	 * Check for invalid times, including things like the missing
137 	 * hour when switching from "standard time" to "daylight saving".
138 	 */
139 	if (tsecs == (time_t)-1)
140 		tsecs = (time_t)-2;
141 	return (tsecs);
142 }
143 
144 /*-
145  * Parse a cyclic time specification, the format is as follows:
146  *
147  *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
148  *
149  * to rotate a logfile cyclic at
150  *
151  *	- every day (D) within a specific hour (hh)	(hh = 0...23)
152  *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
153  *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
154  *
155  * We don't accept a timezone specification; missing fields
156  * are defaulted to the current date but time zero.
157  */
158 time_t
159 parseDWM(char *s, time_t *next_time)
160 {
161 	char *t;
162 	time_t tsecs;
163 	struct tm tm, *tmp;
164 	long l;
165 	int nd;
166 	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
167 	int WMseen = 0;
168 	int Dseen = 0;
169 
170 	tmp = localtime(&timenow);
171 	tm = *tmp;
172 	if (next_time != NULL)
173 		*next_time = (time_t)-1;
174 
175 	/* set no. of days per month */
176 
177 	nd = mtab[tm.tm_mon];
178 
179 	if (tm.tm_mon == 1) {
180 		if (((tm.tm_year + 1900) % 4 == 0) &&
181 		    ((tm.tm_year + 1900) % 100 != 0) &&
182 		    ((tm.tm_year + 1900) % 400 == 0)) {
183 			nd++;	/* leap year, 29 days in february */
184 		}
185 	}
186 	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
187 
188 	for (;;) {
189 		switch (*s) {
190 		case 'D':
191 			if (Dseen)
192 				return (-1);
193 			Dseen++;
194 			s++;
195 			l = strtol(s, &t, 10);
196 			if (l < 0 || l > 23)
197 				return (-1);
198 			tm.tm_hour = l;
199 			break;
200 
201 		case 'W':
202 			if (WMseen)
203 				return (-1);
204 			WMseen++;
205 			s++;
206 			l = strtol(s, &t, 10);
207 			if (l < 0 || l > 6)
208 				return (-1);
209 			if (l != tm.tm_wday) {
210 				int save;
211 
212 				if (l < tm.tm_wday) {
213 					save = 6 - tm.tm_wday;
214 					save += (l + 1);
215 				} else {
216 					save = l - tm.tm_wday;
217 				}
218 
219 				tm.tm_mday += save;
220 
221 				if (tm.tm_mday > nd) {
222 					tm.tm_mon++;
223 					tm.tm_mday = tm.tm_mday - nd;
224 				}
225 			}
226 			break;
227 
228 		case 'M':
229 			if (WMseen)
230 				return (-1);
231 			WMseen++;
232 			s++;
233 			if (tolower(*s) == 'l') {
234 				tm.tm_mday = nd;
235 				s++;
236 				t = s;
237 			} else {
238 				l = strtol(s, &t, 10);
239 				if (l < 1 || l > 31)
240 					return (-1);
241 
242 				if (l > nd)
243 					return (-1);
244 				tm.tm_mday = l;
245 			}
246 			break;
247 
248 		default:
249 			return (-1);
250 			break;
251 		}
252 
253 		if (*t == '\0' || isspace(*t))
254 			break;
255 		else
256 			s = t;
257 	}
258 
259 	tsecs = mktime(&tm);
260 	/*
261 	 * Check for invalid times, including things like the missing
262 	 * hour when switching from "standard time" to "daylight saving".
263 	 */
264 	if (tsecs == (time_t)-1)
265 		tsecs = (time_t)-2;
266 	return (tsecs);
267 }
268