You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
582 lines
13 KiB
582 lines
13 KiB
/* mplay.c - memory-mapped database log replay */ |
|
/* |
|
* Copyright 2011-2023 Howard Chu, Symas Corp. |
|
* All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted only as authorized by the OpenLDAP |
|
* Public License. |
|
* |
|
* A copy of this license is available in the file LICENSE in the |
|
* top-level directory of the distribution or, alternatively, at |
|
* <http://www.OpenLDAP.org/license.html>. |
|
*/ |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <time.h> |
|
#include <string.h> |
|
#include <ctype.h> |
|
#include <assert.h> |
|
#ifndef _WIN32 |
|
#include <unistd.h> |
|
#include <sys/types.h> |
|
#include <sys/wait.h> |
|
#endif |
|
|
|
#include "lmdb.h" |
|
|
|
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr) |
|
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0)) |
|
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \ |
|
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort())) |
|
|
|
#define MDB_SCNy(t) "z" #t |
|
|
|
#define SCMP(s) s, (sizeof(s)-1) |
|
char inbuf[8192]; |
|
char *dbuf, *kbuf; |
|
size_t dbufsize; |
|
int maxkey; |
|
|
|
#define SOFF(s) (sizeof(s)+1) |
|
|
|
#define MAXENVS 16 |
|
#define MAXTXNS 16 |
|
#define MAXCRSS 16 |
|
|
|
#define MAXPIDS 16 |
|
|
|
typedef struct crspair { |
|
void *tcrs; /* scanned text pointer */ |
|
MDB_cursor *rcrs; |
|
} crspair; |
|
|
|
typedef struct txnpair { |
|
void *ttxn; /* scanned text pointer */ |
|
MDB_txn *rtxn; |
|
crspair cursors[MAXCRSS]; |
|
int ncursors; |
|
} txnpair; |
|
|
|
typedef struct envpair { |
|
void *tenv; |
|
MDB_env *renv; |
|
txnpair txns[MAXTXNS]; |
|
int ntxns; |
|
} envpair; |
|
|
|
envpair envs[MAXENVS]; |
|
int nenvs; |
|
|
|
envpair *lastenv; |
|
txnpair *lasttxn; |
|
crspair *lastcrs; |
|
|
|
typedef struct pidpair { |
|
int tpid; |
|
pid_t rpid; |
|
int fdout, fdin; |
|
} pidpair; |
|
|
|
pidpair *lastpid; |
|
|
|
pidpair pids[MAXPIDS]; |
|
int npids; |
|
|
|
unsigned long lcount; |
|
|
|
static int unhex(unsigned char *c2) |
|
{ |
|
int x, c; |
|
x = *c2++ & 0x4f; |
|
if (x & 0x40) |
|
x -= 55; |
|
c = x << 4; |
|
x = *c2 & 0x4f; |
|
if (x & 0x40) |
|
x -= 55; |
|
c |= x; |
|
return c; |
|
} |
|
|
|
int inhex(char *in, char *out) |
|
{ |
|
char *c2 = out; |
|
while (isxdigit(*in)) { |
|
*c2++ = unhex((unsigned char *)in); |
|
in += 2; |
|
} |
|
return c2 - out; |
|
} |
|
|
|
static void addenv(void *tenv, MDB_env *renv) |
|
{ |
|
assert(nenvs < MAXENVS); |
|
envs[nenvs].tenv = tenv; |
|
envs[nenvs].renv = renv; |
|
envs[nenvs].ntxns = 0; |
|
lastenv = envs+nenvs; |
|
nenvs++; |
|
} |
|
|
|
static envpair *findenv(void *tenv) |
|
{ |
|
int i; |
|
if (!lastenv || lastenv->tenv != tenv) { |
|
for (i=0; i<nenvs; i++) |
|
if (envs[i].tenv == tenv) |
|
break; |
|
assert(i < nenvs); |
|
lastenv = &envs[i]; |
|
} |
|
return lastenv; |
|
} |
|
|
|
static void delenv(envpair *ep) |
|
{ |
|
int i = ep - envs; |
|
for (; i<nenvs-1; i++) |
|
envs[i] = envs[i+1]; |
|
nenvs--; |
|
lastenv = NULL; |
|
} |
|
|
|
static void addtxn(void *tenv, void *ttxn, MDB_txn *rtxn) |
|
{ |
|
envpair *ep; |
|
txnpair *tp; |
|
|
|
ep = findenv(tenv); |
|
assert(ep->ntxns < MAXTXNS); |
|
tp = ep->txns+ep->ntxns; |
|
tp->ttxn = ttxn; |
|
tp->rtxn = rtxn; |
|
tp->ncursors = 0; |
|
ep->ntxns++; |
|
lasttxn = tp; |
|
} |
|
|
|
static txnpair *findtxn(void *ttxn) |
|
{ |
|
int i, j; |
|
if (lasttxn && lasttxn->ttxn == ttxn) |
|
return lasttxn; |
|
if (lastenv) { |
|
for (i=0; i<lastenv->ntxns; i++) { |
|
if (lastenv->txns[i].ttxn == ttxn) { |
|
lasttxn = lastenv->txns+i; |
|
return lasttxn; |
|
} |
|
} |
|
} |
|
for (i=0; i<nenvs; i++) { |
|
if (envs+i == lastenv) continue; |
|
for (j=0; j<envs[i].ntxns; j++) { |
|
if (envs[i].txns[j].ttxn == ttxn) { |
|
lastenv = envs+i; |
|
lasttxn = envs[i].txns+j; |
|
return lasttxn; |
|
} |
|
} |
|
} |
|
assert(0); /* should have found it */ |
|
} |
|
|
|
static void deltxn(txnpair *tp) |
|
{ |
|
int i = tp - lastenv->txns; |
|
for (; i<lastenv->ntxns-1; i++) |
|
lastenv->txns[i] = lastenv->txns[i+1]; |
|
lastenv->ntxns--; |
|
lasttxn = NULL; |
|
} |
|
|
|
static void addcrs(txnpair *tp, void *tcrs, MDB_cursor *rcrs) |
|
{ |
|
int j = tp->ncursors; |
|
assert(tp->ncursors < MAXCRSS); |
|
|
|
tp->cursors[j].tcrs = tcrs; |
|
tp->cursors[j].rcrs = rcrs; |
|
tp->ncursors++; |
|
lastcrs = tp->cursors+j; |
|
} |
|
|
|
static crspair *findcrs(void *tcrs) |
|
{ |
|
int i, j, k; |
|
envpair *ep; |
|
txnpair *tp; |
|
crspair *cp; |
|
if (lastcrs && lastcrs->tcrs == tcrs) |
|
return lastcrs; |
|
if (lasttxn) { |
|
for (k=0, cp=lasttxn->cursors; k<lasttxn->ncursors; k++, cp++) { |
|
if (cp->tcrs == tcrs) { |
|
lastcrs = cp; |
|
return lastcrs; |
|
} |
|
} |
|
} |
|
if (lastenv) { |
|
for (j=0, tp=lastenv->txns; j<lastenv->ntxns; j++, tp++){ |
|
if (tp == lasttxn) |
|
continue; |
|
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) { |
|
if (cp->tcrs == tcrs) { |
|
lastcrs = cp; |
|
lasttxn = tp; |
|
return lastcrs; |
|
} |
|
} |
|
} |
|
} |
|
for (i=0, ep=envs; i<nenvs; i++, ep++) { |
|
for (j=0, tp=ep->txns; j<ep->ntxns; j++, tp++) { |
|
if (tp == lasttxn) |
|
continue; |
|
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) { |
|
if (cp->tcrs == tcrs) { |
|
lastcrs = cp; |
|
lasttxn = tp; |
|
lastenv = ep; |
|
return lastcrs; |
|
} |
|
} |
|
} |
|
} |
|
assert(0); /* should have found it already */ |
|
} |
|
|
|
static void delcrs(void *tcrs) |
|
{ |
|
int i; |
|
crspair *cp = findcrs(tcrs); |
|
mdb_cursor_close(cp->rcrs); |
|
for (i = cp - lasttxn->cursors; i<lasttxn->ncursors-1; i++) |
|
lasttxn->cursors[i] = lasttxn->cursors[i+1]; |
|
lasttxn->ncursors--; |
|
lastcrs = NULL; |
|
} |
|
|
|
void child() |
|
{ |
|
int rc; |
|
MDB_val key, data; |
|
char *ptr; |
|
|
|
while (fgets(inbuf, sizeof(inbuf), stdin)) { |
|
ptr = inbuf; |
|
if (!strncmp(ptr, SCMP("exit"))) |
|
break; |
|
|
|
if (!strncmp(ptr, SCMP("mdb_env_create"))) { |
|
void *tenv; |
|
MDB_env *renv; |
|
sscanf(ptr+SOFF("mdb_env_create"), "%p", &tenv); |
|
E(mdb_env_create(&renv)); |
|
addenv(tenv, renv); |
|
} else if (!strncmp(ptr, SCMP("mdb_env_set_maxdbs"))) { |
|
void *tenv; |
|
envpair *ep; |
|
unsigned int maxdbs; |
|
sscanf(ptr+SOFF("mdb_env_set_maxdbs"), "%p, %u", &tenv, &maxdbs); |
|
ep = findenv(tenv); |
|
E(mdb_env_set_maxdbs(ep->renv, maxdbs)); |
|
} else if (!strncmp(ptr, SCMP("mdb_env_set_mapsize"))) { |
|
void *tenv; |
|
envpair *ep; |
|
size_t mapsize; |
|
sscanf(ptr+SOFF("mdb_env_set_mapsize"), "%p, %"MDB_SCNy(u), &tenv, &mapsize); |
|
ep = findenv(tenv); |
|
E(mdb_env_set_mapsize(ep->renv, mapsize)); |
|
} else if (!strncmp(ptr, SCMP("mdb_env_open"))) { |
|
void *tenv; |
|
envpair *ep; |
|
char *path; |
|
int len; |
|
unsigned int flags, mode; |
|
sscanf(ptr+SOFF("mdb_env_open"), "%p, %n", &tenv, &len); |
|
path = ptr+SOFF("mdb_env_open")+len; |
|
ptr = strchr(path, ','); |
|
*ptr++ = '\0'; |
|
sscanf(ptr, "%u, %o", &flags, &mode); |
|
ep = findenv(tenv); |
|
E(mdb_env_open(ep->renv, path, flags, mode)); |
|
if (!maxkey) { |
|
maxkey = mdb_env_get_maxkeysize(ep->renv); |
|
kbuf = malloc(maxkey+2); |
|
dbuf = malloc(maxkey+2); |
|
dbufsize = maxkey; |
|
} |
|
} else if (!strncmp(ptr, SCMP("mdb_env_close"))) { |
|
void *tenv; |
|
envpair *ep; |
|
sscanf(ptr+SOFF("mdb_env_close"), "%p", &tenv); |
|
ep = findenv(tenv); |
|
mdb_env_close(ep->renv); |
|
delenv(ep); |
|
if (!nenvs) /* if no other envs left, this process is done */ |
|
break; |
|
} else if (!strncmp(ptr, SCMP("mdb_txn_begin"))) { |
|
unsigned int flags; |
|
void *tenv, *ttxn; |
|
envpair *ep; |
|
MDB_txn *rtxn; |
|
sscanf(ptr+SOFF("mdb_txn_begin"), "%p, %*p, %u = %p", &tenv, &flags, &ttxn); |
|
ep = findenv(tenv); |
|
E(mdb_txn_begin(ep->renv, NULL, flags, &rtxn)); |
|
addtxn(tenv, ttxn, rtxn); |
|
} else if (!strncmp(ptr, SCMP("mdb_txn_commit"))) { |
|
void *ttxn; |
|
txnpair *tp; |
|
sscanf(ptr+SOFF("mdb_txn_commit"), "%p", &ttxn); |
|
tp = findtxn(ttxn); |
|
E(mdb_txn_commit(tp->rtxn)); |
|
deltxn(tp); |
|
} else if (!strncmp(ptr, SCMP("mdb_txn_abort"))) { |
|
void *ttxn; |
|
txnpair *tp; |
|
sscanf(ptr+SOFF("mdb_txn_abort"), "%p", &ttxn); |
|
tp = findtxn(ttxn); |
|
mdb_txn_abort(tp->rtxn); |
|
deltxn(tp); |
|
} else if (!strncmp(ptr, SCMP("mdb_dbi_open"))) { |
|
void *ttxn; |
|
txnpair *tp; |
|
char *dbname; |
|
unsigned int flags; |
|
unsigned int tdbi; |
|
MDB_dbi dbi; |
|
sscanf(ptr+SOFF("mdb_dbi_open"), "%p, ", &ttxn); |
|
dbname = strchr(ptr+SOFF("mdb_dbi_open"), ','); |
|
dbname += 2; |
|
ptr = strchr(dbname, ','); |
|
*ptr++ = '\0'; |
|
if (!strcmp(dbname, "(null)")) |
|
dbname = NULL; |
|
sscanf(ptr, "%u = %u", &flags, &tdbi); |
|
tp = findtxn(ttxn); |
|
E(mdb_dbi_open(tp->rtxn, dbname, flags, &dbi)); |
|
} else if (!strncmp(ptr, SCMP("mdb_dbi_close"))) { |
|
void *tenv; |
|
envpair *ep; |
|
unsigned int tdbi; |
|
sscanf(ptr+SOFF("mdb_dbi_close"), "%p, %u", &tenv, &tdbi); |
|
ep = findenv(tenv); |
|
mdb_dbi_close(ep->renv, tdbi); |
|
} else if (!strncmp(ptr, SCMP("mdb_cursor_open"))) { |
|
void *ttxn, *tcrs; |
|
txnpair *tp; |
|
MDB_cursor *rcrs; |
|
unsigned int tdbi; |
|
sscanf(ptr+SOFF("mdb_cursor_open"), "%p, %u = %p", &ttxn, &tdbi, &tcrs); |
|
tp = findtxn(ttxn); |
|
E(mdb_cursor_open(tp->rtxn, tdbi, &rcrs)); |
|
addcrs(tp, tcrs, rcrs); |
|
} else if (!strncmp(ptr, SCMP("mdb_cursor_close"))) { |
|
void *tcrs; |
|
sscanf(ptr+SOFF("mdb_cursor_close"), "%p", &tcrs); |
|
delcrs(tcrs); |
|
} else if (!strncmp(ptr, SCMP("mdb_cursor_put"))) { |
|
void *tcrs; |
|
crspair *cp; |
|
unsigned int flags; |
|
int len; |
|
sscanf(ptr+SOFF("mdb_cursor_put"), "%p, ", &tcrs); |
|
cp = findcrs(tcrs); |
|
ptr = strchr(ptr+SOFF("mdb_cursor_put"), ','); |
|
sscanf(ptr+1, "%"MDB_SCNy(u)",", &key.mv_size); |
|
if (key.mv_size) { |
|
ptr = strchr(ptr, '['); |
|
inhex(ptr+1, kbuf); |
|
key.mv_data = kbuf; |
|
ptr += key.mv_size * 2 + 2; |
|
} |
|
ptr = strchr(ptr+1, ','); |
|
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len); |
|
if (data.mv_size > dbufsize) { |
|
dbuf = realloc(dbuf, data.mv_size+2); |
|
assert(dbuf != NULL); |
|
dbufsize = data.mv_size; |
|
} |
|
ptr += len+1; |
|
if (*ptr == '[') { |
|
inhex(ptr+1, dbuf); |
|
data.mv_data = dbuf; |
|
ptr += data.mv_size * 2 + 2; |
|
} else { |
|
sprintf(dbuf, "%09ld", (long)mdb_txn_id(lasttxn->rtxn)); |
|
} |
|
sscanf(ptr+1, "%u", &flags); |
|
E(mdb_cursor_put(cp->rcrs, &key, &data, flags)); |
|
} else if (!strncmp(ptr, SCMP("mdb_cursor_del"))) { |
|
void *tcrs; |
|
crspair *cp; |
|
unsigned int flags; |
|
sscanf(ptr+SOFF("mdb_cursor_del"), "%p, %u", &tcrs, &flags); |
|
cp = findcrs(tcrs); |
|
E(mdb_cursor_del(cp->rcrs, flags)); |
|
} else if (!strncmp(ptr, SCMP("mdb_put"))) { |
|
void *ttxn; |
|
txnpair *tp; |
|
unsigned int tdbi, flags; |
|
int len; |
|
sscanf(ptr+SOFF("mdb_put"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size); |
|
tp = findtxn(ttxn); |
|
ptr = strchr(ptr+SOFF("mdb_put"), '['); |
|
inhex(ptr+1, kbuf); |
|
key.mv_data = kbuf; |
|
ptr += key.mv_size * 2 + 2; |
|
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len); |
|
if (data.mv_size > dbufsize) { |
|
dbuf = realloc(dbuf, data.mv_size+2); |
|
assert(dbuf != NULL); |
|
dbufsize = data.mv_size; |
|
} |
|
ptr += len+1; |
|
if (*ptr == '[') { |
|
inhex(ptr+1, dbuf); |
|
ptr += data.mv_size * 2 + 2; |
|
} else { |
|
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn)); |
|
} |
|
data.mv_data = dbuf; |
|
sscanf(ptr+1, "%u", &flags); |
|
RES(MDB_KEYEXIST,mdb_put(tp->rtxn, tdbi, &key, &data, flags)); |
|
} else if (!strncmp(ptr, SCMP("mdb_del"))) { |
|
void *ttxn; |
|
txnpair *tp; |
|
unsigned int tdbi; |
|
int len; |
|
sscanf(ptr+SOFF("mdb_del"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size); |
|
tp = findtxn(ttxn); |
|
ptr = strchr(ptr+SOFF("mdb_del"), '['); |
|
inhex(ptr+1, kbuf); |
|
key.mv_data = kbuf; |
|
ptr += key.mv_size * 2 + 2; |
|
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len); |
|
if (data.mv_size > dbufsize) { |
|
dbuf = realloc(dbuf, data.mv_size+2); |
|
assert(dbuf != NULL); |
|
dbufsize = data.mv_size; |
|
} |
|
ptr += len+1; |
|
if (*ptr == '[') { |
|
inhex(ptr+1, dbuf); |
|
} else { |
|
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn)); |
|
} |
|
data.mv_data = dbuf; |
|
RES(MDB_NOTFOUND,mdb_del(tp->rtxn, tdbi, &key, &data)); |
|
} |
|
write(1, "\n", 1); |
|
} |
|
exit(0); |
|
} |
|
|
|
static pidpair *addpid(int tpid) |
|
{ |
|
int fdout[2], fdin[2]; |
|
pid_t pid; |
|
assert(npids < MAXPIDS); |
|
pids[npids].tpid = tpid; |
|
pipe(fdout); |
|
pipe(fdin); |
|
if ((pid = fork()) == 0) { |
|
/* child */ |
|
fflush(stdin); |
|
fflush(stdout); |
|
dup2(fdout[0], 0); |
|
dup2(fdin[1], 1); |
|
child(); |
|
return 0; /* NOTREACHED */ |
|
} else { |
|
pids[npids].rpid = pid; |
|
pids[npids].fdout = fdout[1]; |
|
pids[npids].fdin = fdin[0]; |
|
lastpid = pids+npids; |
|
npids++; |
|
return lastpid; |
|
} |
|
} |
|
|
|
static pidpair *findpid(int tpid) |
|
{ |
|
int i; |
|
if (!lastpid || lastpid->tpid != tpid) { |
|
for (i=0; i<npids; i++) |
|
if (pids[i].tpid == tpid) |
|
break; |
|
if (i == npids) |
|
return NULL; |
|
lastpid = &pids[i]; |
|
} |
|
return lastpid; |
|
} |
|
|
|
volatile pid_t killpid; |
|
|
|
static void delpid(int tpid) |
|
{ |
|
pidpair *pp = findpid(tpid); |
|
if (pp) { |
|
pid_t kpid = pp->rpid; |
|
killpid = kpid; |
|
write(pp->fdout, "exit\n", sizeof("exit")); |
|
while (killpid == kpid) |
|
usleep(10000); |
|
} |
|
} |
|
|
|
static void reaper(int sig) |
|
{ |
|
int status, i; |
|
pid_t pid = waitpid(-1, &status, 0); |
|
if (pid > 0) { |
|
fprintf(stderr, "# %s %d\n", WIFEXITED(status) ? "exited" : "killed", pid); |
|
for (i=0; i<npids; i++) |
|
if (pids[i].rpid == pid) |
|
break; |
|
assert(i < npids); |
|
close(pids[i].fdout); |
|
close(pids[i].fdin); |
|
for (;i<npids-1; i++) |
|
pids[i] = pids[i+1]; |
|
npids--; |
|
killpid = 0; |
|
} |
|
} |
|
|
|
int main(int argc,char * argv[]) |
|
{ |
|
signal(SIGCHLD, reaper); |
|
|
|
while (fgets(inbuf, sizeof(inbuf), stdin)) { |
|
pidpair *pp; |
|
int tpid, len; |
|
char c, *ptr; |
|
lcount++; |
|
|
|
if (inbuf[0] == '#' && !strncmp(inbuf+1, SCMP(" killed"))) { |
|
sscanf(inbuf+SOFF("killed"),"%d", &tpid); |
|
delpid(tpid); |
|
continue; |
|
} |
|
|
|
if (inbuf[0] != '>') |
|
continue; |
|
ptr = inbuf+1; |
|
sscanf(ptr, "%d:%n", &tpid, &len); |
|
pp = findpid(tpid); |
|
if (!pp) |
|
pp = addpid(tpid); /* new process */ |
|
|
|
ptr = inbuf+len+1; |
|
len = strlen(ptr); |
|
write(pp->fdout, ptr, len); /* send command and wait for ack */ |
|
read(pp->fdin, &c, 1); |
|
} |
|
while (npids) |
|
delpid(pids[0].tpid); |
|
}
|
|
|