eb12b59c4e7f4c366c96d68e76e1c7748970c1ee
[ddiss/samba.git] / lib / tdb2 / tools / tdb2torture.c
1 /* this tests tdb by doing lots of ops from several simultaneous
2    writers - that stresses the locking code.
3 */
4
5 #include "config.h"
6 #include "tdb2.h"
7 #include <ccan/err/err.h>
8 #ifdef HAVE_LIBREPLACE
9 #include <replace.h>
10 #else
11 #include <stdlib.h>
12 #include <getopt.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <unistd.h>
18 #include <sys/types.h>
19 #include <fcntl.h>
20 #include <time.h>
21 #include <sys/wait.h>
22 #endif
23
24 /* Currently we default to creating a tdb1.  This will change! */
25 #define TDB2_IS_DEFAULT false
26
27 //#define REOPEN_PROB 30
28 #define DELETE_PROB 8
29 #define STORE_PROB 4
30 #define APPEND_PROB 6
31 #define TRANSACTION_PROB 10
32 #define TRANSACTION_PREPARE_PROB 2
33 #define LOCKSTORE_PROB 5
34 #define TRAVERSE_PROB 20
35 #define TRAVERSE_MOD_PROB 100
36 #define TRAVERSE_ABORT_PROB 500
37 #define CULL_PROB 100
38 #define KEYLEN 3
39 #define DATALEN 100
40
41 static struct tdb_context *db;
42 static int in_transaction;
43 static int in_traverse;
44 static int error_count;
45 #if TRANSACTION_PROB
46 static int always_transaction = 0;
47 #endif
48 static int loopnum;
49 static int count_pipe;
50 static union tdb_attribute log_attr;
51 static union tdb_attribute seed_attr;
52
53 static void tdb_log(struct tdb_context *tdb,
54                     enum tdb_log_level level,
55                     enum TDB_ERROR ecode,
56                     const char *message,
57                     void *data)
58 {
59         printf("tdb:%s:%s:%s\n",
60                tdb_name(tdb), tdb_errorstr(ecode), message);
61         fflush(stdout);
62 #if 0
63         {
64                 char str[200];
65                 signal(SIGUSR1, SIG_IGN);
66                 sprintf(str,"xterm -e gdb /proc/%d/exe %d", getpid(), getpid());
67                 system(str);
68         }
69 #endif
70 }
71
72 #include "../private.h"
73
74 static void segv_handler(int sig, siginfo_t *info, void *p)
75 {
76         char string[100];
77
78         sprintf(string, "%u: death at %p (map_ptr %p, map_size %zu)\n",
79                 getpid(), info->si_addr, db->file->map_ptr,
80                 (size_t)db->file->map_size);
81         if (write(2, string, strlen(string)) > 0)
82                 sleep(60);
83         _exit(11);
84 }
85
86 static void fatal(struct tdb_context *tdb, const char *why)
87 {
88         fprintf(stderr, "%u:%s:%s\n", getpid(), why,
89                 tdb ? tdb_errorstr(tdb_error(tdb)) : "(no tdb)");
90         error_count++;
91 }
92
93 static char *randbuf(int len)
94 {
95         char *buf;
96         int i;
97         buf = (char *)malloc(len+1);
98
99         for (i=0;i<len;i++) {
100                 buf[i] = 'a' + (rand() % 26);
101         }
102         buf[i] = 0;
103         return buf;
104 }
105
106 static void addrec_db(void);
107 static int modify_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
108                            void *state)
109 {
110 #if CULL_PROB
111         if (random() % CULL_PROB == 0) {
112                 tdb_delete(tdb, key);
113         }
114 #endif
115
116 #if TRAVERSE_MOD_PROB
117         if (random() % TRAVERSE_MOD_PROB == 0) {
118                 addrec_db();
119         }
120 #endif
121
122 #if TRAVERSE_ABORT_PROB
123         if (random() % TRAVERSE_ABORT_PROB == 0)
124                 return 1;
125 #endif
126
127         return 0;
128 }
129
130 static void addrec_db(void)
131 {
132         int klen, dlen;
133         char *k, *d;
134         TDB_DATA key, data;
135
136         klen = 1 + (rand() % KEYLEN);
137         dlen = 1 + (rand() % DATALEN);
138
139         k = randbuf(klen);
140         d = randbuf(dlen);
141
142         key.dptr = (unsigned char *)k;
143         key.dsize = klen+1;
144
145         data.dptr = (unsigned char *)d;
146         data.dsize = dlen+1;
147
148 #if REOPEN_PROB
149         if (in_traverse == 0 && in_transaction == 0 && random() % REOPEN_PROB == 0) {
150                 tdb_reopen_all(0);
151                 goto next;
152         }
153 #endif
154
155 #if TRANSACTION_PROB
156         if (in_traverse == 0 && in_transaction == 0 && (always_transaction || random() % TRANSACTION_PROB == 0)) {
157                 if (tdb_transaction_start(db) != 0) {
158                         fatal(db, "tdb_transaction_start failed");
159                 }
160                 in_transaction++;
161                 goto next;
162         }
163         if (in_traverse == 0 && in_transaction && random() % TRANSACTION_PROB == 0) {
164                 if (random() % TRANSACTION_PREPARE_PROB == 0) {
165                         if (tdb_transaction_prepare_commit(db) != 0) {
166                                 fatal(db, "tdb_transaction_prepare_commit failed");
167                         }
168                 }
169                 if (tdb_transaction_commit(db) != 0) {
170                         fatal(db, "tdb_transaction_commit failed");
171                 }
172                 in_transaction--;
173                 goto next;
174         }
175
176         if (in_traverse == 0 && in_transaction && random() % TRANSACTION_PROB == 0) {
177                 tdb_transaction_cancel(db);
178                 in_transaction--;
179                 goto next;
180         }
181 #endif
182
183 #if DELETE_PROB
184         if (random() % DELETE_PROB == 0) {
185                 tdb_delete(db, key);
186                 goto next;
187         }
188 #endif
189
190 #if STORE_PROB
191         if (random() % STORE_PROB == 0) {
192                 if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
193                         fatal(db, "tdb_store failed");
194                 }
195                 goto next;
196         }
197 #endif
198
199 #if APPEND_PROB
200         if (random() % APPEND_PROB == 0) {
201                 if (tdb_append(db, key, data) != 0) {
202                         fatal(db, "tdb_append failed");
203                 }
204                 goto next;
205         }
206 #endif
207
208 #if LOCKSTORE_PROB
209         if (random() % LOCKSTORE_PROB == 0) {
210                 tdb_chainlock(db, key);
211                 if (tdb_fetch(db, key, &data) != TDB_SUCCESS) {
212                         data.dsize = 0;
213                         data.dptr = NULL;
214                 }
215                 if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
216                         fatal(db, "tdb_store failed");
217                 }
218                 if (data.dptr) free(data.dptr);
219                 tdb_chainunlock(db, key);
220                 goto next;
221         }
222 #endif
223
224 #if TRAVERSE_PROB
225         /* FIXME: recursive traverses break transactions? */
226         if (in_traverse == 0 && random() % TRAVERSE_PROB == 0) {
227                 in_traverse++;
228                 tdb_traverse(db, modify_traverse, NULL);
229                 in_traverse--;
230                 goto next;
231         }
232 #endif
233
234         if (tdb_fetch(db, key, &data) == TDB_SUCCESS)
235                 free(data.dptr);
236
237 next:
238         free(k);
239         free(d);
240 }
241
242 static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
243                        void *state)
244 {
245         tdb_delete(tdb, key);
246         return 0;
247 }
248
249 static void usage(void)
250 {
251         printf("Usage: tdbtorture"
252 #if TRANSACTION_PROB
253                " [-t]"
254 #endif
255                " [-k] [-n NUM_PROCS] [-l NUM_LOOPS] [-s SEED] [-S]\n");
256         exit(0);
257 }
258
259 static void send_count_and_suicide(int sig)
260 {
261         /* This ensures our successor can continue where we left off. */
262         if (write(count_pipe, &loopnum, sizeof(loopnum)) != sizeof(loopnum))
263                 exit(2);
264         /* This gives a unique signature. */
265         kill(getpid(), SIGUSR2);
266 }
267
268 static int run_child(const char *filename, int i, int seed, unsigned num_loops,
269                      unsigned start, int tdb_flags)
270 {
271         struct sigaction act = { .sa_sigaction = segv_handler,
272                                  .sa_flags = SA_SIGINFO };
273         sigaction(11, &act, NULL);
274
275         db = tdb_open(filename, tdb_flags, O_RDWR | O_CREAT, 0600,
276                       &log_attr);
277         if (!db) {
278                 fatal(NULL, "db open failed");
279         }
280
281 #if 0
282         if (i == 0) {
283                 printf("pid %i\n", getpid());
284                 sleep(9);
285         } else
286                 sleep(10);
287 #endif
288
289         srand(seed + i);
290         srandom(seed + i);
291
292         /* Set global, then we're ready to handle being killed. */
293         loopnum = start;
294         signal(SIGUSR1, send_count_and_suicide);
295
296         for (;loopnum<num_loops && error_count == 0;loopnum++) {
297                 addrec_db();
298         }
299
300         if (error_count == 0) {
301                 tdb_traverse(db, NULL, NULL);
302 #if TRANSACTION_PROB
303                 if (always_transaction) {
304                         while (in_transaction) {
305                                 tdb_transaction_cancel(db);
306                                 in_transaction--;
307                         }
308                         if (tdb_transaction_start(db) != 0)
309                                 fatal(db, "tdb_transaction_start failed");
310                 }
311 #endif
312                 tdb_traverse(db, traverse_fn, NULL);
313                 tdb_traverse(db, traverse_fn, NULL);
314
315 #if TRANSACTION_PROB
316                 if (always_transaction) {
317                         if (tdb_transaction_commit(db) != 0)
318                                 fatal(db, "tdb_transaction_commit failed");
319                 }
320 #endif
321         }
322
323         tdb_close(db);
324
325         return (error_count < 100 ? error_count : 100);
326 }
327
328 static char *test_path(const char *filename)
329 {
330         const char *prefix = getenv("TEST_DATA_PREFIX");
331
332         if (prefix) {
333                 char *path = NULL;
334                 int ret;
335
336                 ret = asprintf(&path, "%s/%s", prefix, filename);
337                 if (ret == -1) {
338                         return NULL;
339                 }
340                 return path;
341         }
342
343         return strdup(filename);
344 }
345
346 int main(int argc, char * const *argv)
347 {
348         int i, seed = -1;
349         int num_loops = 5000;
350         int num_procs = 3;
351         int c, pfds[2];
352         extern char *optarg;
353         pid_t *pids;
354         int kill_random = 0;
355         int *done;
356         int tdb_flags = TDB_DEFAULT;
357         bool tdb2 = TDB2_IS_DEFAULT;
358         char *test_tdb;
359
360         log_attr.base.attr = TDB_ATTRIBUTE_LOG;
361         log_attr.base.next = &seed_attr;
362         log_attr.log.fn = tdb_log;
363         seed_attr.base.attr = TDB_ATTRIBUTE_SEED;
364         seed_attr.base.next = NULL;
365
366         while ((c = getopt(argc, argv, "n:l:s:thkS12")) != -1) {
367                 switch (c) {
368                 case 'n':
369                         num_procs = strtol(optarg, NULL, 0);
370                         break;
371                 case 'l':
372                         num_loops = strtol(optarg, NULL, 0);
373                         break;
374                 case 's':
375                         seed = strtol(optarg, NULL, 0);
376                         break;
377                 case 'S':
378                         tdb_flags = TDB_NOSYNC;
379                         break;
380                 case 't':
381 #if TRANSACTION_PROB
382                         always_transaction = 1;
383 #else
384                         fprintf(stderr, "Transactions not supported\n");
385                         usage();
386 #endif
387                         break;
388                 case 'k':
389                         kill_random = 1;
390                         break;
391                 case '1':
392                         tdb2 = false;
393                         break;
394                 case '2':
395                         tdb2 = true;
396                         break;
397                 default:
398                         usage();
399                 }
400         }
401
402         if (!tdb2) {
403                 tdb_flags |= TDB_VERSION1;
404                 /* TDB1 tdbs don't use seed. */
405                 log_attr.base.next = NULL;
406         }
407
408         test_tdb = test_path("torture.tdb");
409
410         unlink(test_tdb);
411
412         if (seed == -1) {
413                 seed = (getpid() + time(NULL)) & 0x7FFFFFFF;
414         }
415         seed_attr.seed.seed = (((uint64_t)seed) << 32) | seed;
416
417         if (num_procs == 1 && !kill_random) {
418                 /* Don't fork for this case, makes debugging easier. */
419                 error_count = run_child(test_tdb, 0, seed, num_loops, 0,
420                                         tdb_flags);
421                 goto done;
422         }
423
424         pids = (pid_t *)calloc(sizeof(pid_t), num_procs);
425         done = (int *)calloc(sizeof(int), num_procs);
426
427         if (pipe(pfds) != 0) {
428                 perror("Creating pipe");
429                 exit(1);
430         }
431         count_pipe = pfds[1];
432
433         for (i=0;i<num_procs;i++) {
434                 if ((pids[i]=fork()) == 0) {
435                         close(pfds[0]);
436                         if (i == 0) {
437                                 printf("testing with %d processes, %d loops, seed=%d%s\n",
438                                        num_procs, num_loops, seed,
439 #if TRANSACTION_PROB
440                                        always_transaction ? " (all within transactions)" : ""
441 #else
442                                        ""
443 #endif
444                                         );
445                         }
446                         exit(run_child(test_tdb, i, seed, num_loops, 0,
447                                        tdb_flags));
448                 }
449         }
450
451         while (num_procs) {
452                 int status, j;
453                 pid_t pid;
454
455                 if (error_count != 0) {
456                         /* try and stop the test on any failure */
457                         for (j=0;j<num_procs;j++) {
458                                 if (pids[j] != 0) {
459                                         kill(pids[j], SIGTERM);
460                                 }
461                         }
462                 }
463
464                 pid = waitpid(-1, &status, kill_random ? WNOHANG : 0);
465                 if (pid == 0) {
466                         struct timespec ts;
467
468                         /* Sleep for 1/10 second. */
469                         ts.tv_sec = 0;
470                         ts.tv_nsec = 100000000;
471                         nanosleep(&ts, NULL);
472
473                         /* Kill someone. */
474                         kill(pids[random() % num_procs], SIGUSR1);
475                         continue;
476                 }
477
478                 if (pid == -1) {
479                         perror("failed to wait for child\n");
480                         exit(1);
481                 }
482
483                 for (j=0;j<num_procs;j++) {
484                         if (pids[j] == pid) break;
485                 }
486                 if (j == num_procs) {
487                         printf("unknown child %d exited!?\n", (int)pid);
488                         exit(1);
489                 }
490                 if (WIFSIGNALED(status)) {
491                         if (WTERMSIG(status) == SIGUSR2
492                             || WTERMSIG(status) == SIGUSR1) {
493                                 /* SIGUSR2 means they wrote to pipe. */
494                                 if (WTERMSIG(status) == SIGUSR2) {
495                                         if (read(pfds[0], &done[j],
496                                                  sizeof(done[j]))
497                                             != sizeof(done[j]))
498                                                 err(1,
499                                                     "Short read from child?");
500                                 }
501                                 pids[j] = fork();
502                                 if (pids[j] == 0)
503                                         exit(run_child(test_tdb, j, seed,
504                                                        num_loops, done[j],
505                                                        tdb_flags));
506                                 printf("Restarting child %i for %u-%u\n",
507                                        j, done[j], num_loops);
508                                 continue;
509                         }
510                         printf("child %d exited with signal %d\n",
511                                (int)pid, WTERMSIG(status));
512                         error_count++;
513                 } else {
514                         if (WEXITSTATUS(status) != 0) {
515                                 printf("child %d exited with status %d\n",
516                                        (int)pid, WEXITSTATUS(status));
517                                 error_count++;
518                         }
519                 }
520                 memmove(&pids[j], &pids[j+1],
521                         (num_procs - j - 1)*sizeof(pids[0]));
522                 num_procs--;
523         }
524
525         free(pids);
526
527 done:
528         if (error_count == 0) {
529                 db = tdb_open(test_tdb, TDB_DEFAULT, O_RDWR | O_CREAT,
530                               0600, &log_attr);
531                 if (!db) {
532                         fatal(db, "db open failed");
533                         exit(1);
534                 }
535                 if (tdb_check(db, NULL, NULL) != 0) {
536                         fatal(db, "db check failed");
537                         exit(1);
538                 }
539                 tdb_close(db);
540                 printf("OK\n");
541         }
542
543         free(test_tdb);
544         return error_count;
545 }