]> git.pond.sub.org Git - empserver/blob - src/lib/common/rdsched.c
88f5c6e2348b3d9ab49333e46f50ac809a6660a4
[empserver] / src / lib / common / rdsched.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                Ken Stevens, Steve McClure, Markus Armbruster
5  *
6  *  Empire is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *  ---
20  *
21  *  See files README, COPYING and CREDITS in the root of the source
22  *  tree for related information and legal notices.  It is expected
23  *  that future projects/authors will amend these files as needed.
24  *
25  *  ---
26  *
27  *  rdsched.c: Read update schedule
28  *
29  *  Known contributors to this file:
30  *     Markus Armbruster, 2007-2011
31  */
32
33 /* Required for strptime() */
34 #define _XOPEN_SOURCE 500
35
36 #include <config.h>
37
38 #include <ctype.h>
39 #include <errno.h>
40 #include <limits.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <time.h>
44 #include "prototypes.h"
45
46 static int parse_schedule_line(char *, time_t[], int, time_t, time_t *,
47                                char *, int);
48 static int time_ok(time_t, char *, int);
49 static char *parse_time(time_t *, char *, time_t *);
50 static char *parse_every(time_t *, char *);
51 static char *parse_until(time_t *, char *, time_t *);
52 static char *parse_skip(time_t *, char *, time_t *);
53 static int insert_update(time_t, time_t[], int, time_t);
54 static int delete_update(time_t, time_t[], int);
55
56 /*
57  * Read update schedule from file @fname.
58  * Put the first @n-1 updates after @t0 into @sched[] in ascending order,
59  * terminated with a zero.
60  * Use @anchor as initial anchor for anchor-relative times.
61  * Return 0 on success, -1 on failure.
62  */
63 int
64 read_schedule(char *fname, time_t sched[], int n, time_t t0, time_t anchor)
65 {
66     FILE *fp;
67     int ret, lno;
68     char buf[1024];
69     char *endp;
70
71     if (fname) {
72         fp = fopen(fname, "r");
73         if (!fp) {
74             logerror("Can't open %s for reading (%s)\n",
75                      fname, strerror(errno));
76             return -1;
77         }
78     } else
79         fp = stdin;
80
81     ret = lno = 0;
82     sched[0] = 0;
83     while (fgets(buf, sizeof(buf), fp) != NULL) {
84         ++lno;
85         endp = strchr(buf, '#');
86         if (endp)
87             *endp = 0;
88         if (parse_schedule_line(buf, sched, n, t0, &anchor,
89                                 fname ? fname : "<stdin>", lno)) {
90             ret = -1;
91             break;
92         }
93     }
94
95     if (fname)
96         fclose(fp);
97     return ret;
98 }
99
100 /*
101  * Parse an update schedule directive from @line.
102  * Update @sched[] and @anchor accordingly.
103  * @sched[] holds the first N-1 updates after @t0 in ascending order.
104  * @fname and @lno file name and line number for reporting errors.
105  */
106 static int
107 parse_schedule_line(char *line, time_t sched[], int n,
108                     time_t t0, time_t *anchor,
109                     char *fname, int lno)
110 {
111     char *endp, *p;
112     int bol;
113     time_t t, delta, u;
114
115     if ((endp = parse_time(&t, line, anchor))) {
116         if (!time_ok(t, fname, lno))
117             return -1;
118         *anchor = t;
119         insert_update(t, sched, n, t0);
120     } else if ((endp = parse_every(&delta, line))) {
121         if ((p = parse_until(&u, endp, anchor))) {
122             endp = p;
123             if (!time_ok(u, fname, lno))
124                 return -1;
125         } else
126             u = (time_t)-1;
127         t = *anchor;
128         do {
129             t += delta;
130         } while ((u == (time_t)-1 || t <= u)
131                  && insert_update(t, sched, n, t0) < n - 1);
132     } else if ((endp = parse_skip(&t, line, anchor))) {
133         if (!time_ok(t, fname, lno))
134             return -1;
135         delete_update(t, sched, n);
136     } else
137         endp = line;
138
139     bol = endp == line;
140     while (isspace(*endp)) endp++;
141     if (*endp) {
142         if (bol)
143             logerror("%s:%d: unintelligible\n", fname, lno);
144         else
145             logerror("%s:%d: trailing junk\n", fname, lno);
146         return -1;
147     }
148
149     return 0;
150 }
151
152 /*
153  * Complain and return zero when @t is bad, else return non-zero.
154  * @fname and @lno file name and line number.
155  */
156 static int
157 time_ok(time_t t, char *fname, int lno)
158 {
159     if (t == (time_t)-1) {
160         logerror("%s:%d: time weird\n", fname, lno);
161         return 0;
162     }
163     return 1;
164 }
165
166 /*
167  * Parse a time from @s into *@t.
168  * *@anchor is the base for anchor-relative time.
169  * Return pointer to first character not parsed on success,
170  * null pointer on failure.
171  */
172 static char *
173 parse_time(time_t *t, char *s, time_t *anchor)
174 {
175     static char *fmt[] = {
176         /*
177          * Absolute time formats
178          * Must set tm_year, tm_mon, tm_mday, tm_hour, tm_min.
179          */
180         "%Y-%m-%d %H:%M ",      /* ISO 8601 */
181         "%b %d %H:%M %Y ",      /* like ctime(): Dec 22 15:35 2006 */
182         "%d %b %Y %H:%M ",      /* like RFC 2822: 22 Dec 2006 15:35 */
183         /*
184          * Relative to anchor formats
185          * Must set tm_wday, may set tm_hour and tm_min
186          */
187         "next %a %H:%M ",       /* next Fri 15:35 */
188         "next %a ",             /* next Fri */
189         NULL
190     };
191     char *p, *endp;
192     int i;
193     struct tm tm, nexttm;
194
195     for (p = s; isspace(*(unsigned char *)p); ++p) ;
196
197     for (i = 0; ; i++) {
198         if (!fmt[i])
199             return NULL;
200         /*
201          * Carefully initialize tm so we can tell what strptime()
202          * actually set.
203          *
204          * Beware: some losing implementations of strptime() happily
205          * succeed when they fully consumed the first argument,
206          * regardless of whether they matched the full second argument
207          * or not.  Observed on FreeBSD 6.2.
208          */
209         memset(&tm, 0, sizeof(tm));
210         tm.tm_hour = -1;        /* to recognize anchor-relat. w/o time */
211         tm.tm_year = INT_MIN;   /* strptime() lossage work-around */
212         tm.tm_mon = tm.tm_mday = tm.tm_min = tm.tm_wday = -1; /* ditto */
213         endp = strptime(p, fmt[i], &tm);
214         if (endp
215             /* strptime() lossage work-around: */
216             && ((tm.tm_year != INT_MIN && tm.tm_mon != -1
217                  && tm.tm_mday != -1 && tm.tm_hour != -1 && tm.tm_min != -1)
218                 || (tm.tm_wday != -1
219                     && (tm.tm_hour == -1 || tm.tm_min != -1))))
220             break;
221     }
222
223     if (tm.tm_mday == -1) {
224         /* relative to anchor */
225         nexttm = *localtime(anchor);
226         if (tm.tm_hour >= 0) {
227             /* got hour and minute */
228             nexttm.tm_hour = tm.tm_hour;
229             nexttm.tm_min = tm.tm_min;
230             nexttm.tm_sec = 0;
231         }
232         nexttm.tm_mday += tm.tm_wday - nexttm.tm_wday;
233         if (tm.tm_wday <= nexttm.tm_wday)
234             nexttm.tm_mday += 7;
235         tm = nexttm;
236     }
237
238     tm.tm_isdst = -1;
239     *t = mktime(&tm);
240     return endp;
241 }
242
243 /*
244  * Parse an every clause from @s into *@secs.
245  * Return pointer to first character not parsed on success,
246  * null pointer on failure.
247  */
248 static char *
249 parse_every(time_t *secs, char *s)
250 {
251     int nch;
252     unsigned delta;
253
254     nch = -1;
255     sscanf(s, " every %u hours%n", &delta, &nch);
256     if (nch >= 0)
257         delta *= 60;
258     else
259         sscanf(s, " every %u minutes%n", &delta, &nch);
260     if (nch < 0)
261         return NULL;
262     *secs = 60 * delta;
263     return s + nch;
264 }
265
266 /*
267  * Parse an until clause from @s into *@t.
268  * *@anchor is the base for anchor-relative time.
269  * Return pointer to first character not parsed on success,
270  * null pointer on failure.
271  */
272 static char *
273 parse_until(time_t *t, char *s, time_t *anchor)
274 {
275     int nch;
276
277     nch = -1;
278     sscanf(s, " until%n", &nch);
279     if (nch < 0)
280         return NULL;
281     return parse_time(t, s + nch, anchor);
282 }
283
284 /*
285  * Parse an skip clause from @s into *@t.
286  * *@anchor is the base for anchor-relative time.
287  * Return pointer to first character not parsed on success,
288  * null pointer on failure.
289  */
290 static char *
291 parse_skip(time_t *t, char *s, time_t *anchor)
292 {
293     int nch;
294
295     nch = -1;
296     sscanf(s, " skip%n", &nch);
297     if (nch < 0)
298         return NULL;
299     return parse_time(t, s + nch, anchor);
300 }
301
302 /*
303  * Return the index of the first update at or after @t in @sched[].
304  */
305 static int
306 find_update(time_t t, time_t sched[])
307 {
308     int i;
309
310     /* Could use binary search here, but it's hardly worth it */
311     for (i = 0; sched[i] && t > sched[i]; i++) ;
312     return i;
313 }
314
315 /*
316  * Insert update at @t into @sched[].
317  * @sched[] holds the first @n-1 updates after @t0 in ascending order.
318  * If @t is before @t0 or outside game_days/game_hours, return -1.
319  * If there's no space for @t in @sched[], return N-1.
320  * Else insert @t into @sched[] and return its index in @sched[].
321  */
322 static int
323 insert_update(time_t t, time_t sched[], int n, time_t t0)
324 {
325     int i;
326
327     if (t <= t0 || !gamehours(t))
328         return -1;
329
330     i = find_update(t, sched);
331     memmove(sched + i + 1, sched + i, (n - 1 - i) * sizeof(*sched));
332     sched[i] = t;
333     sched[n - 1] = 0;
334     return i;
335 }
336
337 /*
338  * Delete update @t from @sched[].
339  * @sched[] holds @n-1 updates in ascending order.
340  * Return the index of the first update after @t in @sched[].
341  */
342 static int
343 delete_update(time_t t, time_t sched[], int n)
344 {
345     int i = find_update(t, sched);
346     if (t == sched[i])
347         memmove(sched + i, sched + i + 1, (n - 1 - i) * sizeof(*sched));
348     return i;
349 }