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