/* * Empire - A multi-player, client/server Internet based war game. * Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak, * Ken Stevens, Steve McClure, Markus Armbruster * * Empire is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * --- * * See files README, COPYING and CREDITS in the root of the source * tree for related information and legal notices. It is expected * that future projects/authors will amend these files as needed. * * --- * * rdsched.c: Read update schedule * * Known contributors to this file: * Markus Armbruster, 2007-2011 */ #define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include "prototypes.h" static int parse_schedule_line(char *, time_t[], int, time_t, time_t *, char *, int); static int time_ok(time_t, char *, int); static char *parse_time(time_t *, char *, time_t *); static char *parse_every(time_t *, char *); static char *parse_until(time_t *, char *, time_t *); static char *parse_skip(time_t *, char *, time_t *); static int insert_update(time_t, time_t[], int, time_t); static int delete_update(time_t, time_t[], int); /* * Read update schedule from file FNAME. * Put the first N-1 updates after T0 into SCHED[] in ascending order, * terminated with a zero. * Use ANCHOR as initial anchor for anchor-relative times. * Return 0 on success, -1 on failure. */ int read_schedule(char *fname, time_t sched[], int n, time_t t0, time_t anchor) { FILE *fp; int ret, lno; char buf[1024]; char *endp; if (fname) { fp = fopen(fname, "r"); if (!fp) { logerror("Can't open %s for reading (%s)\n", fname, strerror(errno)); return -1; } } else fp = stdin; ret = lno = 0; sched[0] = 0; while (fgets(buf, sizeof(buf), fp) != NULL) { ++lno; endp = strchr(buf, '#'); if (endp) *endp = 0; if (parse_schedule_line(buf, sched, n, t0, &anchor, fname ? fname : "", lno)) { ret = -1; break; } } if (fname) fclose(fp); return ret; } /* * Parse an update schedule directive from LINE. * Update SCHED[] and ANCHOR accordingly. * SCHED[] holds the first N-1 updates after T0 in ascending order. * FNAME and LNO file name and line number for reporting errors. */ static int parse_schedule_line(char *line, time_t sched[], int n, time_t t0, time_t *anchor, char *fname, int lno) { char *endp, *p; int bol; time_t t, delta, u; if ((endp = parse_time(&t, line, anchor))) { if (!time_ok(t, fname, lno)) return -1; *anchor = t; insert_update(t, sched, n, t0); } else if ((endp = parse_every(&delta, line))) { if ((p = parse_until(&u, endp, anchor))) { endp = p; if (!time_ok(u, fname, lno)) return -1; } else u = (time_t)-1; t = *anchor; do { t += delta; } while ((u == (time_t)-1 || t <= u) && insert_update(t, sched, n, t0) < n - 1); } else if ((endp = parse_skip(&t, line, anchor))) { if (!time_ok(t, fname, lno)) return -1; delete_update(t, sched, n); } else endp = line; bol = endp == line; while (isspace(*endp)) endp++; if (*endp) { if (bol) logerror("%s:%d: unintelligible\n", fname, lno); else logerror("%s:%d: trailing junk\n", fname, lno); return -1; } return 0; } /* * Complain and return zero when T is bad, else return non-zero. * FNAME and LNO file name and line number. */ static int time_ok(time_t t, char *fname, int lno) { if (t == (time_t)-1) { logerror("%s:%d: time weird\n", fname, lno); return 0; } return 1; } /* * Parse a time from S into *T. * *ANCHOR is the base for anchor-relative time. * Return pointer to first character not parsed on success, * null pointer on failure. */ static char * parse_time(time_t *t, char *s, time_t *anchor) { static char *fmt[] = { /* * Absolute time formats * Must set tm_year, tm_mon, tm_mday, tm_hour, tm_min. */ "%Y-%m-%d %H:%M ", /* ISO 8601 */ "%b %d %H:%M %Y ", /* like ctime(): Dec 22 15:35 2006 */ "%d %b %Y %H:%M ", /* like RFC 2822: 22 Dec 2006 15:35 */ /* * Relative to anchor formats * Must set tm_wday, may set tm_hour and tm_min */ "next %a %H:%M ", /* next Fri 15:35 */ "next %a ", /* next Fri */ NULL }; char *p, *endp; int i; struct tm tm, nexttm; for (p = s; isspace(*(unsigned char *)p); ++p) ; for (i = 0; ; i++) { if (!fmt[i]) return NULL; /* * Carefully initialize tm so we can tell what strptime() * actually set. * * Beware: some losing implementations of strptime() happily * succeed when they fully consumed the first argument, * regardless of whether they matched the full second argument * or not. Observed on FreeBSD 6.2. */ memset(&tm, 0, sizeof(tm)); tm.tm_hour = -1; /* to recognize anchor-relat. w/o time */ tm.tm_year = INT_MIN; /* strptime() lossage work-around */ tm.tm_mon = tm.tm_mday = tm.tm_min = tm.tm_wday = -1; /* ditto */ endp = strptime(p, fmt[i], &tm); if (endp /* strptime() lossage work-around: */ && ((tm.tm_year != INT_MIN && tm.tm_mon != -1 && tm.tm_mday != -1 && tm.tm_hour != -1 && tm.tm_min != -1) || (tm.tm_wday != -1 && (tm.tm_hour == -1 || tm.tm_min != -1)))) break; } if (tm.tm_mday == -1) { /* relative to anchor */ nexttm = *localtime(anchor); if (tm.tm_hour >= 0) { /* got hour and minute */ nexttm.tm_hour = tm.tm_hour; nexttm.tm_min = tm.tm_min; nexttm.tm_sec = 0; } nexttm.tm_mday += tm.tm_wday - nexttm.tm_wday; if (tm.tm_wday <= nexttm.tm_wday) nexttm.tm_mday += 7; tm = nexttm; } tm.tm_isdst = -1; *t = mktime(&tm); return endp; } /* * Parse an every clause from S into *SECS. * Return pointer to first character not parsed on success, * null pointer on failure. */ static char * parse_every(time_t *secs, char *s) { int nch; unsigned delta; nch = -1; sscanf(s, " every %u hours%n", &delta, &nch); if (nch >= 0) delta *= 60; else sscanf(s, " every %u minutes%n", &delta, &nch); if (nch < 0) return NULL; *secs = 60 * delta; return s + nch; } /* * Parse an until clause from S into *T. * *ANCHOR is the base for anchor-relative time. * Return pointer to first character not parsed on success, * null pointer on failure. */ static char * parse_until(time_t *t, char *s, time_t *anchor) { int nch; nch = -1; sscanf(s, " until%n", &nch); if (nch < 0) return NULL; return parse_time(t, s + nch, anchor); } /* * Parse an skip clause from S into *T. * *ANCHOR is the base for anchor-relative time. * Return pointer to first character not parsed on success, * null pointer on failure. */ static char * parse_skip(time_t *t, char *s, time_t *anchor) { int nch; nch = -1; sscanf(s, " skip%n", &nch); if (nch < 0) return NULL; return parse_time(t, s + nch, anchor); } /* * Return the index of the first update at or after T in SCHED[]. */ static int find_update(time_t t, time_t sched[]) { int i; /* Could use binary search here, but it's hardly worth it */ for (i = 0; sched[i] && t > sched[i]; i++) ; return i; } /* * Insert update at T into SCHED[]. * SCHED[] holds the first N-1 updates after T0 in ascending order. * If T is before T0 or outside game_days/game_hours, return -1. * If there's no space for T in SCHED[], return N-1. * Else insert T into SCHED[] and return its index in SCHED[]. */ static int insert_update(time_t t, time_t sched[], int n, time_t t0) { int i; if (t <= t0 || !gamehours(t)) return -1; i = find_update(t, sched); memmove(sched + i + 1, sched + i, (n - 1 - i) * sizeof(*sched)); sched[i] = t; sched[n - 1] = 0; return i; } /* * Delete update T from SCHED[]. * SCHED[] holds N-1 updates in ascending order. * Return the index of the first update after T in SCHED[]. */ static int delete_update(time_t t, time_t sched[], int n) { int i = find_update(t, sched); if (t == sched[i]) memmove(sched + i, sched + i + 1, (n - 1 - i) * sizeof(*sched)); return i; }