2 * Empire - A multi-player, client/server Internet based war game.
3 * Copyright (C) 1986-2021, 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
41 #include "prototypes.h"
43 static int parse_schedule_line(char *, time_t[], int, time_t, time_t *,
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);
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.
61 read_schedule(char *fname, time_t sched[], int n, time_t t0, time_t anchor)
69 fp = fopen(fname, "r");
71 logerror("Can't open %s for reading (%s)\n",
72 fname, strerror(errno));
80 while (fgets(buf, sizeof(buf), fp) != NULL) {
82 endp = strchr(buf, '#');
85 if (parse_schedule_line(buf, sched, n, t0, &anchor,
86 fname ? fname : "<stdin>", lno)) {
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.
104 parse_schedule_line(char *line, time_t sched[], int n,
105 time_t t0, time_t *anchor,
106 char *fname, int lno)
112 if ((endp = parse_time(&t, line, anchor))) {
113 if (!time_ok(t, fname, lno))
116 insert_update(t, sched, n, t0);
117 } else if ((endp = parse_every(&delta, line))) {
118 if ((p = parse_until(&u, endp, anchor))) {
120 if (!time_ok(u, fname, lno))
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))
132 delete_update(t, sched, n);
137 while (isspace(*endp)) endp++;
140 logerror("%s:%d: unintelligible\n", fname, lno);
142 logerror("%s:%d: trailing junk\n", fname, lno);
150 * Complain and return zero when @t is bad, else return non-zero.
151 * @fname and @lno file name and line number.
154 time_ok(time_t t, char *fname, int lno)
156 if (t == (time_t)-1) {
157 logerror("%s:%d: time weird\n", fname, lno);
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.
170 parse_time(time_t *t, char *s, time_t *anchor)
172 static char *fmt[] = {
174 * Absolute time formats
175 * Must set tm_year, tm_mon, tm_mday, tm_hour, tm_min.
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 */
181 * Relative to anchor formats
182 * Must set tm_wday, may set tm_hour and tm_min
184 "next %a %H:%M ", /* next Fri 15:35 */
185 "next %a ", /* next Fri */
190 struct tm tm, nexttm;
192 for (p = s; isspace(*(unsigned char *)p); ++p) ;
198 * Carefully initialize tm so we can tell what strptime()
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.
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);
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)
216 && (tm.tm_hour == -1 || tm.tm_min != -1))))
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;
229 nexttm.tm_mday += tm.tm_wday - nexttm.tm_wday;
230 if (tm.tm_wday <= nexttm.tm_wday)
241 * Parse an every clause from @s into *@secs.
242 * Return pointer to first character not parsed on success,
243 * null pointer on failure.
246 parse_every(time_t *secs, char *s)
252 sscanf(s, " every %u hours%n", &delta, &nch);
256 sscanf(s, " every %u minutes%n", &delta, &nch);
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.
270 parse_until(time_t *t, char *s, time_t *anchor)
275 sscanf(s, " until%n", &nch);
278 return parse_time(t, s + nch, anchor);
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.
288 parse_skip(time_t *t, char *s, time_t *anchor)
293 sscanf(s, " skip%n", &nch);
296 return parse_time(t, s + nch, anchor);
300 * Return the index of the first update at or after @t in @sched[].
303 find_update(time_t t, time_t sched[])
307 /* Could use binary search here, but it's hardly worth it */
308 for (i = 0; sched[i] && t > sched[i]; i++) ;
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[].
320 insert_update(time_t t, time_t sched[], int n, time_t t0)
324 if (t <= t0 || !gamehours(t))
327 i = find_update(t, sched);
328 memmove(sched + i + 1, sched + i, (n - 1 - i) * sizeof(*sched));
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[].
340 delete_update(time_t t, time_t sched[], int n)
342 int i = find_update(t, sched);
344 memmove(sched + i, sched + i + 1, (n - 1 - i) * sizeof(*sched));