1/* this tests tdb by doing lots of ops from several simultaneous
2   writers - that stresses the locking code.
3*/
4
5#include "replace.h"
6#include "tdb.h"
7#include "system/time.h"
8#include "system/wait.h"
9#include "system/filesys.h"
10
11#ifdef HAVE_GETOPT_H
12#include <getopt.h>
13#endif
14
15
16#define REOPEN_PROB 30
17#define DELETE_PROB 8
18#define STORE_PROB 4
19#define APPEND_PROB 6
20#define TRANSACTION_PROB 10
21#define LOCKSTORE_PROB 5
22#define TRAVERSE_PROB 20
23#define TRAVERSE_READ_PROB 20
24#define CULL_PROB 100
25#define KEYLEN 3
26#define DATALEN 100
27
28static struct tdb_context *db;
29static int in_transaction;
30static int error_count;
31
32#ifdef PRINTF_ATTRIBUTE
33static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
34#endif
35static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...)
36{
37	va_list ap;
38
39	error_count++;
40
41	va_start(ap, format);
42	vfprintf(stdout, format, ap);
43	va_end(ap);
44	fflush(stdout);
45#if 0
46	{
47		char *ptr;
48		asprintf(&ptr,"xterm -e gdb /proc/%d/exe %d", getpid(), getpid());
49		system(ptr);
50		free(ptr);
51	}
52#endif
53}
54
55static void fatal(const char *why)
56{
57	perror(why);
58	error_count++;
59}
60
61static char *randbuf(int len)
62{
63	char *buf;
64	int i;
65	buf = (char *)malloc(len+1);
66
67	for (i=0;i<len;i++) {
68		buf[i] = 'a' + (rand() % 26);
69	}
70	buf[i] = 0;
71	return buf;
72}
73
74static int cull_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
75			 void *state)
76{
77#if CULL_PROB
78	if (random() % CULL_PROB == 0) {
79		tdb_delete(tdb, key);
80	}
81#endif
82	return 0;
83}
84
85static void addrec_db(void)
86{
87	int klen, dlen;
88	char *k, *d;
89	TDB_DATA key, data;
90
91	klen = 1 + (rand() % KEYLEN);
92	dlen = 1 + (rand() % DATALEN);
93
94	k = randbuf(klen);
95	d = randbuf(dlen);
96
97	key.dptr = (unsigned char *)k;
98	key.dsize = klen+1;
99
100	data.dptr = (unsigned char *)d;
101	data.dsize = dlen+1;
102
103#if TRANSACTION_PROB
104	if (in_transaction == 0 && random() % TRANSACTION_PROB == 0) {
105		if (tdb_transaction_start(db) != 0) {
106			fatal("tdb_transaction_start failed");
107		}
108		in_transaction++;
109		goto next;
110	}
111	if (in_transaction && random() % TRANSACTION_PROB == 0) {
112		if (tdb_transaction_commit(db) != 0) {
113			fatal("tdb_transaction_commit failed");
114		}
115		in_transaction--;
116		goto next;
117	}
118	if (in_transaction && random() % TRANSACTION_PROB == 0) {
119		if (tdb_transaction_cancel(db) != 0) {
120			fatal("tdb_transaction_cancel failed");
121		}
122		in_transaction--;
123		goto next;
124	}
125#endif
126
127#if REOPEN_PROB
128	if (in_transaction == 0 && random() % REOPEN_PROB == 0) {
129		tdb_reopen_all(0);
130		goto next;
131	}
132#endif
133
134#if DELETE_PROB
135	if (random() % DELETE_PROB == 0) {
136		tdb_delete(db, key);
137		goto next;
138	}
139#endif
140
141#if STORE_PROB
142	if (random() % STORE_PROB == 0) {
143		if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
144			fatal("tdb_store failed");
145		}
146		goto next;
147	}
148#endif
149
150#if APPEND_PROB
151	if (random() % APPEND_PROB == 0) {
152		if (tdb_append(db, key, data) != 0) {
153			fatal("tdb_append failed");
154		}
155		goto next;
156	}
157#endif
158
159#if LOCKSTORE_PROB
160	if (random() % LOCKSTORE_PROB == 0) {
161		tdb_chainlock(db, key);
162		data = tdb_fetch(db, key);
163		if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
164			fatal("tdb_store failed");
165		}
166		if (data.dptr) free(data.dptr);
167		tdb_chainunlock(db, key);
168		goto next;
169	}
170#endif
171
172#if TRAVERSE_PROB
173	if (random() % TRAVERSE_PROB == 0) {
174		tdb_traverse(db, cull_traverse, NULL);
175		goto next;
176	}
177#endif
178
179#if TRAVERSE_READ_PROB
180	if (random() % TRAVERSE_READ_PROB == 0) {
181		tdb_traverse_read(db, NULL, NULL);
182		goto next;
183	}
184#endif
185
186	data = tdb_fetch(db, key);
187	if (data.dptr) free(data.dptr);
188
189next:
190	free(k);
191	free(d);
192}
193
194static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
195                       void *state)
196{
197	tdb_delete(tdb, key);
198	return 0;
199}
200
201static void usage(void)
202{
203	printf("Usage: tdbtorture [-n NUM_PROCS] [-l NUM_LOOPS] [-s SEED] [-H HASH_SIZE]\n");
204	exit(0);
205}
206
207 int main(int argc, char * const *argv)
208{
209	int i, seed = -1;
210	int num_procs = 3;
211	int num_loops = 5000;
212	int hash_size = 2;
213	int c;
214	extern char *optarg;
215	pid_t *pids;
216
217	struct tdb_logging_context log_ctx;
218	log_ctx.log_fn = tdb_log;
219
220	while ((c = getopt(argc, argv, "n:l:s:H:h")) != -1) {
221		switch (c) {
222		case 'n':
223			num_procs = strtol(optarg, NULL, 0);
224			break;
225		case 'l':
226			num_loops = strtol(optarg, NULL, 0);
227			break;
228		case 'H':
229			hash_size = strtol(optarg, NULL, 0);
230			break;
231		case 's':
232			seed = strtol(optarg, NULL, 0);
233			break;
234		default:
235			usage();
236		}
237	}
238
239	unlink("torture.tdb");
240
241	pids = calloc(sizeof(pid_t), num_procs);
242	pids[0] = getpid();
243
244	for (i=0;i<num_procs-1;i++) {
245		if ((pids[i+1]=fork()) == 0) break;
246	}
247
248	db = tdb_open_ex("torture.tdb", hash_size, TDB_CLEAR_IF_FIRST,
249			 O_RDWR | O_CREAT, 0600, &log_ctx, NULL);
250	if (!db) {
251		fatal("db open failed");
252	}
253
254	if (seed == -1) {
255		seed = (getpid() + time(NULL)) & 0x7FFFFFFF;
256	}
257
258	if (i == 0) {
259		printf("testing with %d processes, %d loops, %d hash_size, seed=%d\n",
260		       num_procs, num_loops, hash_size, seed);
261	}
262
263	srand(seed + i);
264	srandom(seed + i);
265
266	for (i=0;i<num_loops && error_count == 0;i++) {
267		addrec_db();
268	}
269
270	if (error_count == 0) {
271		tdb_traverse_read(db, NULL, NULL);
272		tdb_traverse(db, traverse_fn, NULL);
273		tdb_traverse(db, traverse_fn, NULL);
274	}
275
276	tdb_close(db);
277
278	if (getpid() != pids[0]) {
279		return error_count;
280	}
281
282	for (i=1;i<num_procs;i++) {
283		int status, j;
284		pid_t pid;
285		if (error_count != 0) {
286			/* try and stop the test on any failure */
287			for (j=1;j<num_procs;j++) {
288				if (pids[j] != 0) {
289					kill(pids[j], SIGTERM);
290				}
291			}
292		}
293		pid = waitpid(-1, &status, 0);
294		if (pid == -1) {
295			perror("failed to wait for child\n");
296			exit(1);
297		}
298		for (j=1;j<num_procs;j++) {
299			if (pids[j] == pid) break;
300		}
301		if (j == num_procs) {
302			printf("unknown child %d exited!?\n", (int)pid);
303			exit(1);
304		}
305		if (WEXITSTATUS(status) != 0) {
306			printf("child %d exited with status %d\n",
307			       (int)pid, WEXITSTATUS(status));
308			error_count++;
309		}
310		pids[j] = 0;
311	}
312
313	if (error_count == 0) {
314		printf("OK\n");
315	}
316
317	return error_count;
318}
319