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