]> git.pond.sub.org Git - empserver/blob - src/lib/common/file.c
Generation numbers to catch write back of stale copies
[empserver] / src / lib / common / file.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2009, 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  *  file.c: Operations on Empire tables (`files' for historical reasons)
29  *
30  *  Known contributors to this file:
31  *     Dave Pare, 1989
32  *     Steve McClure, 2000
33  *     Markus Armbruster, 2005-2008
34  */
35
36 #include <config.h>
37
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <unistd.h>
43 #include "file.h"
44 #include "match.h"
45 #include "misc.h"
46 #include "nsc.h"
47 #include "prototypes.h"
48
49 static int ef_realloc_cache(struct empfile *, int);
50 static int fillcache(struct empfile *, int);
51 static int do_read(struct empfile *, void *, int, int);
52 static int do_write(struct empfile *, void *, int, int);
53 static unsigned get_seqno(struct empfile *, int);
54 static void new_seqno(struct empfile *, void *);
55 static void must_be_fresh(struct empfile *, void *);
56 static void do_blank(struct empfile *, void *, int, int);
57 static int ef_check(int);
58
59 static unsigned ef_generation;
60
61 /*
62  * Open the file-backed table TYPE (EF_SECTOR, ...).
63  * HOW are flags to control operation.  Naturally, immutable flags are
64  * not permitted.
65  * If NELT is non-negative, the table must have that many elements.
66  * Return non-zero on success, zero on failure.
67  * You must call ef_close() before the next ef_open().
68  */
69 int
70 ef_open(int type, int how, int nelt)
71 {
72     struct empfile *ep;
73     struct flock lock;
74     int oflags, fd, fsiz, nslots;
75
76     if (ef_check(type) < 0)
77         return 0;
78     if (CANT_HAPPEN(how & EFF_IMMUTABLE))
79         how &= ~EFF_IMMUTABLE;
80
81     /* open file */
82     ep = &empfile[type];
83     if (CANT_HAPPEN(ep->fd >= 0))
84         return 0;
85     oflags = O_RDWR;
86     if (how & EFF_PRIVATE)
87         oflags = O_RDONLY;
88     if (how & EFF_CREATE)
89         oflags |= O_CREAT | O_TRUNC;
90 #if defined(_WIN32)
91     oflags |= O_BINARY;
92 #endif
93     if ((fd = open(ep->file, oflags, S_IRWUG)) < 0) {
94         logerror("Can't open %s (%s)", ep->file, strerror(errno));
95         return 0;
96     }
97
98     lock.l_type = how & EFF_PRIVATE ? F_RDLCK : F_WRLCK;
99     lock.l_whence = SEEK_SET;
100     lock.l_start = lock.l_len = 0;
101     if (fcntl(fd, F_SETLK, &lock) == -1) {
102         logerror("Can't lock %s (%s)", ep->file, strerror(errno));
103         close(fd);
104         return 0;
105     }
106
107     /* get file size */
108     fsiz = fsize(fd);
109     if (fsiz % ep->size) {
110         logerror("Can't open %s (file size not a multiple of record size %d)",
111                  ep->file, ep->size);
112         close(fd);
113         return 0;
114     }
115     ep->fids = fsiz / ep->size;
116     if (nelt >= 0 && nelt != ep->fids) {
117         logerror("Can't open %s (got %d records instead of %d)",
118                  ep->file, ep->fids, nelt);
119         close(fd);
120         return 0;
121     }
122
123     /* allocate cache */
124     if (ep->flags & EFF_STATIC) {
125         /* ep->cache already points to space for ep->csize elements */
126         if (how & EFF_MEM) {
127             if (ep->fids > ep->csize) {
128                 logerror("Can't open %s: file larger than %d bytes",
129                          ep->file, ep->fids * ep->size);
130                 close(fd);
131                 return 0;
132             }
133         }
134     } else {
135         if (CANT_HAPPEN(ep->cache))
136             free(ep->cache);
137         if (how & EFF_MEM)
138             nslots = ep->fids;
139         else
140             nslots = blksize(fd) / ep->size;
141         if (!ef_realloc_cache(ep, nslots)) {
142             logerror("Can't map %s (%s)", ep->file, strerror(errno));
143             close(fd);
144             return 0;
145         }
146     }
147     ep->baseid = 0;
148     ep->cids = 0;
149     ep->flags = (ep->flags & EFF_IMMUTABLE) | (how & ~EFF_CREATE);
150     ep->fd = fd;
151
152     /* map file into cache */
153     if ((how & EFF_MEM) && ep->fids) {
154         if (fillcache(ep, 0) != ep->fids) {
155             ep->cids = 0;       /* prevent cache flush */
156             ep->flags &= EFF_IMMUTABLE; /* maintain invariant */
157             ef_close(type);
158             return 0;
159         }
160     }
161
162     if (ep->onresize && ep->onresize(type) < 0)
163         return 0;
164     return 1;
165 }
166
167 /*
168  * Reallocate cache for table EP to hold COUNT slots.
169  * The table must not be allocated statically.
170  * The cache may still be unmapped.
171  * If reallocation succeeds, any pointers obtained from ef_ptr()
172  * become invalid.
173  * If it fails, the cache is unchanged, and errno is set.
174  * Return non-zero on success, zero on failure.
175  */
176 static int
177 ef_realloc_cache(struct empfile *ep, int count)
178 {
179     void *cache;
180
181     if (CANT_HAPPEN(ep->flags & EFF_STATIC))
182         return 0;
183     if (CANT_HAPPEN(count < 0))
184         count = 0;
185
186     /*
187      * Avoid zero slots, because that can lead to null cache, which
188      * would be interpreted as unmapped cache.
189      */
190     if (count == 0)
191         count++;
192     cache = realloc(ep->cache, count * ep->size);
193     if (!cache)
194         return 0;
195
196     ep->cache = cache;
197     ep->csize = count;
198     return 1;
199 }
200
201 /*
202  * Open the table TYPE as view of table BASE.
203  * Return non-zero on success, zero on failure.
204  * Beware: views work only as long as BASE doesn't change size!
205  * You must call ef_close(TYPE) before closing BASE.
206  */
207 int
208 ef_open_view(int type, int base)
209 {
210     struct empfile *ep;
211
212     if (CANT_HAPPEN(!EF_IS_VIEW(type)))
213         return -1;
214     ep = &empfile[type];
215     if (CANT_HAPPEN(!(ef_flags(base) & EFF_MEM)))
216         return -1;
217
218     ep->cache = empfile[base].cache;
219     ep->csize = empfile[base].csize;
220     ep->flags |= EFF_MEM;
221     ep->baseid = empfile[base].baseid;
222     ep->cids = empfile[base].cids;
223     ep->fids = empfile[base].fids;
224     return 0;
225 }
226
227 /*
228  * Close the file-backed table TYPE (EF_SECTOR, ...).
229  * Return non-zero on success, zero on failure.
230  */
231 int
232 ef_close(int type)
233 {
234     struct empfile *ep;
235     int retval = 1;
236
237     if (ef_check(type) < 0)
238         return 0;
239     ep = &empfile[type];
240
241     if (EF_IS_VIEW(type))
242         ep->cache = NULL;
243     else {
244         if (!ef_flush(type))
245             retval = 0;
246         ep->flags &= EFF_IMMUTABLE;
247         if (!(ep->flags & EFF_STATIC)) {
248             free(ep->cache);
249             ep->cache = NULL;
250         }
251         if (close(ep->fd) < 0) {
252             logerror("Error closing %s (%s)", ep->file, strerror(errno));
253             retval = 0;
254         }
255         ep->fd = -1;
256     }
257     ep->baseid = ep->cids = ep->fids = 0;
258     if (ep->onresize && ep->onresize(type) < 0)
259         retval = 0;
260     return retval;
261 }
262
263 /*
264  * Flush file-backed table TYPE (EF_SECTOR, ...) to its backing file.
265  * Do nothing if the table is privately mapped.
266  * Update timestamps of written elements if table is EFF_TYPED.
267  * Return non-zero on success, zero on failure.
268  */
269 int
270 ef_flush(int type)
271 {
272     struct empfile *ep;
273
274     if (ef_check(type) < 0)
275         return 0;
276     ep = &empfile[type];
277     if (ep->flags & EFF_PRIVATE)
278         return 1;               /* nothing to do */
279     if (CANT_HAPPEN(ep->fd < 0))
280         return 0;
281     /*
282      * We don't know which cache entries are dirty.  ef_write() writes
283      * through, but direct updates through ef_ptr() don't.  They are
284      * allowed only with EFF_MEM.  Assume the whole cash is dirty
285      * then.
286      */
287     if (ep->flags & EFF_MEM) {
288         if (do_write(ep, ep->cache, ep->baseid, ep->cids) < 0)
289             return 0;
290     }
291
292     return 1;
293 }
294
295 /*
296  * Return pointer to element ID in table TYPE if it exists, else NULL.
297  * The table must be fully cached, i.e. flags & EFF_MEM.
298  * The caller is responsible for flushing changes he makes.
299  */
300 void *
301 ef_ptr(int type, int id)
302 {
303     struct empfile *ep;
304
305     if (ef_check(type) < 0)
306         return NULL;
307     ep = &empfile[type];
308     if (CANT_HAPPEN(!(ep->flags & EFF_MEM) || !ep->cache))
309         return NULL;
310     if (id < 0 || id >= ep->fids)
311         return NULL;
312     return ep->cache + ep->size * id;
313 }
314
315 /*
316  * Read element ID from table TYPE into buffer INTO.
317  * FIXME pass buffer size!
318  * Return non-zero on success, zero on failure.
319  */
320 int
321 ef_read(int type, int id, void *into)
322 {
323     struct empfile *ep;
324     void *cachep;
325
326     if (ef_check(type) < 0)
327         return 0;
328     ep = &empfile[type];
329     if (CANT_HAPPEN(!ep->cache))
330         return 0;
331     if (id < 0 || id >= ep->fids)
332         return 0;
333
334     if (ep->flags & EFF_MEM) {
335         cachep = ep->cache + id * ep->size;
336     } else {
337         if (ep->baseid + ep->cids <= id || ep->baseid > id) {
338             if (fillcache(ep, id) < 1)
339                 return 0;
340         }
341         cachep = ep->cache + (id - ep->baseid) * ep->size;
342     }
343     memcpy(into, cachep, ep->size);
344     ef_mark_fresh(type, into);
345
346     if (ep->postread)
347         ep->postread(id, into);
348     return 1;
349 }
350
351 /*
352  * Fill cache of file-backed EP with elements starting at ID.
353  * If any were read, return their number.
354  * Else return -1 and leave the cache unchanged.
355  */
356 static int
357 fillcache(struct empfile *ep, int id)
358 {
359     int ret;
360
361     if (CANT_HAPPEN(!ep->cache))
362         return -1;
363
364     ret = do_read(ep, ep->cache, id, MIN(ep->csize, ep->fids - id));
365     if (ret >= 0) {
366         /* cache changed */
367         ep->baseid = id;
368         ep->cids = ret;
369     }
370     return ret;
371 }
372
373 static int
374 do_read(struct empfile *ep, void *buf, int id, int count)
375 {
376     int n, ret;
377     char *p;
378
379     if (CANT_HAPPEN(ep->fd < 0 || id < 0 || count < 0))
380         return -1;
381
382     if (lseek(ep->fd, id * ep->size, SEEK_SET) == (off_t)-1) {
383         logerror("Error seeking %s to elt %d (%s)",
384                  ep->file, id, strerror(errno));
385         return -1;
386     }
387
388     p = buf;
389     n = count * ep->size;
390     while (n > 0) {
391         ret = read(ep->fd, p, n);
392         if (ret < 0) {
393             if (errno != EINTR) {
394                 logerror("Error reading %s elt %d (%s)",
395                          ep->file,
396                          id + (int)((p - (char *)buf) / ep->size),
397                          strerror(errno));
398                 break;
399             }
400         } else if (ret == 0) {
401             logerror("Unexpected EOF reading %s elt %d",
402                      ep->file, id + (int)((p - (char *)buf) / ep->size));
403             break;
404         } else {
405             p += ret;
406             n -= ret;
407         }
408     }
409
410     return (p - (char *)buf) / ep->size;
411 }
412
413 /*
414  * Write COUNT elements starting at ID from BUF to file-backed EP.
415  * Update the timestamp if the table is EFF_TYPED.
416  * Don't actually write if table is privately mapped.
417  * Return 0 on success, -1 on error (file may be corrupt then).
418  */
419 static int
420 do_write(struct empfile *ep, void *buf, int id, int count)
421 {
422     int i, n, ret;
423     char *p;
424     struct emptypedstr *elt;
425     time_t now;
426
427     if (CANT_HAPPEN(ep->fd < 0 || id < 0 || count < 0))
428         return -1;
429
430     if (ep->flags & EFF_TYPED) {
431         now = ep->flags & EFF_NOTIME ? (time_t)-1 : time(NULL);
432         for (i = 0; i < count; i++) {
433             /*
434              * TODO Oopses here could be due to bad data corruption.
435              * Fail instead of attempting to recover?
436              */
437             elt = (struct emptypedstr *)((char *)buf + i * ep->size);
438             if (CANT_HAPPEN(elt->ef_type != ep->uid))
439                 elt->ef_type = ep->uid;
440             if (CANT_HAPPEN(elt->uid != id + i))
441                 elt->uid = id + i;
442             if (now != (time_t)-1)
443                 elt->timestamp = now;
444         }
445     }
446
447     if (ep->flags & EFF_PRIVATE)
448         return 0;
449
450     if (lseek(ep->fd, id * ep->size, SEEK_SET) == (off_t)-1) {
451         logerror("Error seeking %s to elt %d (%s)",
452                  ep->file, id, strerror(errno));
453         return -1;
454     }
455
456     p = buf;
457     n = count * ep->size;
458     while (n > 0) {
459         ret = write(ep->fd, p, n);
460         if (ret < 0) {
461             if (errno != EINTR) {
462                 logerror("Error writing %s elt %d (%s)",
463                          ep->file,
464                          id + (int)((p - (char *)buf) / ep->size),
465                          strerror(errno));
466                 return -1;
467             }
468         } else {
469             p += ret;
470             n -= ret;
471         }
472     }
473
474     return 0;
475 }
476
477 /*
478  * Write element ID into table TYPE from buffer FROM.
479  * FIXME pass buffer size!
480  * Update timestamp in FROM if table is EFF_TYPED.
481  * If table is file-backed and not privately mapped, write through
482  * cache straight to disk.
483  * Cannot write beyond the end of fully cached table (flags & EFF_MEM).
484  * Can write at the end of partially cached table.
485  * Return non-zero on success, zero on failure.
486  */
487 int
488 ef_write(int type, int id, void *from)
489 {
490     struct empfile *ep;
491     char *cachep;
492
493     if (ef_check(type) < 0)
494         return 0;
495     ep = &empfile[type];
496     if (CANT_HAPPEN((ep->flags & (EFF_MEM | EFF_PRIVATE)) == EFF_PRIVATE))
497         return 0;
498     if (CANT_HAPPEN((ep->flags & EFF_MEM) ? id >= ep->fids : id > ep->fids))
499         return 0;               /* not implemented */
500     new_seqno(ep, from);
501     if (id >= ep->fids) {
502         /* write beyond end of file extends it, take note */
503         ep->fids = id + 1;
504         if (ep->onresize && ep->onresize(type) < 0)
505             return 0;
506     }
507     if (id >= ep->baseid && id < ep->baseid + ep->cids) {
508         cachep = ep->cache + (id - ep->baseid) * ep->size;
509         if (cachep != from)
510             must_be_fresh(ep, from);
511     } else
512         cachep = NULL;
513     if (ep->prewrite)
514         ep->prewrite(id, cachep, from);
515     if (ep->fd >= 0) {
516         if (do_write(ep, from, id, 1) < 0)
517             return 0;
518     }
519     if (cachep && cachep != from)       /* update the cache if necessary */
520         memcpy(cachep, from, ep->size);
521     return 1;
522 }
523
524 /*
525  * Change element id.
526  * BUF is an element of table TYPE.
527  * ID is its new element ID.
528  * If table is EFF_TYPED, change id and sequence number stored in BUF.
529  * Else do nothing.
530  */
531 void
532 ef_set_uid(int type, void *buf, int uid)
533 {
534     struct emptypedstr *elt;
535     struct empfile *ep;
536
537     if (ef_check(type) < 0)
538         return;
539     ep = &empfile[type];
540     if (!(ep->flags & EFF_TYPED))
541         return;
542     elt = buf;
543     if (elt->uid == uid)
544         return;
545     elt->uid = uid;
546     elt->seqno = get_seqno(ep, uid);
547 }
548
549 /*
550  * Return sequence number of element ID in table EP.
551  * Return zero if table is not EFF_TYPED (it has no sequence number
552  * then).
553  */
554 static unsigned
555 get_seqno(struct empfile *ep, int id)
556 {
557     struct emptypedstr *elt;
558
559     if (!(ep->flags & EFF_TYPED))
560         return 0;
561     if (id < 0 || id >= ep->fids)
562         return 0;
563     if (id >= ep->baseid && id < ep->baseid + ep->cids)
564         elt = (void *)(ep->cache + (id - ep->baseid) * ep->size);
565     else {
566         /* need a buffer, steal last cache slot */
567         if (ep->cids == ep->csize)
568             ep->cids--;
569         elt = (void *)(ep->cache + ep->cids * ep->size);
570         if (do_read(ep, elt, id, 1) < 0)
571             return 0;           /* deep trouble */
572     }
573     return elt->seqno;
574 }
575
576 /*
577  * Increment sequence number in BUF, which is about to be written to EP.
578  * Do nothing if table is not EFF_TYPED (it has no sequence number
579  * then).
580  */
581 static void
582 new_seqno(struct empfile *ep, void *buf)
583 {
584     struct emptypedstr *elt = buf;
585     unsigned old_seqno;
586
587     if (!(ep->flags & EFF_TYPED))
588         return;
589     old_seqno = get_seqno(ep, elt->uid);
590     if (CANT_HAPPEN(old_seqno != elt->seqno))
591         old_seqno = MAX(old_seqno, elt->seqno);
592     elt->seqno = old_seqno + 1;
593 }
594
595 void
596 ef_make_stale(void)
597 {
598     ef_generation++;
599 }
600
601 void
602 ef_mark_fresh(int type, void *buf)
603 {
604     struct empfile *ep;
605
606     if (ef_check(type) < 0)
607         return;
608     ep = &empfile[type];
609     if (!(ep->flags & EFF_TYPED))
610         return;
611     ((struct emptypedstr *)buf)->generation = ef_generation;
612 }
613
614 static void
615 must_be_fresh(struct empfile *ep, void *buf)
616 {
617     struct emptypedstr *elt = buf;
618
619     if (!(ep->flags & EFF_TYPED))
620         return;
621     CANT_HAPPEN(elt->generation != ef_generation);
622 }
623
624 /*
625  * Extend table TYPE by COUNT elements.
626  * Any pointers obtained from ef_ptr() become invalid.
627  * Return non-zero on success, zero on failure.
628  */
629 int
630 ef_extend(int type, int count)
631 {
632     struct empfile *ep;
633     char *p;
634     int need_sentinel, i, id;
635
636     if (ef_check(type) < 0 || CANT_HAPPEN(EF_IS_VIEW(type)))
637         return 0;
638     ep = &empfile[type];
639     if (CANT_HAPPEN(count < 0))
640         return 0;
641
642     id = ep->fids;
643     if (ep->flags & EFF_MEM) {
644         need_sentinel = (ep->flags & EFF_SENTINEL) != 0;
645         if (id + count + need_sentinel > ep->csize) {
646             if (ep->flags & EFF_STATIC) {
647                 logerror("Can't extend %s beyond %d elements",
648                          ep->name, ep->csize - need_sentinel);
649                 return 0;
650             }
651             if (!ef_realloc_cache(ep, id + count + need_sentinel)) {
652                 logerror("Can't extend %s to %d elements (%s)",
653                          ep->name, id + count, strerror(errno));
654                 return 0;
655             }
656         }
657         p = ep->cache + id * ep->size;
658         do_blank(ep, p, id, count);
659         if (ep->fd >= 0) {
660             if (do_write(ep, p, id, count) < 0)
661                 return 0;
662         }
663         if (need_sentinel)
664             memset(ep->cache + (id + count) * ep->size, 0, ep->size);
665         ep->cids = id + count;
666     } else {
667         /* need a buffer, steal last cache slot */
668         if (ep->cids == ep->csize)
669             ep->cids--;
670         p = ep->cache + ep->cids * ep->size;
671         for (i = 0; i < count; i++) {
672             do_blank(ep, p, id + i, 1);
673             if (do_write(ep, p, id + i, 1) < 0)
674                 return 0;
675         }
676     }
677     ep->fids = id + count;
678     if (ep->onresize && ep->onresize(type) < 0)
679         return 0;
680     return 1;
681 }
682
683 /*
684  * Initialize element ID for EP in BUF.
685  * FIXME pass buffer size!
686  */
687 void
688 ef_blank(int type, int id, void *buf)
689 {
690     struct empfile *ep;
691     struct emptypedstr *elt;
692
693     if (ef_check(type) < 0)
694         return;
695     ep = &empfile[type];
696     do_blank(ep, buf, id, 1);
697     if (ep->flags & EFF_TYPED) {
698         elt = buf;
699         elt->seqno = get_seqno(ep, elt->uid);
700     }
701     ef_mark_fresh(type, buf);
702 }
703
704 /*
705  * Initialize COUNT elements of EP in BUF, starting with element ID.
706  */
707 static void
708 do_blank(struct empfile *ep, void *buf, int id, int count)
709 {
710     int i;
711     struct emptypedstr *elt;
712
713     memset(buf, 0, count * ep->size);
714     for (i = 0; i < count; i++) {
715         elt = (struct emptypedstr *)((char *)buf + i * ep->size);
716         if (ep->flags & EFF_TYPED) {
717             elt->ef_type = ep->uid;
718             elt->uid = id + i;
719         }
720         if (ep->oninit)
721             ep->oninit(elt);
722     }
723 }
724
725 /*
726  * Truncate table TYPE to COUNT elements.
727  * Any pointers obtained from ef_ptr() become invalid.
728  * Return non-zero on success, zero on failure.
729  */
730 int
731 ef_truncate(int type, int count)
732 {
733     struct empfile *ep;
734     int need_sentinel;
735
736     if (ef_check(type) < 0 || CANT_HAPPEN(EF_IS_VIEW(type)))
737         return 0;
738     ep = &empfile[type];
739     if (CANT_HAPPEN(count < 0 || count > ep->fids))
740         return 0;
741
742     if (ep->fd >= 0 && !(ep->flags & EFF_PRIVATE)) {
743         if (ftruncate(ep->fd, count * ep->size) < 0) {
744             logerror("Can't truncate %s to %d elements (%s)",
745                      ep->file, count, strerror(errno));
746             return 0;
747         }
748     }
749     ep->fids = count;
750
751     if (ep->flags & EFF_MEM) {
752         need_sentinel = (ep->flags & EFF_SENTINEL) != 0;
753         if (!(ep->flags & EFF_STATIC)) {
754             if (!ef_realloc_cache(ep, count + need_sentinel)) {
755                 logerror("Can't shrink %s cache after truncate (%s)",
756                          ep->name, strerror(errno));
757                 /* continue with unshrunk cache */
758             }
759         }
760         if (need_sentinel)
761             memset(ep->cache + count * ep->size, 0, ep->size);
762         ep->cids = count;
763     } else {
764         if (ep->baseid >= count)
765             ep->cids = 0;
766         else if (ep->cids > count - ep->baseid)
767             ep->cids = count - ep->baseid;
768     }
769
770     if (ep->onresize && ep->onresize(type) < 0)
771         return 0;
772     return 1;
773 }
774
775 struct castr *
776 ef_cadef(int type)
777 {
778     if (ef_check(type) < 0)
779         return NULL;
780     return empfile[type].cadef;
781 }
782
783 int
784 ef_nelem(int type)
785 {
786     if (ef_check(type) < 0)
787         return 0;
788     return empfile[type].fids;
789 }
790
791 int
792 ef_flags(int type)
793 {
794     if (ef_check(type) < 0)
795         return 0;
796     return empfile[type].flags;
797 }
798
799 time_t
800 ef_mtime(int type)
801 {
802     if (ef_check(type) < 0)
803         return 0;
804     if (empfile[type].fd <= 0)
805         return 0;
806     return fdate(empfile[type].fd);
807 }
808
809 /*
810  * Search for a table matching NAME, return its table type.
811  * Return M_NOTFOUND if there are no matches, M_NOTUNIQUE if there are
812  * several.
813  */
814 int
815 ef_byname(char *name)
816 {
817     return stmtch(name, empfile, offsetof(struct empfile, name),
818                   sizeof(empfile[0]));
819 }
820
821 /*
822  * Search CHOICES[] for a table type matching NAME, return it.
823  * Return M_NOTFOUND if there are no matches, M_NOTUNIQUE if there are
824  * several.
825  * CHOICES[] must be terminated with a negative value.
826  */
827 int
828 ef_byname_from(char *name, int choices[])
829 {
830     int res;
831     int *p;
832
833     res = M_NOTFOUND;
834     for (p = choices; *p >= 0; p++) {
835         if (ef_check(*p) < 0)
836             continue;
837         switch (mineq(name, empfile[*p].name)) {
838         case ME_MISMATCH:
839             break;
840         case ME_PARTIAL:
841             if (res >= 0)
842                 return M_NOTUNIQUE;
843             res = *p;
844             break;
845         case ME_EXACT:
846             return *p;
847         }
848     }
849     return res;
850 }
851
852 char *
853 ef_nameof(int type)
854 {
855     if (ef_check(type) < 0)
856         return "bad ef_type";
857     return empfile[type].name;
858 }
859
860 static int
861 ef_check(int type)
862 {
863     if (CANT_HAPPEN((unsigned)type >= EF_MAX))
864         return -1;
865     return 0;
866 }
867
868 /*
869  * Ensure table contains element ID.
870  * If necessary, extend it in steps of COUNT elements.
871  * Return non-zero on success, zero on failure.
872  */
873 int
874 ef_ensure_space(int type, int id, int count)
875 {
876     if (ef_check(type) < 0)
877         return 0;
878     CANT_HAPPEN(id < 0);
879
880     while (id >= empfile[type].fids) {
881         if (!ef_extend(type, count))
882             return 0;
883     }
884     return 1;
885 }