2 * Empire - A multi-player, client/server Internet based war game.
3 * Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
4 * Ken Stevens, Steve McClure, Markus Armbruster
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.
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.
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/>.
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.
27 * rdsched.c: Read update schedule
29 * Known contributors to this file:
30 * Markus Armbruster, 2007-2011
33 /* Required for strptime() */
34 #define _XOPEN_SOURCE 500
44 #include "prototypes.h"
46 static int parse_schedule_line(char *, time_t[], int, time_t, time_t *,
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);
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.
64 read_schedule(char *fname, time_t sched[], int n, time_t t0, time_t anchor)
72 fp = fopen(fname, "r");
74 logerror("Can't open %s for reading (%s)\n",
75 fname, strerror(errno));
83 while (fgets(buf, sizeof(buf), fp) != NULL) {
85 endp = strchr(buf, '#');
88 if (parse_schedule_line(buf, sched, n, t0, &anchor,
89 fname ? fname : "<stdin>", lno)) {
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.
107 parse_schedule_line(char *line, time_t sched[], int n,
108 time_t t0, time_t *anchor,
109 char *fname, int lno)
115 if ((endp = parse_time(&t, line, anchor))) {
116 if (!time_ok(t, fname, lno))
119 insert_update(t, sched, n, t0);
120 } else if ((endp = parse_every(&delta, line))) {
121 if ((p = parse_until(&u, endp, anchor))) {
123 if (!time_ok(u, fname, lno))
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))
135 delete_update(t, sched, n);
140 while (isspace(*endp)) endp++;
143 logerror("%s:%d: unintelligible\n", fname, lno);
145 logerror("%s:%d: trailing junk\n", fname, lno);
153 * Complain and return zero when @t is bad, else return non-zero.
154 * @fname and @lno file name and line number.
157 time_ok(time_t t, char *fname, int lno)
159 if (t == (time_t)-1) {
160 logerror("%s:%d: time weird\n", fname, lno);
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.
173 parse_time(time_t *t, char *s, time_t *anchor)
175 static char *fmt[] = {
177 * Absolute time formats
178 * Must set tm_year, tm_mon, tm_mday, tm_hour, tm_min.
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 */
184 * Relative to anchor formats
185 * Must set tm_wday, may set tm_hour and tm_min
187 "next %a %H:%M ", /* next Fri 15:35 */
188 "next %a ", /* next Fri */
193 struct tm tm, nexttm;
195 for (p = s; isspace(*(unsigned char *)p); ++p) ;
201 * Carefully initialize tm so we can tell what strptime()
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.
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);
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)
219 && (tm.tm_hour == -1 || tm.tm_min != -1))))
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;
232 nexttm.tm_mday += tm.tm_wday - nexttm.tm_wday;
233 if (tm.tm_wday <= nexttm.tm_wday)
244 * Parse an every clause from @s into *@secs.
245 * Return pointer to first character not parsed on success,
246 * null pointer on failure.
249 parse_every(time_t *secs, char *s)
255 sscanf(s, " every %u hours%n", &delta, &nch);
259 sscanf(s, " every %u minutes%n", &delta, &nch);
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.
273 parse_until(time_t *t, char *s, time_t *anchor)
278 sscanf(s, " until%n", &nch);
281 return parse_time(t, s + nch, anchor);
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.
291 parse_skip(time_t *t, char *s, time_t *anchor)
296 sscanf(s, " skip%n", &nch);
299 return parse_time(t, s + nch, anchor);
303 * Return the index of the first update at or after @t in @sched[].
306 find_update(time_t t, time_t sched[])
310 /* Could use binary search here, but it's hardly worth it */
311 for (i = 0; sched[i] && t > sched[i]; i++) ;
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[].
323 insert_update(time_t t, time_t sched[], int n, time_t t0)
327 if (t <= t0 || !gamehours(t))
330 i = find_update(t, sched);
331 memmove(sched + i + 1, sched + i, (n - 1 - i) * sizeof(*sched));
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[].
343 delete_update(time_t t, time_t sched[], int n)
345 int i = find_update(t, sched);
347 memmove(sched + i, sched + i + 1, (n - 1 - i) * sizeof(*sched));