1 #include <ccan/tdb/tdb.h>
2 #include <ccan/grab_file/grab_file.h>
3 #include <ccan/talloc/talloc.h>
4 #include <ccan/str_talloc/str_talloc.h>
5 #include <ccan/str/str.h>
10 #define STRINGIFY2(x) #x
11 #define STRINGIFY(x) STRINGIFY2(x)
14 #define try(expr, op) \
18 if (tdb_error(tdb) != -op.ret) \
19 errx(1, "Line %u: " STRINGIFY(expr) \
21 i+1, ret, tdb_errorstr(tdb)); \
22 } else if (ret != op.ret) \
23 errx(1, "Line %u: " STRINGIFY(expr) "= %i: %s", \
24 i+1, ret, tdb_errorstr(tdb)); \
27 /* Try or imitate results. */
28 #define unreliable(expr, expect, force, undo) \
31 if (ret != expect) { \
32 warnx("Line %u: %s gave %i not %i", \
33 i+1, STRINGIFY(expr), ret, expect); \
44 OP_TDB_LOCKALL_UNMARK,
45 OP_TDB_LOCKALL_NONBLOCK,
48 OP_TDB_LOCKALL_READ_NONBLOCK,
49 OP_TDB_UNLOCKALL_READ,
51 OP_TDB_CHAINLOCK_NONBLOCK,
52 OP_TDB_CHAINLOCK_MARK,
53 OP_TDB_CHAINLOCK_UNMARK,
55 OP_TDB_CHAINLOCK_READ,
56 OP_TDB_CHAINUNLOCK_READ,
57 OP_TDB_INCREMENT_SEQNUM_NONBLOCK,
64 OP_TDB_TRANSACTION_START,
65 OP_TDB_TRANSACTION_CANCEL,
66 OP_TDB_TRANSACTION_COMMIT,
67 OP_TDB_TRAVERSE_READ_START,
68 OP_TDB_TRAVERSE_START,
86 static unsigned char hex_char(unsigned int line, char c)
89 if (c >= 'A' && c <= 'F')
91 if (c >= '0' && c <= '9')
93 errx(1, "Line %u: invalid hex character '%c'", line, c);
96 /* TDB data is <size>:<%02x>* */
97 static TDB_DATA make_tdb_data(const void *ctx,
98 unsigned int line, const char *word)
104 data.dsize = atoi(word);
105 data.dptr = talloc_array(ctx, unsigned char, data.dsize);
106 p = strchr(word, ':');
108 errx(1, "Line %u: Invalid tdb data '%s'", line, word);
110 for (i = 0; i < data.dsize; i++)
111 data.dptr[i] = hex_char(line, p[i*2])*16
112 + hex_char(line, p[i*2+1]);
116 static void add_op(struct op **op, unsigned int i,
117 enum op_type type, const char *key, const char *data,
121 *op = talloc_realloc(NULL, *op, struct op, i+1);
127 new->key = make_tdb_data(*op, i+1, key);
131 new->data = make_tdb_data(*op, i+1, data);
133 new->data = tdb_null;
136 static int get_len(TDB_DATA key, TDB_DATA data, void *private_data)
141 struct traverse_info {
143 unsigned int start_line;
148 static unsigned run_ops(struct tdb_context *tdb, const struct op op[],
149 unsigned int start, unsigned int stop);
151 static int traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
154 struct traverse_info *tinfo = _tinfo;
156 if (tinfo->i == tinfo->num)
157 errx(1, "Transaction starting line %u did not terminate",
160 if (tinfo->op[tinfo->i].op != OP_TDB_TRAVERSE)
161 errx(1, "Transaction starting line %u terminatd early",
164 /* Check we have right key. */
165 if (key.dsize != tinfo->op[tinfo->i].key.dsize
166 || memcmp(key.dptr, tinfo->op[tinfo->i].key.dptr, key.dsize))
167 errx(1, "Line %u: bad traverse key", tinfo->i+1);
168 if (data.dsize != tinfo->op[tinfo->i].data.dsize
169 || memcmp(data.dptr, tinfo->op[tinfo->i].data.dptr, data.dsize))
170 errx(1, "Line %u: bad traverse data", tinfo->i+1);
173 /* Run any normal ops. */
174 tinfo->i = run_ops(tdb, tinfo->op, tinfo->i, tinfo->num);
176 if (tinfo->op[tinfo->i].op == OP_TDB_TRAVERSE_END)
181 static unsigned op_traverse(struct tdb_context *tdb,
182 const struct op op[],
186 struct traverse_info tinfo = { op, line, line, num };
188 tdb_traverse(tdb, traverse, &tinfo);
189 if (tinfo.i < num && op[tinfo.i].op != OP_TDB_TRAVERSE_END)
190 errx(1, "%u: Short traversal", line);
194 static unsigned op_read_traverse(struct tdb_context *tdb,
195 const struct op op[],
199 struct traverse_info tinfo = { op, line, line, num };
201 tdb_traverse_read(tdb, traverse, &tinfo);
202 if (tinfo.i < num && op[tinfo.i].op != OP_TDB_TRAVERSE_END)
203 errx(1, "%u: Short traversal", line);
207 static __attribute__((noinline))
208 unsigned run_ops(struct tdb_context *tdb, const struct op op[],
209 unsigned int start, unsigned int stop)
214 for (i = start; i < stop; i++) {
217 try(tdb_lockall(tdb), op[i]);
219 case OP_TDB_LOCKALL_MARK:
220 try(tdb_lockall_mark(tdb), op[i]);
222 case OP_TDB_LOCKALL_UNMARK:
223 try(tdb_lockall_unmark(tdb), op[i]);
225 case OP_TDB_LOCKALL_NONBLOCK:
226 unreliable(tdb_lockall_nonblock(tdb), op[i].ret,
227 tdb_lockall(tdb), tdb_unlockall(tdb));
229 case OP_TDB_UNLOCKALL:
230 try(tdb_unlockall(tdb), op[i]);
232 case OP_TDB_LOCKALL_READ:
233 try(tdb_lockall_read(tdb), op[i]);
235 case OP_TDB_LOCKALL_READ_NONBLOCK:
236 unreliable(tdb_lockall_read_nonblock(tdb), op[i].ret,
237 tdb_lockall_read(tdb),
238 tdb_unlockall_read(tdb));
240 case OP_TDB_UNLOCKALL_READ:
241 try(tdb_unlockall_read(tdb), op[i]);
243 case OP_TDB_CHAINLOCK:
244 try(tdb_chainlock(tdb, op[i].key), op[i]);
246 case OP_TDB_CHAINLOCK_NONBLOCK:
247 unreliable(tdb_chainlock_nonblock(tdb, op[i].key),
249 tdb_chainlock(tdb, op[i].key),
250 tdb_chainunlock(tdb, op[i].key));
252 case OP_TDB_CHAINLOCK_MARK:
253 try(tdb_chainlock_mark(tdb, op[i].key), op[i]);
255 case OP_TDB_CHAINLOCK_UNMARK:
256 try(tdb_chainlock_unmark(tdb, op[i].key), op[i]);
258 case OP_TDB_CHAINUNLOCK:
259 try(tdb_chainunlock(tdb, op[i].key), op[i]);
261 case OP_TDB_CHAINLOCK_READ:
262 try(tdb_chainlock_read(tdb, op[i].key), op[i]);
264 case OP_TDB_CHAINUNLOCK_READ:
265 try(tdb_chainunlock_read(tdb, op[i].key), op[i]);
267 case OP_TDB_INCREMENT_SEQNUM_NONBLOCK:
268 tdb_increment_seqnum_nonblock(tdb);
270 case OP_TDB_PARSE_RECORD:
271 try(tdb_parse_record(tdb, op[i].key, get_len, NULL), op[i]);
274 try(tdb_exists(tdb, op[i].key), op[i]);
277 try(tdb_store(tdb, op[i].key, op[i].data, op[i].flag), op[i]);
280 try(tdb_append(tdb, op[i].key, op[i].data), op[i]);
282 case OP_TDB_GET_SEQNUM:
283 try(tdb_get_seqnum(tdb), op[i]);
285 case OP_TDB_WIPE_ALL:
286 try(tdb_wipe_all(tdb), op[i]);
288 case OP_TDB_TRANSACTION_START:
289 try(tdb_transaction_start(tdb), op[i]);
291 case OP_TDB_TRANSACTION_CANCEL:
292 try(tdb_transaction_cancel(tdb), op[i]);
294 case OP_TDB_TRANSACTION_COMMIT:
295 try(tdb_transaction_commit(tdb), op[i]);
297 case OP_TDB_TRAVERSE_READ_START:
298 i = op_read_traverse(tdb, op, i+1, stop);
300 case OP_TDB_TRAVERSE_START:
301 i = op_traverse(tdb, op, i+1, stop);
303 case OP_TDB_TRAVERSE:
304 case OP_TDB_TRAVERSE_END:
305 /* Either of these mean we're in a traversal,
306 * finished this iteration. */
308 case OP_TDB_FIRSTKEY:
309 data = tdb_firstkey(tdb);
310 if (data.dsize != op[i].data.dsize
311 || memcmp(data.dptr, op[i].data.dptr, data.dsize))
312 errx(1, "Line %u: bad firstkey", i+1);
315 data = tdb_nextkey(tdb, op[i].key);
316 if (data.dsize != op[i].data.dsize
317 || memcmp(data.dptr, op[i].data.dptr, data.dsize))
318 errx(1, "Line %u: bad nextkey", i+1);
321 data = tdb_fetch(tdb, op[i].key);
322 if (data.dsize != op[i].data.dsize
323 || memcmp(data.dptr, op[i].data.dptr, data.dsize))
324 errx(1, "Line %u: bad fetch", i+1);
327 try(tdb_delete(tdb, op[i].key), op[i]);
330 errx(1, "Line %u: unexpected close", i+1);
337 int main(int argc, char *argv[])
342 struct tdb_context *tdb = NULL;
343 struct op *op = talloc_array(NULL, struct op, 1);
344 struct timeval start, end;
347 errx(1, "Usage: %s <tracefile> <tdbfile>", argv[0]);
349 file = grab_file(NULL, argv[1], NULL);
351 err(1, "Reading %s", argv[1]);
353 lines = strsplit(file, file, "\n", NULL);
355 for (i = 0; lines[i]; i++) {
356 char **words = strsplit(lines, lines[i], " ", NULL);
357 if (!tdb && !streq(words[0], "tdb_open"))
358 errx(1, "Line %u is not tdb_open", i+1);
360 if (streq(words[0], "tdb_open")) {
362 errx(1, "Line %u: tdb_open again?", i+1);
363 tdb = tdb_open(argv[2], atoi(words[2]),
364 strtoul(words[3], NULL, 0),
365 strtoul(words[4], NULL, 0), 0600);
367 err(1, "Opening tdb %s", argv[2]);
368 } else if (streq(words[0], "tdb_lockall")) {
369 add_op(&op, i, OP_TDB_LOCKALL, NULL, NULL, 0, 0);
370 } else if (streq(words[0], "tdb_lockall_mark")) {
371 add_op(&op, i, OP_TDB_LOCKALL_MARK, NULL, NULL, 0, 0);
372 } else if (streq(words[0], "tdb_lockall_unmark")) {
373 add_op(&op, i, OP_TDB_LOCKALL_UNMARK, NULL, NULL, 0, 0);
374 } else if (streq(words[0], "tdb_lockall_nonblock")) {
375 add_op(&op, i, OP_TDB_LOCKALL_NONBLOCK, NULL, NULL, 0,
377 } else if (streq(words[0], "tdb_unlockall")) {
378 add_op(&op, i, OP_TDB_UNLOCKALL, NULL, NULL, 0, 0);
379 } else if (streq(words[0], "tdb_lockall_read")) {
380 add_op(&op, i, OP_TDB_LOCKALL_READ, NULL, NULL, 0, 0);
381 } else if (streq(words[0], "tdb_lockall_read_nonblock")) {
382 add_op(&op, i, OP_TDB_LOCKALL_READ_NONBLOCK, NULL, NULL,
384 } else if (streq(words[0], "tdb_unlockall_read\n")) {
385 add_op(&op, i, OP_TDB_UNLOCKALL_READ, NULL, NULL, 0, 0);
386 } else if (streq(words[0], "tdb_chainlock")) {
387 add_op(&op, i, OP_TDB_CHAINLOCK, words[1], NULL, 0, 0);
388 } else if (streq(words[0], "tdb_chainlock_nonblock")) {
389 add_op(&op, i, OP_TDB_CHAINLOCK_NONBLOCK,
390 words[1], NULL, 0, atoi(words[3]));
391 } else if (streq(words[0], "tdb_chainlock_mark")) {
392 add_op(&op, i, OP_TDB_CHAINLOCK_MARK, words[1], NULL,
394 } else if (streq(words[0], "tdb_chainlock_unmark")) {
395 add_op(&op, i, OP_TDB_CHAINLOCK_UNMARK, words[1], NULL,
397 } else if (streq(words[0], "tdb_chainunlock")) {
398 add_op(&op, i, OP_TDB_CHAINUNLOCK, words[1], NULL,
400 } else if (streq(words[0], "tdb_chainlock_read")) {
401 add_op(&op, i, OP_TDB_CHAINLOCK_READ, words[1],
403 } else if (streq(words[0], "tdb_chainunlock_read")) {
404 add_op(&op, i, OP_TDB_CHAINUNLOCK_READ, words[1],
406 } else if (streq(words[0], "tdb_close")) {
407 add_op(&op, i, OP_TDB_CLOSE, NULL, NULL, 0, 0);
408 } else if (streq(words[0], "tdb_increment_seqnum_nonblock")) {
409 add_op(&op, i, OP_TDB_INCREMENT_SEQNUM_NONBLOCK,
411 } else if (streq(words[0], "tdb_fetch")) {
412 if (streq(words[3], "ENOENT"))
413 add_op(&op, i, OP_TDB_FETCH, words[1], NULL, 0,
416 add_op(&op, i, OP_TDB_FETCH, words[1], words[3],
418 } else if (streq(words[0], "tdb_parse_record")) {
419 if (streq(words[3], "ENOENT"))
420 add_op(&op, i, OP_TDB_PARSE_RECORD,
421 words[1], NULL, 0, -TDB_ERR_NOEXIST);
423 add_op(&op, i, OP_TDB_PARSE_RECORD,
424 words[1], NULL, 0, atoi(words[3]));
425 } else if (streq(words[0], "tdb_exists")) {
426 add_op(&op, i, OP_TDB_EXISTS, words[1], NULL, 0,
428 } else if (streq(words[0], "tdb_delete")) {
429 add_op(&op, i, OP_TDB_DELETE, words[1], NULL, 0,
430 streq(words[3], "ENOENT")
431 ? -TDB_ERR_NOEXIST : 0);
432 } else if (streq(words[0], "tdb_store")) {
435 if (streq(words[1], "insert"))
437 else if (streq(words[1], "modify"))
439 else if (streq(words[1], "normal"))
442 errx(1, "Line %u: invalid tdb_store", i+1);
444 if (streq(words[5], "EEXIST"))
445 add_op(&op, i, OP_TDB_STORE, words[2], words[3],
446 flag, -TDB_ERR_EXISTS);
447 else if (streq(words[5], "ENOENT"))
448 add_op(&op, i, OP_TDB_STORE, words[2], words[3],
449 flag, -TDB_ERR_NOEXIST);
451 add_op(&op, i, OP_TDB_STORE, words[2], words[3],
453 } else if (streq(words[0], "tdb_append")) {
454 add_op(&op, i, OP_TDB_STORE, words[1], words[2], 0, 0);
455 } else if (streq(words[0], "tdb_get_seqnum")) {
456 add_op(&op, i, OP_TDB_GET_SEQNUM, NULL, NULL, 0,
458 } else if (streq(words[0], "tdb_wipe_all")) {
459 add_op(&op, i, OP_TDB_WIPE_ALL, NULL, NULL, 0, 0);
460 } else if (streq(words[0], "tdb_transaction_start")) {
461 add_op(&op, i, OP_TDB_TRANSACTION_START, NULL, NULL,
463 } else if (streq(words[0], "tdb_transaction_cancel")) {
464 add_op(&op, i, OP_TDB_TRANSACTION_CANCEL, NULL, NULL,
466 } else if (streq(words[0], "tdb_transaction_commit")) {
467 add_op(&op, i, OP_TDB_TRANSACTION_COMMIT, NULL, NULL,
469 } else if (streq(words[0], "tdb_traverse_read_start")) {
470 add_op(&op, i, OP_TDB_TRAVERSE_READ_START, NULL, NULL,
472 } else if (streq(words[0], "tdb_traverse_start")) {
473 add_op(&op, i, OP_TDB_TRAVERSE_START, NULL, NULL,
475 } else if (streq(words[0], "tdb_traverse_end")) {
476 add_op(&op, i, OP_TDB_TRAVERSE_END, NULL, NULL,
478 } else if (streq(words[0], "traverse")) {
479 add_op(&op, i, OP_TDB_TRAVERSE, words[1], words[2],
481 } else if (streq(words[0], "tdb_firstkey")) {
482 if (streq(words[2], "ENOENT"))
483 add_op(&op, i, OP_TDB_FIRSTKEY, NULL, NULL,
484 0, -TDB_ERR_NOEXIST);
486 add_op(&op, i, OP_TDB_FIRSTKEY, NULL, words[2],
488 } else if (streq(words[0], "tdb_nextkey")) {
489 if (streq(words[3], "ENOENT"))
490 add_op(&op, i, OP_TDB_NEXTKEY, words[1], NULL,
491 0, -TDB_ERR_NOEXIST);
493 add_op(&op, i, OP_TDB_NEXTKEY,
494 words[1], words[3], 0, 0);
496 errx(1, "Line %u: unknown op '%s'", i+1, words[0]);
499 gettimeofday(&start, NULL);
500 run_ops(tdb, op, 1, i-1);
501 gettimeofday(&end, NULL);
502 if (op[i-1].op != OP_TDB_CLOSE)
503 warnx("Last operation is not tdb_close: incomplete?");
505 end.tv_sec -= start.tv_sec;
506 printf("Time replaying: %lu usec\n",
507 end.tv_sec * 1000000UL + (end.tv_usec - start.tv_usec));