Add sequence numbers to game state

This oopses on output dependency violations, e.g. two threads doing a
read-modify-write without synchronization, or the one thread nesting
several read-modify-writes.  Such bugs are difficult to spot, and tend
to be abusable.  I figure we have quite a few of them.

New struct emptypedstr member seqno.  Make sure all members of unit
empobj_storage share it.  Initialize it in files: main() and
file_sct_init().  Set it in ef_blank() and new ef_set_uid() by calling
new get_seqno().  Use ef_set_uid() when copying objects: swaps(),
doland(), doship(), doplane(), dounit(), delete_old_news().  Step it
in ef_write() by calling new new_seqno().

Factor do_read() out of fillcache() to make it available for
get_seqno().
This commit is contained in:
Markus Armbruster 2008-05-17 22:44:00 +02:00
parent 087c0aae36
commit 536ef0b0a2
20 changed files with 134 additions and 19 deletions

View file

@ -48,7 +48,10 @@
static int ef_realloc_cache(struct empfile *, int);
static int fillcache(struct empfile *, int);
static int do_read(struct empfile *, void *, int, int);
static int do_write(struct empfile *, void *, int, int);
static unsigned get_seqno(struct empfile *, int);
static void new_seqno(struct empfile *, void *);
static void do_blank(struct empfile *, void *, int, int);
/*
@ -308,11 +311,28 @@ ef_read(int type, int id, void *into)
*/
static int
fillcache(struct empfile *ep, int id)
{
int ret;
if (CANT_HAPPEN(!ep->cache))
return -1;
ret = do_read(ep, ep->cache, id, MIN(ep->csize, ep->fids - id));
if (ret >= 0) {
/* cache changed */
ep->baseid = id;
ep->cids = ret;
}
return ret;
}
static int
do_read(struct empfile *ep, void *buf, int id, int count)
{
int n, ret;
char *p;
if (CANT_HAPPEN(ep->fd < 0 || !ep->cache))
if (CANT_HAPPEN(ep->fd < 0 || id < 0 || count < 0))
return -1;
if (lseek(ep->fd, id * ep->size, SEEK_SET) == (off_t)-1) {
@ -321,21 +341,21 @@ fillcache(struct empfile *ep, int id)
return -1;
}
p = ep->cache;
n = MIN(ep->csize, ep->fids - id) * ep->size;
p = buf;
n = count * ep->size;
while (n > 0) {
ret = read(ep->fd, p, n);
if (ret < 0) {
if (errno != EINTR) {
logerror("Error reading %s elt %d (%s)",
ep->file,
id + (int)((p - ep->cache) / ep->size),
id + (int)((p - (char *)buf) / ep->size),
strerror(errno));
break;
}
} else if (ret == 0) {
logerror("Unexpected EOF reading %s elt %d",
ep->file, id + (int)((p - ep->cache) / ep->size));
ep->file, id + (int)((p - (char *)buf) / ep->size));
break;
} else {
p += ret;
@ -343,12 +363,7 @@ fillcache(struct empfile *ep, int id)
}
}
if (p == ep->cache)
return -1; /* nothing read, old cache still ok */
ep->baseid = id;
ep->cids = (p - ep->cache) / ep->size;
return ep->cids;
return (p - (char *)buf) / ep->size;
}
/*
@ -440,6 +455,7 @@ ef_write(int type, int id, void *from)
ep->prewrite(id, from);
if (CANT_HAPPEN((ep->flags & EFF_MEM) ? id >= ep->fids : id > ep->fids))
return 0; /* not implemented */
new_seqno(ep, from);
if (ep->fd >= 0) {
if (do_write(ep, from, id, 1) < 0)
return 0;
@ -457,6 +473,77 @@ ef_write(int type, int id, void *from)
return 1;
}
/*
* 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.
* Else do nothing.
*/
void
ef_set_uid(int type, void *buf, int uid)
{
struct emptypedstr *elt;
struct empfile *ep;
if (ef_check(type) < 0)
return;
ep = &empfile[type];
if (!(ep->flags & EFF_TYPED))
return;
elt = buf;
if (elt->uid == uid)
return;
elt->uid = uid;
elt->seqno = get_seqno(ep, uid);
}
/*
* 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;
if (!(ep->flags & EFF_TYPED))
return 0;
if (id < 0 || id >= ep->fids)
return 0;
if (id >= ep->baseid && id < ep->baseid + ep->cids)
elt = (void *)(ep->cache + (id - ep->baseid) * ep->size);
else {
/* need a buffer, steal last cache slot */
if (ep->cids == ep->csize)
ep->cids--;
elt = (void *)(ep->cache + ep->cids * ep->size);
if (do_read(ep, elt, id, 1) < 0)
return 0; /* deep trouble */
}
return elt->seqno;
}
/*
* 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).
*/
static void
new_seqno(struct empfile *ep, void *buf)
{
struct emptypedstr *elt = buf;
unsigned old_seqno;
if (!(ep->flags & EFF_TYPED))
return;
old_seqno = get_seqno(ep, elt->uid);
if (CANT_HAPPEN(old_seqno != elt->seqno))
old_seqno = MAX(old_seqno, elt->seqno);
elt->seqno = old_seqno + 1;
}
/*
* Extend table TYPE by COUNT elements.
* Any pointers obtained from ef_ptr() become invalid.
@ -518,9 +605,17 @@ ef_extend(int type, int count)
void
ef_blank(int type, int id, void *buf)
{
struct empfile *ep;
struct emptypedstr *elt;
if (ef_check(type) < 0)
return;
do_blank(&empfile[type], buf, id, 1);
ep = &empfile[type];
do_blank(ep, buf, id, 1);
if (ep->flags & EFF_TYPED) {
elt = buf;
elt->seqno = get_seqno(ep, elt->uid);
}
}
/*