/*
* Empire - A multi-player, client/server Internet based war game.
- * Copyright (C) 1986-2011, Dave Pare, Jeff Bailey, Thomas Ruschak,
+ * Copyright (C) 1986-2016, Dave Pare, Jeff Bailey, Thomas Ruschak,
* Ken Stevens, Steve McClure, Markus Armbruster
*
* Empire is free software: you can redistribute it and/or modify
* Known contributors to this file:
* Dave Pare, 1989
* Steve McClure, 2000
- * Markus Armbruster, 2005-2011
+ * Markus Armbruster, 2005-2014
*/
#include <config.h>
#include <errno.h>
#include <fcntl.h>
+#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "file.h"
#include "match.h"
#include "misc.h"
-#include "nsc.h"
#include "prototypes.h"
static int open_locked(char *, int, mode_t);
static unsigned get_seqno(struct empfile *, int);
static void new_seqno(struct empfile *, void *);
static void must_be_fresh(struct empfile *, void *);
+static int do_extend(struct empfile *, int);
static void do_blank(struct empfile *, void *, int, int);
static int ef_check(int);
static unsigned ef_generation;
/*
- * Open the file-backed table TYPE (EF_SECTOR, ...).
- * HOW are flags to control operation. Naturally, immutable flags are
+ * Open the file-backed table @type (EF_SECTOR, ...).
+ * @how are flags to control operation. Naturally, immutable flags are
* not permitted.
- * If NELT is non-negative, the table must have that many elements.
+ * The table must not be already open.
* Return non-zero on success, zero on failure.
- * You must call ef_close() before the next ef_open().
*/
int
-ef_open(int type, int how, int nelt)
+ef_open(int type, int how)
{
struct empfile *ep;
- int oflags, fd, fsiz, nslots;
+ int oflags, fd, fsiz, fids, nslots, fail;
if (ef_check(type) < 0)
return 0;
/* open file */
ep = &empfile[type];
- if (CANT_HAPPEN(ep->fd >= 0))
+ if (CANT_HAPPEN(!ep->file || ep->base != EF_BAD || ep->fd >= 0))
return 0;
+ if (CANT_HAPPEN(ep->prewrite && !(how & EFF_MEM)))
+ return 0; /* not implemented */
oflags = O_RDWR;
if (how & EFF_PRIVATE)
oflags = O_RDONLY;
}
/* get file size */
- fsiz = fsize(fd);
- if (fsiz % ep->size) {
- logerror("Can't open %s (file size not a multiple of record size %d)",
- ep->file, ep->size);
- close(fd);
- return 0;
- }
- ep->fids = fsiz / ep->size;
- if (nelt >= 0 && nelt != ep->fids) {
- logerror("Can't open %s (got %d records instead of %d)",
- ep->file, ep->fids, nelt);
- close(fd);
- return 0;
+ if (how & EFF_CREATE) {
+ fids = ep->nent >= 0 ? ep->nent : 0;
+ } else {
+ fsiz = fsize(fd);
+ if (fsiz % ep->size) {
+ logerror("Can't open %s (file size not a multiple of record size %d)",
+ ep->file, ep->size);
+ close(fd);
+ return 0;
+ }
+ fids = fsiz / ep->size;
+ if (ep->nent >= 0 && ep->nent != fids) {
+ logerror("Can't open %s (got %d records instead of %d)",
+ ep->file, fids, ep->nent);
+ close(fd);
+ return 0;
+ }
}
/* allocate cache */
if (ep->flags & EFF_STATIC) {
/* ep->cache already points to space for ep->csize elements */
if (how & EFF_MEM) {
- if (ep->fids > ep->csize) {
+ if (fids > ep->csize) {
+ CANT_HAPPEN(ep->nent >= 0); /* insufficient static cache */
logerror("Can't open %s (file larger than %d records)",
ep->file, ep->csize);
close(fd);
if (CANT_HAPPEN(ep->cache))
free(ep->cache);
if (how & EFF_MEM)
- nslots = ep->fids;
+ nslots = fids;
else
nslots = blksize(fd) / ep->size;
if (!ef_realloc_cache(ep, nslots)) {
ep->flags = (ep->flags & EFF_IMMUTABLE) | (how & ~EFF_CREATE);
ep->fd = fd;
- /* map file into cache */
- if ((how & EFF_MEM) && ep->fids) {
- if (fillcache(ep, 0) != ep->fids) {
- ep->cids = 0; /* prevent cache flush */
- ef_close(type);
- return 0;
- }
+ if (how & EFF_CREATE) {
+ /* populate new file */
+ ep->fids = 0;
+ fail = !do_extend(ep, fids);
+ } else {
+ ep->fids = fids;
+ if ((how & EFF_MEM) && fids)
+ fail = fillcache(ep, 0) != fids;
+ else
+ fail = 0;
+ }
+ if (fail) {
+ ep->cids = 0; /* prevent cache flush */
+ ef_close(type);
+ return 0;
}
if (ep->onresize)
}
/*
- * Reallocate cache for table EP to hold COUNT slots.
+ * Reallocate cache for table @ep to hold @count slots.
* The table must not be allocated statically.
* The cache may still be unmapped.
* If reallocation succeeds, any pointers obtained from ef_ptr()
}
/*
- * Open the table TYPE as view of table BASE.
+ * Open the table @type, which is a view of a base table
+ * The table must not be already open.
* Return non-zero on success, zero on failure.
- * Beware: views work only as long as BASE doesn't change size!
- * You must call ef_close(TYPE) before closing BASE.
+ * Beware: views work only as long as the base table doesn't change size!
+ * You must close the view before closing its base table.
*/
int
-ef_open_view(int type, int base)
+ef_open_view(int type)
{
struct empfile *ep;
+ int base;
- if (CANT_HAPPEN(!EF_IS_VIEW(type)))
- return -1;
+ if (ef_check(type) < 0)
+ return 0;
ep = &empfile[type];
- if (CANT_HAPPEN(!(ef_flags(base) & EFF_MEM)))
+ base = ep->base;
+ if (ef_check(base) < 0)
+ return 0;
+ if (CANT_HAPPEN(!(ef_flags(base) & EFF_MEM)
+ || ep->file
+ || ep->size != empfile[base].size
+ || ep->nent != empfile[base].nent
+ || ep->cache || ep->oninit || ep->postread
+ || ep->prewrite || ep->onresize))
return -1;
ep->cache = empfile[base].cache;
}
/*
- * Close the open table TYPE (EF_SECTOR, ...).
+ * Close the open table @type (EF_SECTOR, ...).
* Return non-zero on success, zero on failure.
*/
int
}
/*
- * Flush file-backed table TYPE (EF_SECTOR, ...) to its backing file.
+ * Flush file-backed table @type (EF_SECTOR, ...) to its backing file.
* Do nothing if the table is privately mapped.
* Update timestamps of written elements if table is EFF_TYPED.
* Return non-zero on success, zero on failure.
}
/*
- * Return pointer to element ID in table TYPE if it exists, else NULL.
+ * Return pointer to element @id in table @type if it exists, else NULL.
* The table must be fully cached, i.e. flags & EFF_MEM.
* The caller is responsible for flushing changes he makes.
*/
}
/*
- * Read element ID from table TYPE into buffer INTO.
+ * Read element @id from table @type into buffer @into.
* FIXME pass buffer size!
- * INTO is marked fresh with ef_mark_fresh().
+ * @into is marked fresh with ef_mark_fresh().
* Return non-zero on success, zero on failure.
*/
int
}
/*
- * Fill cache of file-backed EP with elements starting at ID.
+ * Fill cache of file-backed @ep with elements starting at @id.
* If any were read, return their number.
* Else return -1 and leave the cache unchanged.
*/
}
/*
- * Write COUNT elements starting at ID from BUF to file-backed EP.
+ * Write @count elements starting at @id from @buf to file-backed @ep.
* Update the timestamp if the table is EFF_TYPED.
* Don't actually write if table is privately mapped.
* Return 0 on success, -1 on error (file may be corrupt then).
{
int i, n, ret;
char *p;
- struct emptypedstr *elt;
+ struct ef_typedstr *elt;
time_t now;
if (CANT_HAPPEN(ep->fd < 0 || id < 0 || count < 0))
* TODO Oopses here could be due to bad data corruption.
* Fail instead of attempting to recover?
*/
- elt = (struct emptypedstr *)((char *)buf + i * ep->size);
+ elt = (struct ef_typedstr *)((char *)buf + i * ep->size);
if (CANT_HAPPEN(elt->ef_type != ep->uid))
elt->ef_type = ep->uid;
if (CANT_HAPPEN(elt->uid != id + i))
}
/*
- * Write element ID into table TYPE from buffer FROM.
+ * Write element @id into table @type from buffer @from.
* FIXME pass buffer size!
- * Update timestamp in FROM if table is EFF_TYPED.
+ * Update timestamp in @from if table is EFF_TYPED.
* If table is file-backed and not privately mapped, write through
* cache straight to disk.
* Cannot write beyond the end of fully cached table (flags & EFF_MEM).
* Can write at the end of partially cached table.
- * FROM must be fresh; see ef_make_stale().
+ * @from must be fresh; see ef_make_stale().
* Return non-zero on success, zero on failure.
*/
int
ep = &empfile[type];
if (CANT_HAPPEN((ep->flags & (EFF_MEM | EFF_PRIVATE)) == EFF_PRIVATE))
return 0;
- if (CANT_HAPPEN((ep->flags & EFF_MEM) ? id >= ep->fids : id > ep->fids))
- return 0; /* not implemented */
+ if (CANT_HAPPEN(id < 0))
+ return 0;
+ if (CANT_HAPPEN(ep->nent >= 0 && id >= ep->nent))
+ return 0; /* beyond fixed size */
new_seqno(ep, from);
if (id >= ep->fids) {
- /* write beyond end of file extends it, take note */
+ /* beyond end of file */
+ if (CANT_HAPPEN((ep->flags & EFF_MEM) || id > ep->fids))
+ return 0; /* not implemented */
+ /* write at end of file extends it */
ep->fids = id + 1;
if (ep->onresize)
ep->onresize(type);
/*
* Change element id.
- * BUF is an element of table TYPE.
- * ID is its new element ID.
- * If table is EFF_TYPED, change id and sequence number stored in BUF.
+ * @buf is an element of table @type.
+ * @id is its new element ID.
+ * If table is EFF_TYPED, change id and sequence number stored in @buf.
* Else do nothing.
*/
void
ef_set_uid(int type, void *buf, int uid)
{
- struct emptypedstr *elt;
+ struct ef_typedstr *elt;
struct empfile *ep;
if (ef_check(type) < 0)
}
/*
- * Return sequence number of element ID in table EP.
+ * Are *A and *B equal, except for timestamps and such?
+ */
+int
+ef_typedstr_eq(struct ef_typedstr *a, struct ef_typedstr *b)
+{
+ return a->ef_type == b->ef_type
+ && a->seqno == b->seqno
+ && a->uid == b->uid
+ && !memcmp((char *)a + sizeof(*a), (char *)b + sizeof(*a),
+ empfile[a->ef_type].size - sizeof(*a));
+}
+
+/*
+ * Return sequence number of element @id in table @ep.
* Return zero if table is not EFF_TYPED (it has no sequence number
* then).
*/
static unsigned
get_seqno(struct empfile *ep, int id)
{
- struct emptypedstr *elt;
+ struct ef_typedstr *elt;
if (!(ep->flags & EFF_TYPED))
return 0;
}
/*
- * Increment sequence number in BUF, which is about to be written to EP.
+ * Increment sequence number in @buf, which is about to be written to @ep.
* Do nothing if table is not EFF_TYPED (it has no sequence number
* then).
- * Else, BUF's sequence number must match the one in EP's cache. If
+ * Else, @buf's sequence number must match the one in @ep's cache. If
* it doesn't, we're about to clobber a previous write.
*/
static void
new_seqno(struct empfile *ep, void *buf)
{
- struct emptypedstr *elt = buf;
+ struct ef_typedstr *elt = buf;
unsigned old_seqno;
if (!(ep->flags & EFF_TYPED))
ef_generation++;
}
-/* Mark copy of an element of table TYPE in BUF fresh. */
+/* Mark copy of an element of table TYPE in BUF fresh. */
void
ef_mark_fresh(int type, void *buf)
{
ep = &empfile[type];
if (!(ep->flags & EFF_TYPED))
return;
- ((struct emptypedstr *)buf)->generation = ef_generation;
+ ((struct ef_typedstr *)buf)->generation = ef_generation;
}
static void
must_be_fresh(struct empfile *ep, void *buf)
{
- struct emptypedstr *elt = buf;
+ struct ef_typedstr *elt = buf;
if (!(ep->flags & EFF_TYPED))
return;
}
/*
- * Extend table TYPE by COUNT elements.
+ * Extend table @type by @count elements.
* Any pointers obtained from ef_ptr() become invalid.
* Return non-zero on success, zero on failure.
*/
ef_extend(int type, int count)
{
struct empfile *ep;
- char *p;
- int need_sentinel, i, id;
- if (ef_check(type) < 0 || CANT_HAPPEN(EF_IS_VIEW(type)))
+ if (ef_check(type) < 0)
return 0;
ep = &empfile[type];
- if (CANT_HAPPEN(count < 0))
+ if (ep->nent >= 0) {
+ logerror("Can't extend %s, its size is fixed", ep->name);
+ return 0;
+ }
+ if (!do_extend(ep, count))
+ return 0;
+ if (ep->onresize)
+ ep->onresize(type);
+ return 1;
+}
+
+static int
+do_extend(struct empfile *ep, int count)
+{
+ char *p;
+ int need_sentinel, i, id;
+
+ if (CANT_HAPPEN(EF_IS_VIEW(ep->uid)) || count < 0)
return 0;
id = ep->fids;
}
}
ep->fids = id + count;
- if (ep->onresize)
- ep->onresize(type);
return 1;
}
/*
- * Initialize element ID for table TYPE in BUF.
+ * Initialize element @id for table @type in @buf.
* FIXME pass buffer size!
- * BUF is marked fresh with ef_mark_fresh().
+ * @buf is marked fresh with ef_mark_fresh().
*/
void
ef_blank(int type, int id, void *buf)
{
struct empfile *ep;
- struct emptypedstr *elt;
+ struct ef_typedstr *elt;
if (ef_check(type) < 0)
return;
}
/*
- * Initialize COUNT elements of EP in BUF, starting with element ID.
+ * Initialize @count elements of @ep in @buf, starting with element @id.
*/
static void
do_blank(struct empfile *ep, void *buf, int id, int count)
{
int i;
- struct emptypedstr *elt;
+ struct ef_typedstr *elt;
memset(buf, 0, count * ep->size);
for (i = 0; i < count; i++) {
- elt = (struct emptypedstr *)((char *)buf + i * ep->size);
+ elt = (struct ef_typedstr *)((char *)buf + i * ep->size);
if (ep->flags & EFF_TYPED) {
elt->ef_type = ep->uid;
elt->uid = id + i;
}
/*
- * Truncate table TYPE to COUNT elements.
+ * Truncate table @type to @count elements.
* Any pointers obtained from ef_ptr() become invalid.
* Return non-zero on success, zero on failure.
*/
if (ef_check(type) < 0 || CANT_HAPPEN(EF_IS_VIEW(type)))
return 0;
ep = &empfile[type];
+ if (ep->nent >= 0) {
+ logerror("Can't truncate %s, its size is fixed", ep->name);
+ return 0;
+ }
if (CANT_HAPPEN(count < 0 || count > ep->fids))
return 0;
}
/*
- * Search for a table matching NAME, return its table type.
+ * Search for a table matching @name, return its table type.
* Return M_NOTFOUND if there are no matches, M_NOTUNIQUE if there are
* several.
*/
}
/*
- * Search CHOICES[] for a table type matching NAME, return it.
+ * Search @choices[] for a table type matching @name, return it.
* Return M_NOTFOUND if there are no matches, M_NOTUNIQUE if there are
* several.
- * CHOICES[] must be terminated with a negative value.
+ * @choices[] must be terminated with a negative value.
*/
int
ef_byname_from(char *name, int choices[])
return res;
}
+/*
+ * Return name of table @type. Always a single, short word.
+ */
char *
ef_nameof(int type)
{
if (ef_check(type) < 0)
- return "bad ef_type";
+ return "bad";
return empfile[type].name;
}
+/*
+ * Return "pretty" name of table @type.
+ */
+char *
+ef_nameof_pretty(int type)
+{
+ if (ef_check(type) < 0)
+ return "bad";
+ return empfile[type].pretty_name;
+}
+
static int
ef_check(int type)
{
}
/*
- * Ensure table contains element ID.
- * If necessary, extend it in steps of COUNT elements.
+ * Ensure table @type contains element @id.
+ * If necessary, extend it in steps of @count elements.
* Return non-zero on success, zero on failure.
*/
int
ef_ensure_space(int type, int id, int count)
{
- if (ef_check(type) < 0)
+ if (ef_check(type) < 0 || CANT_HAPPEN(id < 0))
return 0;
- CANT_HAPPEN(id < 0);
while (id >= empfile[type].fids) {
if (!ef_extend(type, count))
}
return 1;
}
+
+/*
+ * Return maximum ID acceptable for table @type.
+ * Assuming infinite memory and disk space.
+ */
+int
+ef_id_limit(int type)
+{
+ struct empfile *ep;
+
+ if (ef_check(type) < 0)
+ return -1;
+ ep = &empfile[type];
+ if (ep->nent >= 0)
+ return ep->nent - 1;
+ if (ep->flags & EFF_MEM) {
+ if (ep->flags & EFF_STATIC)
+ return ep->csize - 1 - ((ep->flags & EFF_SENTINEL) != 0);
+ }
+ return INT_MAX;
+}