Handle tracing on internal dbs.
[ccan] / ccan / tdb / tools / replay_trace.c
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>
6 #include <err.h>
7 #include <ctype.h>
8 #include <sys/time.h>
9
10 #define STRINGIFY2(x) #x
11 #define STRINGIFY(x) STRINGIFY2(x)
12
13 /* Try or die. */
14 #define try(expr, op)                                                   \
15         do {                                                            \
16                 int ret = (expr);                                       \
17                 if (ret < 0) {                                          \
18                         if (tdb_error(tdb) != -op.ret)                  \
19                                 errx(1, "Line %u: " STRINGIFY(expr)     \
20                                      "= %i: %s",                        \
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));              \
25         } while (0)
26
27 /* Try or imitate results. */
28 #define unreliable(expr, expect, force, undo)                           \
29         do {                                                            \
30                 int ret = expr;                                         \
31                 if (ret != expect) {                                    \
32                         warnx("Line %u: %s gave %i not %i",             \
33                               i+1, STRINGIFY(expr), ret, expect);       \
34                         if (expect == 0)                                \
35                                 force;                                  \
36                         else                                            \
37                                 undo;                                   \
38                 }                                                       \
39         } while (0)
40
41 enum op_type {
42         OP_TDB_LOCKALL,
43         OP_TDB_LOCKALL_MARK,
44         OP_TDB_LOCKALL_UNMARK,
45         OP_TDB_LOCKALL_NONBLOCK,
46         OP_TDB_UNLOCKALL,
47         OP_TDB_LOCKALL_READ,
48         OP_TDB_LOCKALL_READ_NONBLOCK,
49         OP_TDB_UNLOCKALL_READ,
50         OP_TDB_CHAINLOCK,
51         OP_TDB_CHAINLOCK_NONBLOCK,
52         OP_TDB_CHAINLOCK_MARK,
53         OP_TDB_CHAINLOCK_UNMARK,
54         OP_TDB_CHAINUNLOCK,
55         OP_TDB_CHAINLOCK_READ,
56         OP_TDB_CHAINUNLOCK_READ,
57         OP_TDB_INCREMENT_SEQNUM_NONBLOCK,
58         OP_TDB_PARSE_RECORD,
59         OP_TDB_EXISTS,
60         OP_TDB_STORE,
61         OP_TDB_APPEND,
62         OP_TDB_GET_SEQNUM,
63         OP_TDB_WIPE_ALL,
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,
69         OP_TDB_TRAVERSE_END,
70         OP_TDB_TRAVERSE,
71         OP_TDB_FIRSTKEY,
72         OP_TDB_NEXTKEY,
73         OP_TDB_FETCH,
74         OP_TDB_DELETE,
75         OP_TDB_CLOSE,
76 };
77
78 struct op {
79         enum op_type op;
80         TDB_DATA key;
81         TDB_DATA data;
82         int flag;
83         int ret;
84 };
85
86 static unsigned char hex_char(unsigned int line, char c)
87 {
88         c = toupper(c);
89         if (c >= 'A' && c <= 'F')
90                 return c - 'A' + 10;
91         if (c >= '0' && c <= '9')
92                 return c - '0';
93         errx(1, "Line %u: invalid hex character '%c'", line, c);
94 }
95
96 /* TDB data is <size>:<%02x>* */
97 static TDB_DATA make_tdb_data(const void *ctx,
98                               unsigned int line, const char *word)
99 {
100         TDB_DATA data;
101         unsigned int i;
102         const char *p;
103
104         data.dsize = atoi(word);
105         data.dptr = talloc_array(ctx, unsigned char, data.dsize);
106         p = strchr(word, ':');
107         if (!p)
108                 errx(1, "Line %u: Invalid tdb data '%s'", line, word);
109         p++;
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]);
113         return data;
114 }
115
116 static void add_op(struct op **op, unsigned int i,
117                    enum op_type type, const char *key, const char *data,
118                    int flag, int ret)
119 {
120         struct op *new;
121         *op = talloc_realloc(NULL, *op, struct op, i+1);
122         new = (*op) + i;
123         new->op = type;
124         new->flag = flag;
125         new->ret = ret;
126         if (key)
127                 new->key = make_tdb_data(*op, i+1, key);
128         else
129                 new->key = tdb_null;
130         if (data)
131                 new->data = make_tdb_data(*op, i+1, data);
132         else
133                 new->data = tdb_null;
134 }
135
136 static int get_len(TDB_DATA key, TDB_DATA data, void *private_data)
137 {
138         return data.dsize;
139 }
140
141 struct traverse_info {
142         const struct op *op;
143         unsigned int start_line;
144         unsigned int i;
145         unsigned int num;
146 };
147
148 static unsigned run_ops(struct tdb_context *tdb, const struct op op[],
149                         unsigned int start, unsigned int stop);
150
151 static int traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data,
152                     void *_tinfo)
153 {
154         struct traverse_info *tinfo = _tinfo;
155
156         if (tinfo->i == tinfo->num)
157                 errx(1, "Transaction starting line %u did not terminate",
158                      tinfo->start_line);
159
160         if (tinfo->op[tinfo->i].op != OP_TDB_TRAVERSE)
161                 errx(1, "Transaction starting line %u terminatd early",
162                      tinfo->start_line);
163
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);
171
172         tinfo->i++;
173         /* Run any normal ops. */
174         tinfo->i = run_ops(tdb, tinfo->op, tinfo->i, tinfo->num);
175
176         if (tinfo->op[tinfo->i].op == OP_TDB_TRAVERSE_END)
177                 return 1;
178         return 0;
179 }
180
181 static unsigned op_traverse(struct tdb_context *tdb,
182                             const struct op op[],
183                             unsigned int line,
184                             unsigned int num)
185 {
186         struct traverse_info tinfo = { op, line, line, num };
187
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);
191         return tinfo.i;
192 }
193
194 static unsigned op_read_traverse(struct tdb_context *tdb,
195                                  const struct op op[],
196                                  unsigned int line,
197                                  unsigned int num)
198 {
199         struct traverse_info tinfo = { op, line, line, num };
200
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);
204         return tinfo.i;
205 }
206
207 static __attribute__((noinline))
208 unsigned run_ops(struct tdb_context *tdb, const struct op op[],
209                         unsigned int start, unsigned int stop)
210 {
211         unsigned int i;
212         TDB_DATA data;
213
214         for (i = start; i < stop; i++) {
215                 switch (op[i].op) {
216                 case OP_TDB_LOCKALL:
217                         try(tdb_lockall(tdb), op[i]);
218                         break;
219                 case OP_TDB_LOCKALL_MARK:
220                         try(tdb_lockall_mark(tdb), op[i]);
221                         break;
222                 case OP_TDB_LOCKALL_UNMARK:
223                         try(tdb_lockall_unmark(tdb), op[i]);
224                         break;
225                 case OP_TDB_LOCKALL_NONBLOCK:
226                         unreliable(tdb_lockall_nonblock(tdb), op[i].ret,
227                                    tdb_lockall(tdb), tdb_unlockall(tdb));
228                         break;
229                 case OP_TDB_UNLOCKALL:
230                         try(tdb_unlockall(tdb), op[i]);
231                         break;
232                 case OP_TDB_LOCKALL_READ:
233                         try(tdb_lockall_read(tdb), op[i]);
234                         break;
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));
239                         break;
240                 case OP_TDB_UNLOCKALL_READ:
241                         try(tdb_unlockall_read(tdb), op[i]);
242                         break;
243                 case OP_TDB_CHAINLOCK:
244                         try(tdb_chainlock(tdb, op[i].key), op[i]);
245                         break;
246                 case OP_TDB_CHAINLOCK_NONBLOCK:
247                         unreliable(tdb_chainlock_nonblock(tdb, op[i].key),
248                                    op[i].ret,
249                                    tdb_chainlock(tdb, op[i].key),
250                                    tdb_chainunlock(tdb, op[i].key));
251                         break;
252                 case OP_TDB_CHAINLOCK_MARK:
253                         try(tdb_chainlock_mark(tdb, op[i].key), op[i]);
254                         break;
255                 case OP_TDB_CHAINLOCK_UNMARK:
256                         try(tdb_chainlock_unmark(tdb, op[i].key), op[i]);
257                         break;
258                 case OP_TDB_CHAINUNLOCK:
259                         try(tdb_chainunlock(tdb, op[i].key), op[i]);
260                         break;
261                 case OP_TDB_CHAINLOCK_READ:
262                         try(tdb_chainlock_read(tdb, op[i].key), op[i]);
263                         break;
264                 case OP_TDB_CHAINUNLOCK_READ:
265                         try(tdb_chainunlock_read(tdb, op[i].key), op[i]);
266                         break;
267                 case OP_TDB_INCREMENT_SEQNUM_NONBLOCK:
268                         tdb_increment_seqnum_nonblock(tdb);
269                         break;
270                 case OP_TDB_PARSE_RECORD:
271                         try(tdb_parse_record(tdb, op[i].key, get_len, NULL), op[i]);
272                         break;
273                 case OP_TDB_EXISTS:
274                         try(tdb_exists(tdb, op[i].key), op[i]);
275                         break;
276                 case OP_TDB_STORE:
277                         try(tdb_store(tdb, op[i].key, op[i].data, op[i].flag), op[i]);
278                         break;
279                 case OP_TDB_APPEND:
280                         try(tdb_append(tdb, op[i].key, op[i].data), op[i]);
281                         break;
282                 case OP_TDB_GET_SEQNUM:
283                         try(tdb_get_seqnum(tdb), op[i]);
284                         break;
285                 case OP_TDB_WIPE_ALL:
286                         try(tdb_wipe_all(tdb), op[i]);
287                         break;
288                 case OP_TDB_TRANSACTION_START:
289                         try(tdb_transaction_start(tdb), op[i]);
290                         break;
291                 case OP_TDB_TRANSACTION_CANCEL:
292                         try(tdb_transaction_cancel(tdb), op[i]);
293                         break;
294                 case OP_TDB_TRANSACTION_COMMIT:
295                         try(tdb_transaction_commit(tdb), op[i]);
296                         break;
297                 case OP_TDB_TRAVERSE_READ_START:
298                         i = op_read_traverse(tdb, op, i+1, stop);
299                         break;
300                 case OP_TDB_TRAVERSE_START:
301                         i = op_traverse(tdb, op, i+1, stop);
302                         break;
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. */
307                         return i;
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);
313                         break;
314                 case OP_TDB_NEXTKEY:
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);
319                         break;
320                 case OP_TDB_FETCH:
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);
325                         break;
326                 case OP_TDB_DELETE:
327                         try(tdb_delete(tdb, op[i].key), op[i]);
328                         break;
329                 case OP_TDB_CLOSE:
330                         errx(1, "Line %u: unexpected close", i+1);
331                         break;
332                 }
333         }
334         return i;
335 }
336
337 int main(int argc, char *argv[])
338 {
339         const char *file;
340         char **lines;
341         unsigned int i;
342         struct tdb_context *tdb = NULL;
343         struct op *op = talloc_array(NULL, struct op, 1);
344         struct timeval start, end;
345
346         if (argc != 3)
347                 errx(1, "Usage: %s <tracefile> <tdbfile>", argv[0]);
348
349         file = grab_file(NULL, argv[1], NULL);
350         if (!file)
351                 err(1, "Reading %s", argv[1]);
352
353         lines = strsplit(file, file, "\n", NULL);
354
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);
359
360                 if (streq(words[0], "tdb_open")) {
361                         if (tdb)
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);
366                         if (!tdb)
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,
376                                atoi(words[1]));
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,
383                                0, atoi(words[1]));
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,
393                                0, 0);
394                 } else if (streq(words[0], "tdb_chainlock_unmark")) {
395                         add_op(&op, i, OP_TDB_CHAINLOCK_UNMARK, words[1], NULL,
396                                0, 0);
397                 } else if (streq(words[0], "tdb_chainunlock")) {
398                         add_op(&op, i, OP_TDB_CHAINUNLOCK, words[1], NULL,
399                                0, 0);
400                 } else if (streq(words[0], "tdb_chainlock_read")) {
401                         add_op(&op, i, OP_TDB_CHAINLOCK_READ, words[1],
402                                NULL, 0, 0);
403                 } else if (streq(words[0], "tdb_chainunlock_read")) {
404                         add_op(&op, i, OP_TDB_CHAINUNLOCK_READ, words[1],
405                                NULL, 0, 0);
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,
410                                NULL, NULL, 0, 0);
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,
414                                        -TDB_ERR_NOEXIST);
415                         else
416                                 add_op(&op, i, OP_TDB_FETCH, words[1], words[3],
417                                        0, 0);
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);
422                         else
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,
427                                atoi(words[3]));
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")) {
433                         int flag;
434
435                         if (streq(words[1], "insert"))
436                                 flag = TDB_INSERT;
437                         else if (streq(words[1], "modify"))
438                                 flag = TDB_MODIFY;
439                         else if (streq(words[1], "normal"))
440                                 flag = 0;
441                         else
442                                 errx(1, "Line %u: invalid tdb_store", i+1);
443
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);
450                         else
451                                 add_op(&op, i, OP_TDB_STORE, words[2], words[3],
452                                        flag, 0);
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,
457                                atoi(words[2]));
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,
462                                0, 0);
463                 } else if (streq(words[0], "tdb_transaction_cancel")) {
464                         add_op(&op, i, OP_TDB_TRANSACTION_CANCEL, NULL, NULL,
465                                0, 0);
466                 } else if (streq(words[0], "tdb_transaction_commit")) {
467                         add_op(&op, i, OP_TDB_TRANSACTION_COMMIT, NULL, NULL,
468                                0, 0);
469                 } else if (streq(words[0], "tdb_traverse_read_start")) {
470                         add_op(&op, i, OP_TDB_TRAVERSE_READ_START, NULL, NULL,
471                                0, 0);
472                 } else if (streq(words[0], "tdb_traverse_start")) {
473                         add_op(&op, i, OP_TDB_TRAVERSE_START, NULL, NULL,
474                                0, 0);
475                 } else if (streq(words[0], "tdb_traverse_end")) {
476                         add_op(&op, i, OP_TDB_TRAVERSE_END, NULL, NULL,
477                                0, atoi(words[2]));
478                 } else if (streq(words[0], "traverse")) {
479                         add_op(&op, i, OP_TDB_TRAVERSE, words[1], words[2],
480                                0, 0);
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);
485                         else
486                                 add_op(&op, i, OP_TDB_FIRSTKEY, NULL, words[2],
487                                        0, 0);
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);
492                         else
493                                 add_op(&op, i, OP_TDB_NEXTKEY,
494                                        words[1], words[3], 0, 0);
495                 } else
496                         errx(1, "Line %u: unknown op '%s'", i+1, words[0]);
497         }
498
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?");
504         tdb_close(tdb);
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));
508         exit(0);
509 }