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

@ -588,7 +588,7 @@ doland(char op, int arg, char *p, struct sctstr *sect)
return RET_SYN;
sect->sct_x = newx;
sect->sct_y = newy;
sect->sct_uid = XYOFFSET(newx, newy);
ef_set_uid(EF_SECTOR, &sect, XYOFFSET(newx, newy));
break;
case 'D':
if (!sarg_xy(p, &newx, &newy))
@ -771,7 +771,7 @@ doship(char op, int arg, char *p, struct shpstr *ship)
ship->shp_rflags = arg;
break;
case 'U':
ship->shp_uid = arg;
ef_set_uid(EF_SHIP, ship, arg);
break;
case 'O':
if (ship->shp_own)
@ -875,7 +875,7 @@ dounit(char op, int arg, char *p, struct lndstr *land)
land->lnd_land = arg;
break;
case 'U':
land->lnd_uid = arg;
ef_set_uid(EF_LAND, land, arg);
break;
case 'O':
if (land->lnd_own)
@ -998,7 +998,7 @@ doplane(char op, int arg, char *p, struct plnstr *plane)
plane->pln_nuketype = arg;
break;
case 'U':
plane->pln_uid = arg;
ef_set_uid(EF_PLANE, plane, arg);
break;
case 'l':
if (!sarg_xy(p, &newx, &newy))

View file

@ -63,14 +63,14 @@ swaps(void)
/* change the location of secta to that of sectb */
secta.sct_x = sectb.sct_x;
secta.sct_y = sectb.sct_y;
secta.sct_uid = sectb.sct_uid;
ef_set_uid(EF_SECTOR, &secta, sectb.sct_uid);
secta.sct_dist_x = sectb.sct_x;
secta.sct_dist_y = sectb.sct_y;
secta.sct_coastal = sectb.sct_coastal;
/* change the location of sectb to where secta was */
sectb.sct_x = tmp.sct_x;
sectb.sct_y = tmp.sct_y;
sectb.sct_uid = tmp.sct_uid;
ef_set_uid(EF_SECTOR, &sectb, tmp.sct_uid);
sectb.sct_dist_x = tmp.sct_x;
sectb.sct_dist_y = tmp.sct_y;
sectb.sct_coastal = tmp.sct_coastal;

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);
}
}
/*

View file

@ -111,7 +111,7 @@ delete_old_news(void)
for (j = 0; getnews(i + j, &news); j++) {
if (news.nws_vrb == 0)
break;
news.nws_uid = j;
ef_set_uid(EF_NEWS, &news, j);
putnews(j, &news);
}
CANT_HAPPEN(i + j != news_tail);