]> git.pond.sub.org Git - empserver/blob - src/lib/common/xundump.c
Properly parse and validate column headers:
[empserver] / src / lib / common / xundump.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2006, 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
33  */
34
35 /* FIXME normalize terminology: table/rows/columns or file/records/fields */
36
37 #include <config.h>
38
39 #include <stdio.h>
40 #include <stdlib.h>
41
42 #include <ctype.h>
43 #include <string.h>
44 #include <stdarg.h>
45 #include <time.h>
46
47 #include "prototypes.h"
48 #include "file.h"
49 #include "nsc.h"
50 #include "match.h"
51
52 #define MAX_NUM_COLUMNS 256
53
54 static char *fname = "";
55 static int lineno = 0;
56 static int human;
57
58 /*
59  * TODO
60  * This structure could be replaced with struct valstr.
61  */
62 enum enum_value {
63     VAL_NOTUSED,
64     VAL_STRING,         /* uses v_string */
65     VAL_SYMBOL,         /* uses v_string */
66     VAL_SYMBOL_SET,     /* uses v_string */
67     VAL_INDEX_ID,       /* uses v_index_name and v_int */
68     VAL_INDEX_SYMBOL,   /* uses v_index_name and v_string */
69     VAL_DOUBLE          /* uses v_double */
70 };
71
72 struct value {
73     enum enum_value v_type;
74     union {
75         char *v_string;
76         double v_double;
77         int v_int;
78     } v_field;
79     char *v_index_name;
80 };
81
82 static int gripe(char *fmt, ...) ATTRIBUTE((format (printf, 1, 2)));
83
84 static int
85 gripe(char *fmt, ...)
86 {
87     va_list ap;
88
89     fprintf(stderr, "%s:%d: ", fname, lineno);
90     va_start(ap, fmt);
91     vfprintf(stderr, fmt, ap);
92     va_end(ap);
93     putc('\n', stderr);
94
95     return -1;
96 }
97
98 static int
99 skipfs(FILE *fp)
100 {
101     int ch;
102
103     do {
104         ch = getc(fp);
105     } while (ch == ' ' || ch == '\t');
106
107     if (ch == '#') {
108         do {
109             ch = getc(fp);
110         } while (ch != EOF && ch != '\n');
111     }
112
113     return ch;
114 }
115
116 static int
117 getid(FILE *fp, char *buf)
118 {
119     int n;
120     if (fscanf(fp, "%1023[^#() \t\n]%n", buf, &n) != 1 || !isalpha(buf[0]))
121         return -1;
122     return n;
123 }
124
125 static char *
126 xuesc(char *buf)
127 {
128     char *src, *dst;
129     int octal_chr, n;
130
131     dst = buf;
132     src = buf;
133     while (*src) {
134         if (*src == '\\') {
135             if (sscanf(++src, "%3o%n", &octal_chr, &n) != 1 || n != 3)
136                 return NULL;
137             *dst++ = (char)octal_chr;
138             src += 3;
139         } else
140             *dst++ = *src++;
141     }
142     *dst = '\0';
143     return buf;
144 }
145
146 static int
147 xuflds(FILE *fp, struct value values[])
148 {
149     int i, ch;
150     char buf[1024];
151     char *p;
152     int len, l1;
153
154     for (i = 0; ; i++) {
155         values[i].v_type = VAL_NOTUSED;
156         if (i >= MAX_NUM_COLUMNS)
157             return gripe("Too many columns");
158
159         ch = skipfs(fp);
160         switch (ch) {
161         case EOF:
162             return gripe("Unexpected EOF");
163         case '\n':
164             return i;
165         case '+': case '-': case '.':
166         case '0': case '1': case '2': case '3': case '4':
167         case '5': case '6': case '7': case '8': case '9':
168             ungetc(ch, fp);
169             if (fscanf(fp, "%lg", &values[i].v_field.v_double) != 1)
170                 return gripe("Malformed number in field %d", i + 1);
171             values[i].v_type = VAL_DOUBLE;
172             break;
173         case '"':
174             ch = getc(fp);
175             if (ch == '"')
176                 buf[0] = 0;
177             else {
178                 ungetc(ch, fp);
179                 if (fscanf(fp, "%1023[^\"\n]", buf) != 1 || getc(fp) != '"')
180                     return gripe("Malformed string in field %d", i + 1);
181                 if (!xuesc(buf))
182                     return gripe("Invalid escape sequence in field %d",
183                                  i + 1);
184             }
185             values[i].v_type = VAL_STRING;
186             values[i].v_field.v_string = strdup(buf);
187             break;
188         case '(':
189             p = strdup("");
190             len = 0;
191             for (;;) {
192                 ch = skipfs(fp);
193                 if (ch == EOF || ch == '\n')
194                     return gripe("Unmatched '(' in field %d", i + 1);
195                 if (ch == ')')
196                     break;
197                 ungetc(ch, fp);
198                 l1 = getid(fp, buf);
199                 if (l1 < 0)
200                     return gripe("Junk in field %d", i + 1);
201                 p = realloc(p, len + l1 + 2);
202                 strcpy(p + len, buf);
203                 strcpy(p + len + l1, " ");
204                 len += l1 + 1;
205             }
206             if (len)
207                 p[len - 1] = 0;
208             values[i].v_type = VAL_SYMBOL_SET;
209             values[i].v_field.v_string = p;
210             break;
211         default:
212             ungetc(ch, fp);
213             if (getid(fp, buf) < 0) {
214                 return gripe("Junk in field %d", i + 1);
215             }
216             ch = getc(fp);
217             ungetc(ch, fp);
218             if (ch == '(') {
219                 ch = getc(fp);
220                 ch = getc(fp);
221                 ungetc(ch, fp);
222                 if (isdigit(ch) || ch == '-') {
223                     if (fscanf(fp, "%d", &values[i].v_field.v_int) != 1) {
224                         return gripe("Malformed number in index field %d", i + 1);
225                     }
226                     values[i].v_index_name = strdup(buf);
227                     values[i].v_type = VAL_INDEX_ID;
228                 } else {
229                     values[i].v_index_name = strdup(buf);
230                     if (getid(fp, buf) < 0) {
231                         free(values[i].v_index_name);
232                         values[i].v_index_name = NULL;
233                         return gripe("Malformed string in index field %d", i + 1);
234                     }
235                     values[i].v_field.v_string = strdup(buf);
236                     values[i].v_type = VAL_INDEX_SYMBOL;
237                 }
238                 ch = getc(fp);
239                 if (ch != ')') {
240                     free(values[i].v_index_name);
241                     values[i].v_index_name = NULL;
242                     if (values[i].v_type == VAL_INDEX_SYMBOL)
243                         free(values[i].v_field.v_string);
244                     values[i].v_type = VAL_NOTUSED;
245                     return gripe("Malformed index field %d", i + 1);
246                 }
247             } else if (!strcmp(buf, "nil")) {
248                 values[i].v_type = VAL_STRING;
249                 values[i].v_field.v_string = NULL;
250             } else {
251                 values[i].v_type = VAL_SYMBOL;
252                 values[i].v_field.v_string = strdup(buf);
253             }
254         }
255         ch = getc(fp);
256         if (ch == '\n')
257             ungetc(ch, fp);
258         else if (ch != ' ' && ch != '\t') {
259             values[i].v_type = VAL_NOTUSED;
260             return gripe("Bad field separator after field %d", i + 1);
261         }
262     }
263 }
264
265 static void
266 freeflds(struct value values[])
267 {
268     struct value *vp;
269
270     for (vp = values; vp->v_type != VAL_NOTUSED; vp++) {
271         if (vp->v_type != VAL_DOUBLE && vp->v_type != VAL_INDEX_ID)
272             free(vp->v_field.v_string);
273     }
274     free(vp->v_index_name);
275     vp->v_index_name = NULL;
276 }
277
278 static int
279 xunsymbol1(char *id, struct symbol *symtab, struct castr *ca, int n)
280 {
281     int i = stmtch(id, symtab, offsetof(struct symbol, name),
282                    sizeof(struct symbol));
283     if (i < 0)
284         return gripe("%s %s symbol `%s' in field %d",
285                      i == M_NOTUNIQUE ? "Ambiguous" : "Unknown",
286                      ca->ca_name, id, n);
287     return i;
288 }
289
290 static int
291 xunsymbol(struct castr *ca, struct value *vp, int n)
292 {
293     struct symbol *symtab = (struct symbol *)empfile[ca->ca_table].cache;
294     char *buf = vp->v_field.v_string;
295     int i;
296     int value;
297     char *token;
298
299     if (ca->ca_table == EF_BAD || ef_cadef(ca->ca_table) != symbol_ca)
300         return gripe("%s doesn't take a symbol or symbol set in field %d",
301                      ca->ca_name, n);
302
303     if (vp->v_type == VAL_SYMBOL_SET) {
304         if (!(ca->ca_flags & NSC_BITS))
305             return gripe("%s doesn't take a symbol set in field %d",
306                          ca->ca_name, n);
307         value = 0;
308         for (token = strtok(buf, " "); token; token = strtok(NULL, " ")) {
309             i = xunsymbol1(token, symtab, ca, n);
310             if (i < 0)
311                 return -1;
312             value |= symtab[i].value;
313         }
314     } else if (vp->v_type == VAL_SYMBOL) {
315         if (ca->ca_flags & NSC_BITS)
316             return gripe("%s doesn't take a symbol in field %d",
317                          ca->ca_name, n);
318         i = xunsymbol1(buf, symtab, ca, n);
319         if (i < 0)
320             return -1;
321         value = symtab[i].value;
322     } else
323         return 0;
324
325     vp->v_type = VAL_DOUBLE;
326     vp->v_field.v_double = value;
327     return 0;
328 }
329
330 static int
331 has_const(struct castr ca[])
332 {
333     int i;
334
335     for (i = 0; ca[i].ca_name; i++) {
336         if (ca[i].ca_flags & NSC_CONST)
337             return 1;
338     }
339     return 0;
340 }
341
342 static void
343 xuinitrow(int type, int row)
344 {
345     struct empfile *ep = &empfile[type];
346     char *ptr = ep->cache + ep->size * row;
347
348     memset(ptr, 0, ep->size);
349
350     if (ep->init)
351         ep->init(row, ptr);
352 }
353
354 static int
355 xuloadrow(int type, int row, struct value values[])
356 {
357     int i,j,k;
358     struct empfile *ep = &empfile[type];
359     char *ptr = ep->cache + ep->size * row;
360     struct castr *ca = ep->cadef;
361     void *row_ref;
362
363     i = 0;
364     j = 0;
365     while (ca[i].ca_type != NSC_NOTYPE &&
366            values[j].v_type != VAL_NOTUSED) {
367         if (ca[i].ca_flags & NSC_EXTRA) {
368             i++;
369             continue;
370         }
371         row_ref = (char *)ptr + ca[i].ca_off;
372         k = 0;
373         do {
374             /*
375              * TODO
376              * factor out NSC_CONST comparsion
377              */
378             switch (values[j].v_type) {
379             case VAL_SYMBOL:
380             case VAL_SYMBOL_SET:
381                 if (xunsymbol(&ca[i], &values[j], j) < 0)
382                     return -1;
383                 /* fall through */
384             case VAL_DOUBLE:
385                 switch (ca[i].ca_type) {
386                 case NSC_INT:
387                     if (ca[i].ca_flags & NSC_CONST) {
388                         if (((int *)row_ref)[k] != (int)
389                              values[j].v_field.v_double)
390                             gripe("Field %s must be same, "
391                                 "read %d != expected %d",
392                                 ca[i].ca_name,
393                                 ((int *)row_ref)[k],
394                                 (int)values[j].v_field.v_double);
395
396                     } else
397                         ((int *)row_ref)[k] =
398                             (int)values[j].v_field.v_double;
399                     break;
400                 case NSC_LONG:
401                     if (ca[i].ca_flags & NSC_CONST) {
402                         if (((long *)row_ref)[k] != (long)
403                              values[j].v_field.v_double)
404                             gripe("Field %s must be same, "
405                                 "read %ld != expected %ld",
406                                 ca[i].ca_name,
407                                 ((long *)row_ref)[k],
408                                 (long)values[j].v_field.v_double);
409                     } else
410                         ((long *)row_ref)[k] = (long)
411                             values[j].v_field.v_double;
412                     break;
413                 case NSC_SHORT:
414                     if (ca[i].ca_flags & NSC_CONST) {
415                         if (((short *)row_ref)[k] !=
416                              (short)values[j].v_field.v_double)
417                             gripe("Field %s must be same, "
418                                 "read %d != expected %d",
419                                 ca[i].ca_name,
420                                 ((short *)row_ref)[k],
421                                 (short)values[j].v_field.v_double);
422                     } else
423                         ((short *)row_ref)[k] = (short)
424                             values[j].v_field.v_double;
425                     break;
426                 case NSC_USHORT:
427                     if (ca[i].ca_flags & NSC_CONST) {
428                         if (((unsigned short *)row_ref)[k] !=
429                              (unsigned short)values[j].v_field.v_double)
430                             gripe("Field %s must be same, "
431                                 "read %d != expected %d",
432                                 ca[i].ca_name,
433                                 ((unsigned short *)row_ref)[k],
434                                 (unsigned short)values[j].v_field.v_double);
435                     } else
436                         ((unsigned short *)row_ref)[k] = (unsigned short)
437                             values[j].v_field.v_double;
438                     break;
439                 case NSC_UCHAR:
440                     if (ca[i].ca_flags & NSC_CONST) {
441                         if (((unsigned char *)row_ref)[k] != (unsigned char)
442                              values[j].v_field.v_double)
443                             gripe("Field %s must be same, "
444                                 "read %d != expected %d",
445                                 ca[i].ca_name,
446                                 ((unsigned char *)row_ref)[k],
447                                 (unsigned char)values[j].v_field.v_double);
448                     } else
449                         ((unsigned char *)row_ref)[k] = (unsigned char)
450                             values[j].v_field.v_double;
451                     break;
452                 case NSC_FLOAT:
453                     if (ca[i].ca_flags & NSC_CONST) {
454                         if (((float *)row_ref)[k] != (float)
455                              values[j].v_field.v_double)
456                             gripe("Field %s must be same, "
457                                 "read %g != expected %g",
458                                 ca[i].ca_name,
459                                 ((float *)row_ref)[k],
460                                 (float)values[j].v_field.v_double);
461                     } else
462                         ((float *)row_ref)[k] = (float)
463                             values[j].v_field.v_double;
464                     break;
465                 case NSC_STRING:
466                     return gripe("Field %s is a string type, "
467                         "but %lg was read which is a number",
468                         ca[i].ca_name, values[j].v_field.v_double);
469                 default:
470                     return gripe("Field %s's type %d is not supported",
471                         ca[i].ca_name, ca[i].ca_type);
472                 }
473                 break;
474             case VAL_STRING:
475                 switch(ca[i].ca_type) {
476                 case NSC_STRING:
477                     if (ca[i].ca_flags & NSC_CONST) {
478                         if (strcmp(((char **)row_ref)[k],
479                                    values[j].v_field.v_string) != 0)
480                             gripe("Field %s must be same, "
481                                 "read %s != expected %s",
482                                 ca[i].ca_name,
483                                 ((char **)row_ref)[k],
484                                 values[j].v_field.v_string);
485                     } else
486                         ((char **)row_ref)[k]
487                             = strdup(values[j].v_field.v_string);
488                     break;
489                 case NSC_INT:
490                 case NSC_LONG:
491                 case NSC_SHORT:
492                 case NSC_USHORT:
493                 case NSC_UCHAR:
494                 case NSC_FLOAT:
495                     return gripe("Field %s is a number type %d, "
496                         "but %s was read which is a string",
497                         ca[i].ca_name, ca[i].ca_type,
498                         values[j].v_field.v_string);
499                 default:
500                     return gripe("Field %s's type %d is not supported",
501                             ca[i].ca_name, ca[i].ca_type);
502                 }
503                 break;
504             case VAL_NOTUSED:
505                 return gripe("Missing column %s in file", ca[i].ca_name);
506             case VAL_INDEX_ID:
507             case VAL_INDEX_SYMBOL:
508                 return gripe("Index fields not supported in data rows in %s file",
509                     ca[i].ca_name);
510             default:
511                 return gripe("Unknown value type %d", values[j].v_type);
512             }
513             k++;
514             j++;
515         } while (k < ca[i].ca_len);
516         i++;
517     }
518     if (ca[i].ca_type != NSC_NOTYPE)
519         return gripe("Missing column %s in file", ca[i].ca_name);
520     switch  (values[j].v_type) {
521     case VAL_NOTUSED:
522         break;
523     case VAL_STRING:
524     case VAL_SYMBOL:
525     case VAL_SYMBOL_SET:
526         return gripe("Extra junk after the last column, read %s",
527             values[j].v_field.v_string);
528     case VAL_DOUBLE:
529         return gripe("Extra junk after the last column, read %lg",
530             values[j].v_field.v_double);
531     default:
532         return gripe("Extra junk after the last column, "
533             "unknown value type %d", values[j].v_type);
534     }
535     return 0;
536 }
537
538 static int
539 xuheader(FILE *fp, int expected_table)
540 {
541     char name[64];
542     struct empfile *ep;
543     int res, ch;
544     int type;
545
546     while ((ch = skipfs(fp)) == '\n')
547         lineno++;
548     if (ch == EOF && expected_table == EF_BAD)
549         return -1;
550     ungetc(ch, fp);
551
552     human = ch == 'c';
553     res = -1;
554     if ((human
555          ? fscanf(fp, "config%*[ \t]%63[^ \t#\n]%n", name, &res) != 1
556          : fscanf(fp, "XDUMP%*[ \t]%63[^ \t#\n]%*[ \t]%*[^ \t#\n]%n",
557                   name, &res) != 1) || res < 0)
558         return gripe("Expected xdump header");
559
560     if (skipfs(fp) != '\n')
561         return gripe("Junk after xdump header");
562
563     type = ef_byname(name);
564     if (type < 0)
565         return gripe("Unknown table `%s'", name);
566     ep = &empfile[type];
567     if (CANT_HAPPEN(!(ep->flags & EFF_MEM)))
568         return -1;              /* not implemented */
569
570     if (expected_table != EF_BAD && expected_table != type)
571         return gripe("Expected table `%s', not `%s'",
572                      ef_nameof(expected_table), name);
573     return type;
574 }
575
576 static int
577 xucolumnheader(FILE *fp, int type, struct value values[])
578 {
579     char ch;
580     struct empfile *ep = &empfile[type];
581     struct castr *ca = ep->cadef;
582     int i, j, k;
583
584     if (!human)
585         return 0;
586
587     while ((ch = skipfs(fp)) == '\n');
588     ungetc(ch, fp);
589
590     /* FIXME parse column header */
591     if (xuflds(fp, values) <= 0) {
592         freeflds(values);
593         return gripe("Invalid Column Header for table %s",
594             ef_nameof(type));
595     }
596     /* TODO
597      * check column count form xuflds()
598      */
599
600     j = 0;
601     for (i = 0; ca[i].ca_name; i++) {
602         if (ca[i].ca_flags & NSC_EXTRA)
603             continue;
604         k = 0;
605         do {
606             if (values[j].v_type == VAL_NOTUSED) {
607                 freeflds(values);
608                 return gripe("Not enough columns in the header for table %s",
609                              ef_nameof(type));
610             }
611             switch(values[j].v_type) {
612             case VAL_SYMBOL:
613                 if (ca[i].ca_len != 0) {
614                     freeflds(values);
615                     return gripe("Column %s is a not index format for table %s",
616                         ca[i].ca_name, ef_nameof(type));
617                 }
618                 if (strcmp(values[j].v_field.v_string, ca[i].ca_name) != 0) {
619                     gripe("Column name (%s) does not match header name (%s)",
620                         ca[i].ca_name, values[j].v_field.v_string);
621                     freeflds(values);
622                     return -1;
623                 }
624                 break;
625             case VAL_INDEX_SYMBOL:
626                     return gripe("Column %s is does not currently support index symbol format for table %s",
627                         ca[i].ca_name, ef_nameof(type));
628             case VAL_INDEX_ID:
629                 if (ca[i].ca_len == 0) {
630                     freeflds(values);
631                     return gripe("Column %s is in index format and should not be for table %s",
632                         ca[i].ca_name, ef_nameof(type));
633                 }
634                 if (values[j].v_field.v_int != k) {
635                     freeflds(values);
636                     return gripe("Column Array index %d does not match %d",
637                         values[j].v_field.v_int, k);
638                 }
639                 if (strcmp(values[j].v_index_name, ca[i].ca_name) != 0) {
640                     gripe("Column name (%s) does not match header name (%s)",
641                         ca[i].ca_name, values[j].v_field.v_string);
642                     freeflds(values);
643                     return -1;
644                 }
645                 break;
646             default:
647                 freeflds(values);
648                 return gripe("Column %s is a not string for table %s",
649                     ca[i].ca_name, ef_nameof(type));
650             }
651             j++;
652             k++;
653         } while (k < ca[i].ca_len);
654     }
655     if (values[j].v_type != VAL_NOTUSED) {
656         freeflds(values);
657         return gripe("Too many columns in the header for table %s",
658             ef_nameof(type));
659     }
660     lineno++;
661     freeflds(values);
662     return 0;
663 }
664
665 static int
666 xutrailer(FILE *fp, int type, int row)
667 {
668     int rows, ch, res;
669
670     res = -1;
671     if (human) {
672         if (fscanf(fp, "config%n",  &res) != 0) {
673             return gripe("Malformed table footer");
674         }
675     }
676     ch = skipfs(fp);
677     if (!isdigit(ch)) {
678         if (ch != '\n' || !human)
679             return gripe("Malformed table footer");
680     } else {
681         ungetc(ch, fp);
682         if (fscanf(fp, "%d", &rows) != 1)
683             return gripe("Malformed table footer");
684         if (row != rows)
685             return gripe("Read %d rows, which doesn't match footer "
686                          "%d rows", row, rows);
687     }
688     if (skipfs(fp) != '\n')
689         return gripe("Junk after table footer");
690
691     while ((ch = skipfs(fp)) == '\n') ;
692     ungetc(ch, fp);
693
694     return 0;
695 }
696
697 int
698 xundump(FILE *fp, char *file, int expected_table)
699 {
700     int row, res, ch;
701     struct empfile *ep;
702     int need_sentinel;
703     struct value values[MAX_NUM_COLUMNS + 1];
704     int type;
705     int fixed_rows;
706
707     memset(values, 0, sizeof(values));
708
709     if (strcmp(fname, file) != 0) {
710         fname = file;
711         lineno = 1;
712     } else
713         lineno++;
714
715     if ((type = xuheader(fp, expected_table)) == -1)
716         return -1;
717     
718     ep = &empfile[type];
719     fixed_rows = has_const(ef_cadef(type));
720     need_sentinel = !fixed_rows; /* FIXME only approximation */
721
722     if (xucolumnheader(fp, type, values) == -1)
723         return -1;
724
725     row = 0;
726     for (;;) {
727         lineno++;
728         ch = skipfs(fp);
729         if (ch == '/')
730             break;
731         ungetc(ch, fp);
732         /*
733          * TODO
734          * Add column count check to the return value of xuflds()
735          */
736         res = xuflds(fp, values);
737         if (res > 0 && row >= ep->csize - 1) {
738             /* TODO grow cache unless EFF_STATIC */
739             gripe("Too many rows for table %s", ef_nameof(type));
740             res = -1;
741         }
742         if (res > 0) {
743             if (!fixed_rows)
744                 xuinitrow(type, row);
745             res = xuloadrow(type, row, values);
746             row++;
747         }
748         freeflds(values);
749         if (res < 0)
750             return -1;
751     }
752     if (fixed_rows && row != ep->csize -1)
753         return gripe("Table %s requires %d rows, got %d",
754                      ef_nameof(type), ep->csize - 1, row);
755
756     if (need_sentinel)
757         xuinitrow(type, row);
758
759     if (xutrailer(fp, type, row) == -1)
760         return -1;
761     
762     ep->fids = ep->cids = row;
763     return type;
764 }