]> git.pond.sub.org Git - empserver/blob - src/lib/common/xundump.c
(getobj, setnum): Move override of cur_id from getobj() to setnum().
[empserver] / src / lib / common / xundump.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  *  xundump.c: Load back xdump output
29  * 
30  *  Known contributors to this file:
31  *     Ron Koenderink, 2005
32  *     Markus Armbruster, 2005-2007
33  */
34
35 /*
36  * FIXME:
37  * - Normalize terminology: table/rows/columns or file/records/fields
38  * - Loading tables with NSC_STRING elements more than once leaks memory
39  * TODO:
40  * - Check each partial table supplies the same rows
41  * - Check EFF_CFG tables are dense
42  * - Symbolic references to non-symbol tables
43  * - Symbolic array indexes
44  * TODO, but hardly worth the effort:
45  * - Permit reordering of array elements
46  * - Permit repetition of array elements in split tables
47  */
48
49 #include <config.h>
50
51 #include <ctype.h>
52 #include <stdarg.h>
53 #include <stdio.h>
54 #include <time.h>
55 #include "file.h"
56 #include "match.h"
57 #include "nsc.h"
58 #include "optlist.h"
59 #include "prototypes.h"
60
61 static char *fname;
62 static int lineno;
63 static int human;
64 static int ellipsis, is_partial;
65 static int cur_type, cur_id;
66 static void *cur_obj;
67 static int cur_obj_is_blank;
68 static int nflds;
69 static struct castr **fldca;
70 static int *fldidx;
71 static int *caflds;
72 static unsigned char *caseen;
73
74 static int gripe(char *, ...) ATTRIBUTE((format (printf, 1, 2)));
75 static int deffld(int, char *, int);
76 static int defellipsis(int fldno);
77 static int chkflds(void);
78 static int setnum(int, double);
79 static int setstr(int, char *);
80 static int xunsymbol(char *, struct castr *, int);
81 static int setsym(int, char *);
82 static int mtsymset(int, long *);
83 static int add2symset(int, long *, char *);
84 static int xubody(FILE *);
85 static int xutail(FILE *, int, struct castr *);
86
87 static int
88 gripe(char *fmt, ...)
89 {
90     va_list ap;
91
92     fprintf(stderr, "%s:%d: ", fname, lineno);
93     va_start(ap, fmt);
94     vfprintf(stderr, fmt, ap);
95     va_end(ap);
96     putc('\n', stderr);
97
98     return -1;
99 }
100
101 static int
102 skipfs(FILE *fp)
103 {
104     int ch;
105
106     do {
107         ch = getc(fp);
108     } while (ch == ' ' || ch == '\t');
109
110     if (ch == '#') {
111         do {
112             ch = getc(fp);
113         } while (ch != EOF && ch != '\n');
114     }
115
116     return ch;
117 }
118
119 static int
120 getid(FILE *fp, char *buf)
121 {
122     int n;
123     if (fscanf(fp, "%1023[^#()<>=#\" \t\n]%n", buf, &n) != 1
124         || !isalpha(buf[0]))
125         return -1;
126     return n;
127 }
128
129 static char *
130 xuesc(char *buf)
131 {
132     char *src, *dst;
133     int octal_chr, n;
134
135     dst = buf;
136     src = buf;
137     while (*src) {
138         if (*src == '\\') {
139             if (sscanf(++src, "%3o%n", &octal_chr, &n) != 1 || n != 3)
140                 return NULL;
141             *dst++ = (char)octal_chr;
142             src += 3;
143         } else
144             *dst++ = *src++;
145     }
146     *dst = '\0';
147     return buf;
148 }
149
150 static int
151 xufldname(FILE *fp, int i)
152 {
153     int ch, idx;
154     char buf[1024];
155
156     ch = skipfs(fp);
157     switch (ch) {
158     case EOF:
159         return gripe("Unexpected EOF");
160     case '\n':
161         if (chkflds() < 0)
162             return -1;
163         lineno++;
164         return 0;
165     case '.':
166         if (getc(fp) != '.' || getc(fp) != '.')
167             return gripe("Junk in header field %d", i + 1);
168         if (i == 0)
169             return gripe("... not allowed in field 1");
170         if (defellipsis(i) < 0)
171             return -1;
172         ch = skipfs(fp);
173         if (ch != EOF && ch != '\n')
174             return gripe("Junk after ...");
175         ungetc(ch, fp);
176         return 1;
177     default:
178         ungetc(ch, fp);
179         if (getid(fp, buf) < 0)
180             return gripe("Junk in header field %d", i + 1);
181         ch = getc(fp);
182         if (ch != '(') {
183             ungetc(ch, fp);
184             return deffld(i, buf, -1);
185         }
186         ch = getc(fp);
187         ungetc(ch, fp);
188         if (isdigit(ch) || ch == '-' || ch == '+') {
189             if (fscanf(fp, "%d", &idx) != 1)
190                 return gripe("Malformed number in index of header field %d",
191                              i + 1);
192             if (idx < 0)
193                 return gripe("Index must not be negative in header field %d",
194                              i + 1);
195         } else {
196             if (getid(fp, buf) < 0)
197                 return gripe("Malformed index in header field %d", i + 1);
198             return gripe("Symbolic index in header field %d not yet implemented",
199                          i + 1);
200         }
201         ch = getc(fp);
202         if (ch != ')')
203             return gripe("Malformed index in header field %d", i + 1);
204         return deffld(i, buf, idx);
205     }
206 }
207
208 static int
209 xufld(FILE *fp, int i)
210 {
211     int ch;
212     char buf[1024];
213     double dbl;
214     long set;
215
216     ch = skipfs(fp);
217     switch (ch) {
218     case EOF:
219         return gripe("Unexpected EOF");
220     case '\n':
221         if (i != nflds) {
222             if (fldca[i]->ca_type != NSC_STRINGY && fldca[i]->ca_len)
223                 return gripe("Field %s(%d) missing",
224                              fldca[i]->ca_name, fldidx[i]);
225             return gripe("Field %s missing", fldca[i]->ca_name);
226         }
227         lineno++;
228         return 0;
229     case '+': case '-': case '.':
230     case '0': case '1': case '2': case '3': case '4':
231     case '5': case '6': case '7': case '8': case '9':
232         ungetc(ch, fp);
233         if (fscanf(fp, "%lg", &dbl) != 1)
234             return gripe("Malformed number in field %d", i + 1);
235         return setnum(i, dbl);
236     case '"':
237         ch = getc(fp);
238         if (ch == '"')
239             buf[0] = 0;
240         else {
241             ungetc(ch, fp);
242             if (fscanf(fp, "%1023[^\"\n]", buf) != 1 || getc(fp) != '"')
243                 return gripe("Malformed string in field %d", i + 1);
244             if (!xuesc(buf))
245                 return gripe("Invalid escape sequence in field %d",
246                              i + 1);
247         }
248         return setstr(i, buf);
249     case '(':
250         if (mtsymset(i, &set) < 0)
251             return -1;
252         for (;;) {
253             ch = skipfs(fp);
254             if (ch == EOF || ch == '\n')
255                 return gripe("Unmatched '(' in field %d", i + 1);
256             if (ch == ')')
257                 break;
258             ungetc(ch, fp);
259             if (getid(fp, buf) < 0)
260                 return gripe("Junk in field %d", i + 1);
261             if (add2symset(i, &set, buf) < 0)
262                 return -1;
263         }
264         return setnum(i, set);
265     default:
266         ungetc(ch, fp);
267         if (getid(fp, buf) < 0)
268             return gripe("Junk in field %d", i + 1);
269         if (!strcmp(buf, "nil"))
270             return setstr(i, NULL);
271         else
272             return setsym(i, buf);
273     }
274 }
275
276 static int
277 xuflds(FILE *fp, int (*parse)(FILE *, int))
278 {
279     int i, ch, res;
280
281     for (i = 0; ; i++) {
282         res = parse(fp, i);
283         if (res < 0)
284             return -1;
285         if (res == 0)
286             return i;
287         ch = getc(fp);
288         if (ch == '\n')
289             ungetc(ch, fp);
290         else if (ch != ' ' && ch != '\t')
291             return gripe("Bad field separator after field %d", i + 1);
292     }
293 }
294
295 static int
296 deffld(int fldno, char *name, int idx)
297 {
298     struct castr *ca = ef_cadef(cur_type);
299     int res;
300
301     res = stmtch(name, ca, offsetof(struct castr, ca_name),
302                      sizeof(struct castr));
303     if (res < 0)
304         return gripe("Header %s of field %d is %s", name, fldno + 1,
305                      res == M_NOTUNIQUE ? "ambiguous" : "unknown");
306     if (ca[res].ca_type != NSC_STRINGY && ca[res].ca_len != 0) {
307         if (idx < 0)
308             return gripe("Header %s requires an index in field %d",
309                          ca[res].ca_name, fldno + 1);
310         if (idx >= ca[res].ca_len)
311             return gripe("Header %s(%d) index out of bounds in field %d",
312                          ca[res].ca_name, idx, fldno + 1);
313         if (idx < caflds[res])
314             return gripe("Duplicate header %s(%d) in field %d",
315                          ca[res].ca_name, idx, fldno + 1);
316         if (idx > caflds[res])
317             return gripe("Expected header %s(%d) in field %d",
318                          ca[res].ca_name, caflds[res], fldno + 1);
319     } else {
320         if (idx >= 0)
321             return gripe("Header %s doesn't take an index in field %d",
322                          ca[res].ca_name, fldno + 1);
323         idx = 0;
324         if (caflds[res] && !caseen[res])
325             /* FIXME doesn't catch dupes within table part when caseen[res] */
326             return gripe("Duplicate header %s in field %d",
327                          ca[res].ca_name, fldno + 1);
328     }
329     fldca[fldno] = &ca[res];
330     fldidx[fldno] = idx;
331     if (!caseen[res])
332         caflds[res]++;
333     return 1;
334 }
335
336 static int
337 defellipsis(int fldno)
338 {
339     struct castr *ca = ef_cadef(cur_type);
340
341     if (ca[0].ca_table != cur_type)
342         return gripe("Table %s doesn't support ...", ef_nameof(cur_type));
343     ellipsis = fldno;
344     is_partial = 1;
345     return 0;
346 }
347
348 static int
349 chkflds(void)
350 {
351     struct castr *ca = ef_cadef(cur_type);
352     int i, len, res = 0;
353
354     if (is_partial) {
355         /* Require index field */
356         if (!caflds[0])
357             return gripe("Header field %s required with ...", ca[0].ca_name);
358         /* Want the index field again in continued table: */
359         caflds[0] = 0;
360         return 0;
361     }
362
363     for (i = 0; ca[i].ca_name; i++) {
364         if (ca[i].ca_flags & NSC_EXTRA)
365             continue;
366         len = ca[i].ca_type != NSC_STRINGY ? ca[i].ca_len : 0;
367         if (!len && !caflds[i])
368             res = gripe("Header field %s missing", ca[i].ca_name);
369         else if (len && caflds[i] == len - 1)
370             res = gripe("Header field %s(%d) missing",
371                         ca[i].ca_name, len - 1);
372         else if (len && caflds[i] < len - 1)
373             res = gripe("Header fields %s(%d) ... %s(%d) missing",
374                         ca[i].ca_name, caflds[i], ca[i].ca_name, len - 1);
375     }
376
377     return res;
378 }
379
380 static struct castr *
381 getfld(int fldno, int *idx)
382 {
383     if (fldno >= nflds) {
384         gripe("Too many fields, expected only %d", nflds);
385         return NULL;
386     }
387     if (CANT_HAPPEN(fldno < 0))
388         return NULL;
389     if (idx)
390         *idx = fldidx[fldno];
391     return fldca[fldno];
392 }
393
394 static int
395 fldval_must_match(int fldno)
396 {
397     struct castr *ca = ef_cadef(cur_type);
398     int i = fldca[fldno] - ca;
399
400     return (!cur_obj_is_blank && (fldca[fldno]->ca_flags & NSC_CONST))
401         || caseen[i];
402 }
403
404 static void *
405 getobj(void)
406 {
407     struct empfile *ep = &empfile[cur_type];
408     int need_sentinel = !EF_IS_GAME_STATE(cur_type);
409
410     if (!cur_obj) {
411         cur_obj_is_blank = cur_id >= ep->fids;
412         if (cur_obj_is_blank) {
413             /* TODO grow cache (and posssibly file) unless EFF_STATIC */
414             if (cur_id < ep->csize - !!need_sentinel)
415                 ep->cids = ep->fids = cur_id + 1;
416             /* else: ef_ptr() will fail */
417         }
418         cur_obj = ef_ptr(cur_type, cur_id);
419         if (!cur_obj)
420             gripe("Can't put ID %d into table %s, it holds only 0..%d.",
421                   cur_id, ep->name, ep->fids - 1);
422     }
423
424     return cur_obj;
425 }
426
427 static int
428 setnum(int fldno, double dbl)
429 {
430     struct castr *ca;
431     int idx;
432     char *memb_ptr;
433     double old;
434
435     ca = getfld(fldno, &idx);
436     if (!ca)
437         return -1;
438
439     /*
440      * If this is the record index, put it into cur_id.
441      */
442     if (fldno == 0 && ca->ca_table == cur_type)
443         cur_id = (int)dbl;
444
445     memb_ptr = getobj();
446     if (!memb_ptr)
447         return -1;
448     memb_ptr += ca->ca_off;
449
450     /* FIXME check assignment preserves value */
451     switch (ca->ca_type) {
452     case NSC_CHAR:
453         old = ((signed char *)memb_ptr)[idx];
454         ((signed char *)memb_ptr)[idx] = (signed char)dbl;
455         break;
456     case NSC_UCHAR:
457     case NSC_HIDDEN:
458         old = ((unsigned char *)memb_ptr)[idx];
459         ((unsigned char *)memb_ptr)[idx] = (unsigned char)dbl;
460         break;
461     case NSC_SHORT:
462         old = ((short *)memb_ptr)[idx];
463         ((short *)memb_ptr)[idx] = (short)dbl;
464         break;
465     case NSC_USHORT:
466         old = ((unsigned short *)memb_ptr)[idx];
467         ((unsigned short *)memb_ptr)[idx] = (unsigned short)dbl;
468         break;
469     case NSC_INT:
470         old = ((int *)memb_ptr)[idx];
471         ((int *)memb_ptr)[idx] = (int)dbl;
472         break;
473     case NSC_LONG:
474         old = ((long *)memb_ptr)[idx];
475         ((long *)memb_ptr)[idx] = (long)dbl;
476         break;
477     case NSC_XCOORD:
478         old = ((coord *)memb_ptr)[idx];
479         /* FIXME use variant of xrel() that takes orig instead of nation */
480         if (old >= WORLD_X / 2)
481             old -= WORLD_X;
482         ((coord *)memb_ptr)[idx] = XNORM((coord)dbl);
483         break;
484     case NSC_YCOORD:
485         old = ((coord *)memb_ptr)[idx];
486         /* FIXME use variant of yrel() that takes orig instead of nation */
487         if (old >= WORLD_Y / 2)
488             old -= WORLD_Y;
489         ((coord *)memb_ptr)[idx] = YNORM((coord)dbl);
490         break;
491     case NSC_FLOAT:
492         old = ((float *)memb_ptr)[idx];
493         ((float *)memb_ptr)[idx] = (float)dbl;
494         break;
495     case NSC_DOUBLE:
496         old = ((double *)memb_ptr)[idx];
497         ((double *)memb_ptr)[idx] = dbl;
498         break;
499     case NSC_TIME:
500         old = ((time_t *)memb_ptr)[idx];
501         ((time_t *)memb_ptr)[idx] = (time_t)dbl;
502         break;
503     default:
504         return gripe("Field %d doesn't take numbers", fldno + 1);
505     }
506
507     if (fldval_must_match(fldno) && old != dbl)
508         return gripe("Value for field %d must be %g", fldno + 1, old);
509
510     return 1;
511 }
512
513 static int
514 setstr(int fldno, char *str)
515 {
516     struct castr *ca;
517     int must_match, idx;
518     size_t len;
519     char *memb_ptr, *old;
520
521     ca = getfld(fldno, &idx);
522     if (!ca)
523         return -1;
524
525     memb_ptr = getobj();
526     if (!memb_ptr)
527         return -1;
528     memb_ptr += ca->ca_off;
529     must_match = fldval_must_match(fldno);
530
531     switch (ca->ca_type) {
532     case NSC_STRING:
533         old = ((char **)memb_ptr)[idx];
534         if (!must_match)
535             ((char **)memb_ptr)[idx] = str ? strdup(str) : NULL;
536         len = 65535;            /* really SIZE_MAX, but that's C99 */
537         break;
538     case NSC_STRINGY:
539         if (CANT_HAPPEN(idx))
540             return -1;
541         if (!str)
542             return gripe("Field %d doesn't take nil", fldno + 1);
543         len = ca->ca_len;
544         if (strlen(str) > len)
545             return gripe("Field %d takes at most %d characters",
546                          fldno + 1, (int)len);
547         old = memb_ptr;
548         if (!must_match)
549             strncpy(memb_ptr, str, len);
550         break;
551     default:
552         return gripe("Field %d doesn't take strings", fldno + 1);
553     }
554
555     if (must_match) {
556         if (old && (!str || strncmp(old, str, len)))
557             return gripe("Value for field %d must be \"%.*s\"",
558                          fldno + 1, (int)len, old);
559         if (!old && str)
560             return gripe("Value for field %d must be nil", fldno + 1);
561     }
562
563     return 1;
564 }
565
566 static int
567 xunsymbol(char *id, struct castr *ca, int n)
568 {
569     int i = ef_elt_byname(ca->ca_table, id);
570     if (i < 0)
571         return gripe("%s %s symbol `%s' in field %d",
572                      i == M_NOTUNIQUE ? "Ambiguous" : "Unknown",
573                      ca->ca_name, id, n + 1);
574     return i;
575 }
576
577 static int
578 symval(struct castr *ca, int i)
579 {
580     int type = ca->ca_table;
581
582     if (ef_check(type) < 0)
583         return -1;
584     if (ef_cadef(type) == symbol_ca)
585         /* symbol table, value is in the table */
586         return ((struct symbol *)ef_ptr(type, i))->value;
587     /* value is the table index */
588     return i;
589 }
590
591 static int
592 setsym(int fldno, char *sym)
593 {
594     struct castr *ca;
595     int i;
596
597     ca = getfld(fldno, NULL);
598     if (!ca)
599         return -1;
600
601     if (ca->ca_table == EF_BAD || (ca->ca_flags & NSC_BITS))
602         return gripe("Field %d doesn't take symbols", fldno + 1);
603
604     i = xunsymbol(sym, ca, fldno);
605     if (i < 0)
606         return -1;
607     return setnum(fldno, symval(ca, i));
608 }
609
610 static int
611 mtsymset(int fldno, long *set)
612 {
613     struct castr *ca;
614
615     ca = getfld(fldno, NULL);
616     if (!ca)
617         return -1;
618
619     if (ca->ca_table == EF_BAD || ef_cadef(ca->ca_table) != symbol_ca
620         || !(ca->ca_flags & NSC_BITS))
621         return gripe("Field %d doesn't take symbol sets", fldno + 1);
622     *set = 0;
623     return 0;
624 }
625
626 static int
627 add2symset(int fldno, long *set, char *sym)
628 {
629     struct castr *ca;
630     int i;
631
632     ca = getfld(fldno, NULL);
633     if (!ca)
634         return -1;
635
636     i = xunsymbol(sym, ca, fldno);
637     if (i < 0)
638         return -1;
639     *set |= symval(ca, i);
640     return 0;
641 }
642
643 static int
644 xuheader(FILE *fp, int expected_table)
645 {
646     char name[64];
647     int res, ch;
648     int type;
649
650     while ((ch = skipfs(fp)) == '\n')
651         lineno++;
652     if (ch == EOF && expected_table == EF_BAD)
653         return -2;
654     ungetc(ch, fp);
655
656     human = ch == 'c';
657     res = -1;
658     if ((human
659          ? fscanf(fp, "config%*[ \t]%63[^ \t#\n]%n", name, &res) != 1
660          : fscanf(fp, "XDUMP%*[ \t]%63[^ \t#\n]%*[ \t]%*[^ \t#\n]%n",
661                   name, &res) != 1) || res < 0)
662         return gripe("Expected xdump header");
663
664     type = ef_byname(name);
665     if (type < 0)
666         return gripe("Unknown table `%s'", name);
667     if (expected_table != EF_BAD && expected_table != type)
668         return gripe("Expected table `%s', not `%s'",
669                      ef_nameof(expected_table), name);
670
671     if (!ef_cadef(type) || !(ef_flags(type) & EFF_MEM)) {
672         CANT_HAPPEN(expected_table != EF_BAD);
673         return gripe("Table `%s' is not permitted here", name);
674     }
675
676     if (skipfs(fp) != '\n')
677         return gripe("Junk after xdump header");
678     lineno++;
679
680     return type;
681 }
682
683 static int
684 xufldhdr(FILE *fp, struct castr ca[])
685 {
686     struct castr **fca;
687     int *fidx;
688     int ch, i, j, n;
689
690     if (human) {
691         while ((ch = skipfs(fp)) == '\n')
692             lineno++;
693         ungetc(ch, fp);
694         ellipsis = 0;
695         nflds = xuflds(fp, xufldname);
696         if (nflds < 0)
697             return -1;
698         nflds -= ellipsis != 0;
699     } else {
700         fca = fldca;
701         fidx = fldidx;
702
703         for (i = 0; ca[i].ca_name; i++) {
704             if ((ca[i].ca_flags & NSC_EXTRA))
705                 continue;
706             n = ca[i].ca_type != NSC_STRINGY ? ca[i].ca_len : 0;
707             j = 0;
708             do {
709                 *fca++ = &ca[i];
710                 *fidx++ = j;
711             } while (++j < n);
712         }
713
714         nflds = fidx - fldidx;
715     }
716
717     return 0;
718 }
719
720 static int
721 xufooter(FILE *fp, int row)
722 {
723     int rows, res;
724
725     res = -1;
726     if (human) {
727         if (fscanf(fp, "config%n",  &res) != 0 || res < 0)
728             return gripe("Malformed table footer");
729     } else {
730         if (fscanf(fp, "%d", &rows) != 1)
731             return gripe("Malformed table footer");
732         if (row != rows)
733             return gripe("Read %d rows, which doesn't match footer "
734                          "%d rows", row, rows);
735     }
736     if (skipfs(fp) != '\n')
737         return gripe("Junk after table footer");
738     lineno++;
739
740     return 0;
741 }
742
743 int
744 xundump(FILE *fp, char *file, int *plno, int expected_table)
745 {
746     struct castr *ca;
747     int type, nca, nf, i, ch;
748
749     fname = file;
750     lineno = *plno;
751
752     if ((type = xuheader(fp, expected_table)) < 0)
753         return type;
754
755     ca = ef_cadef(type);
756     if (CANT_HAPPEN(!ca))
757         return -1;
758
759     nca = nf = 0;
760     for (i = 0; ca[i].ca_name; i++) {
761         nca++;
762         if (!(ca[i].ca_flags & NSC_EXTRA))
763             nf += MAX(1, ca[i].ca_type != NSC_STRINGY ? ca[i].ca_len : 0);
764     }
765     fldca = calloc(nf, sizeof(*fldca));
766     fldidx = calloc(nf, sizeof(*fldidx));
767     caflds = calloc(nca, sizeof(*caflds));
768     caseen = calloc(nca, sizeof(*caseen));
769     cur_type = type;
770
771     if (xutail(fp, type, ca) < 0)
772         type = EF_BAD;
773
774     free(caseen);
775     free(caflds);
776     free(fldidx);
777     free(fldca);
778
779     /* Skip empty lines so that callers can easily check for EOF */
780     while ((ch = skipfs(fp)) == '\n')
781         lineno++;
782     ungetc(ch, fp);
783
784     *plno = lineno;
785     return type;
786 }
787
788 static int
789 xutail(FILE *fp, int type, struct castr *ca)
790 {
791     int recs, i;
792
793     is_partial = 0;
794     for (;;) {
795         if (xufldhdr(fp, ca) < 0)
796             return -1;
797         if ((recs = xubody(fp)) < 0)
798             return -1;
799         if (xufooter(fp, recs) < 0)
800             return -1;
801         if (!ellipsis)
802             return 0;
803         for (i = 0; ca[i].ca_name; i++)
804             caseen[i] = caflds[i] != 0;
805         if (xuheader(fp, type) < 0)
806             return -1;
807     }
808 }
809
810 static int
811 xubody(FILE *fp)
812 {
813     struct empfile *ep = &empfile[cur_type];
814     int need_sentinel = !EF_IS_GAME_STATE(cur_type);
815     int row, n, ch;
816
817     n = 0;
818     for (row = 0;; ++row) {
819         while ((ch = skipfs(fp)) == '\n')
820             lineno++;
821         if (ch == '/')
822             break;
823         ungetc(ch, fp);
824         cur_obj = NULL;
825         cur_id = row;
826         if (xuflds(fp, xufld) < 0)
827             return -1;
828         n = MAX(n, cur_id + 1);
829     }
830
831     if (CANT_HAPPEN(n > ep->fids))
832         n = ep->fids;
833     if (n < ep->fids) {
834         if (EF_IS_GAME_STATE(cur_type) && n != ep->csize)
835             /* TODO truncate file */
836             gripe("Warning: should resize table %s from %d to %d, not implemented",
837                   ef_nameof(cur_type), ep->csize, n);
838         else if (cur_type >= EF_SHIP_CHR && cur_type <= EF_NUKE_CHR)
839             ep->cids = ep->fids = n;
840         else
841             return gripe("Table %s requires %d rows, got %d",
842                          ef_nameof(cur_type), ep->fids, n);
843     }
844
845     if (need_sentinel) {
846         if (CANT_HAPPEN(n >= ep->csize))
847             return gripe("No space for sentinel");
848         memset(ep->cache + ep->size * n, 0, ep->size);
849     }
850
851     return row;
852 }