]> git.pond.sub.org Git - empserver/blob - src/lib/common/rdsched.c
8875c15ac1d6ce2defb11155d6b11b04f608820f
[empserver] / src / lib / common / rdsched.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2007, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                           Ken Stevens, Steve McClure
5  *
6  *  This program 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 2 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, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  ---
21  *
22  *  See files README, COPYING and CREDITS in the root of the source
23  *  tree for related information and legal notices.  It is expected
24  *  that future projects/authors will amend these files as needed.
25  *
26  *  ---
27  *
28  *  rdsched.c: Read update schedule
29  * 
30  *  Known contributors to this file:
31  *     Markus Armbruster, 2007
32  */
33
34 #define _XOPEN_SOURCE 500
35
36 #include <config.h>
37
38 #include <ctype.h>
39 #include <errno.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 lno = 0;
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         fname = "<stdin>";
80     }
81
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, fname, lno))
89             return -1;
90     }
91
92     fclose(fp);
93     return 0;
94 }
95
96 /*
97  * Parse an update schedule directive from LINE.
98  * Update SCHED[] and ANCHOR accordingly.
99  * SCHED[] holds the first N-1 updates after T0 in ascending order.
100  * FNAME and LNO file name and line number for reporting errors.
101  */
102 static int
103 parse_schedule_line(char *line, time_t sched[], int n,
104                     time_t t0, time_t *anchor,
105                     char *fname, int lno)
106 {
107     char *endp, *p;
108     int bol;
109     time_t t, delta, u;
110
111     if ((endp = parse_time(&t, line, anchor))) {
112         if (!time_ok(t, fname, lno))
113             return -1;
114         *anchor = t;
115         insert_update(t, sched, n, t0);
116     } else if ((endp = parse_every(&delta, line))) {
117         if ((p = parse_until(&u, endp, anchor))) {
118             endp = p;
119             if (!time_ok(u, fname, lno))
120                 return -1;
121         } else
122             u = (time_t)-1;
123         t = *anchor;
124         do {
125             t += delta;
126         } while ((u == (time_t)-1 || t <= u)
127                  && insert_update(t, sched, n, t0) < n - 1);
128     } else if ((endp = parse_skip(&t, line, anchor))) {
129         if (!time_ok(t, fname, lno))
130             return -1;
131         delete_update(t, sched, n);
132     } else
133         endp = line;
134
135     bol = endp == line;
136     while (isspace(*endp)) endp++;
137     if (*endp) {
138         if (bol)
139             logerror("%s:%d: unintelligible\n", fname, lno);
140         else
141             logerror("%s:%d: trailing junk\n", fname, lno);
142         return -1;
143     }
144
145     return 0;
146 }
147
148 /*
149  * Complain and return zero when T is bad, else return non-zero.
150  * FNAME and LNO file name and line number.
151  */
152 static int
153 time_ok(time_t t, char *fname, int lno)
154 {
155     if (t == (time_t)-1) {
156         logerror("%s:%d: time weird\n", fname, lno);
157         return 0;
158     }
159     return 1;
160 }
161
162 /*
163  * Parse a time from S into *T.
164  * *ANCHOR is the base for anchor-relative time.
165  * Return pointer to first character not parsed on success,
166  * null pointer on failure.
167  */
168 static char *
169 parse_time(time_t *t, char *s, time_t *anchor)
170 {
171     static char *fmt[] = {
172         "%Y-%m-%d %H:%M ",      /* ISO 8601 */
173         "%b %d %H:%M %Y ",      /* like ctime(): Dec 22 15:35 2006 */
174         "%d %b %Y %H:%M ",      /* 22 Dec 2006 15:35 */
175         "next %a %H:%M ",       /* next Fri 15:35 */
176         "next %a ",             /* next Fri */
177         NULL
178     };
179     char *p, *endp;
180     int i;
181     struct tm tm, nexttm;
182
183     for (p = s; isspace(*(unsigned char *)p); ++p) ;
184
185     for (i = 0; ; i++) {
186         if (!fmt[i])
187             return NULL;
188         memset(&tm, 0, sizeof(tm));
189         tm.tm_hour = -1;
190         endp = strptime(p, fmt[i], &tm);
191         if (endp)
192             break;
193     }
194
195     if (tm.tm_mday == 0) {
196         /* relative to anchor */
197         nexttm = *localtime(anchor);
198         if (tm.tm_hour >= 0) {
199             /* got hour and minute */
200             nexttm.tm_hour = tm.tm_hour;
201             nexttm.tm_min = tm.tm_min;
202             nexttm.tm_sec = 0;
203         }
204         nexttm.tm_mday += tm.tm_wday - nexttm.tm_wday;
205         if (tm.tm_wday <= nexttm.tm_wday)
206             nexttm.tm_mday += 7;
207         tm = nexttm;
208     }
209
210     tm.tm_isdst = -1;
211     *t = mktime(&tm);
212     return endp;
213 }
214
215 /*
216  * Parse an every clause from S into *SECS.
217  * Return pointer to first character not parsed on success,
218  * null pointer on failure.
219  */
220 static char *
221 parse_every(time_t *secs, char *s)
222 {
223     int nch, delta;
224
225     nch = -1;
226     sscanf(s, " every %u hours%n", &delta, &nch);
227     if (nch >= 0)
228         delta *= 60;
229     else
230         sscanf(s, " every %u minutes%n", &delta, &nch);
231     if (nch < 0)
232         return NULL;    *secs = 60 * delta;
233     return s + nch;
234 }
235
236 /*
237  * Parse an until clause from S into *T.
238  * *ANCHOR is the base for anchor-relative time.
239  * Return pointer to first character not parsed on success,
240  * null pointer on failure.
241  */
242 static char *
243 parse_until(time_t *t, char *s, time_t *anchor)
244 {
245     int nch;
246
247     nch = -1;
248     sscanf(s, " until%n", &nch);
249     if (nch < 0)
250         return NULL;
251     return parse_time(t, s + nch, anchor);
252 }
253
254 /*
255  * Parse an skip clause from S into *T.
256  * *ANCHOR is the base for anchor-relative time.
257  * Return pointer to first character not parsed on success,
258  * null pointer on failure.
259  */
260 static char *
261 parse_skip(time_t *t, char *s, time_t *anchor)
262 {
263     int nch;
264
265     nch = -1;
266     sscanf(s, " skip%n", &nch);
267     if (nch < 0)
268         return NULL;
269     return parse_time(t, s + nch, anchor);
270 }
271
272 /*
273  * Return the index of the first update at or after T in SCHED[].
274  */
275 static int
276 find_update(time_t t, time_t sched[])
277 {
278     int i;
279
280     /* Could use binary search here, but it's hardly worth it */
281     for (i = 0; sched[i] && t > sched[i]; i++) ;
282     return i;
283 }
284
285 /*
286  * Insert update at T into SCHED[].
287  * SCHED[] holds the first N-1 updates after T0 in ascending order.
288  * If T is before T0 or outside game_days/game_hours, return -1.
289  * If there's no space for T in SCHED[], return N-1.
290  * Else insert T into SCHED[] and return its index in SCHED[].
291  */
292 static int
293 insert_update(time_t t, time_t sched[], int n, time_t t0)
294 {
295     int i;
296
297     if (t <= t0 || !gamehours(t))
298         return -1;
299
300     i = find_update(t, sched);
301     memmove(sched + i + 1, sched + i, (n - 1 - i) * sizeof(*sched));
302     sched[i] = t;
303     sched[n - 1] = 0;
304     return i;
305 }
306
307 /*
308  * Delete update T from SCHED[].
309  * SCHED[] holds N-1 updates in ascending order.
310  * Return the index of the first update after T in SCHED[].
311  */
312 static int
313 delete_update(time_t t, time_t sched[], int n)
314 {
315     int i = find_update(t, sched);
316     if (t == sched[i])
317         memmove(sched + i, sched + i + 1,
318                 (n - 1 - i) * sizeof(*sched));
319     return i;
320 }