1/*-
2 * Copyright (c) 1992, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 4. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#ifndef lint
31static char copyright[] =
32"@(#) Copyright (c) 1992, 1993, 1994\n\
33	The Regents of the University of California.  All rights reserved.\n";
34#endif /* not lint */
35
36#if defined(LIBC_SCCS) && !defined(lint)
37static char sccsid[] = "@(#)dbtest.c	8.17 (Berkeley) 9/1/94";
38#endif /* LIBC_SCCS and not lint */
39#include <sys/cdefs.h>
40__FBSDID("$FreeBSD$");
41
42#include <sys/param.h>
43#include <sys/stat.h>
44
45#include <ctype.h>
46#include <errno.h>
47#include <fcntl.h>
48#include <limits.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <unistd.h>
53
54#include <db.h>
55
56enum S { COMMAND, COMPARE, GET, PUT, REMOVE, SEQ, SEQFLAG, KEY, DATA };
57
58void	 compare(DBT *, DBT *);
59DBTYPE	 dbtype(char *);
60void	 dump(DB *, int);
61void	 err(const char *, ...) __printflike(1, 2);
62void	 get(DB *, DBT *);
63void	 getdata(DB *, DBT *, DBT *);
64void	 put(DB *, DBT *, DBT *);
65void	 rem(DB *, DBT *);
66char	*sflags(int);
67void	 synk(DB *);
68void	*rfile(char *, size_t *);
69void	 seq(DB *, DBT *);
70u_int	 setflags(char *);
71void	*setinfo(DBTYPE, char *);
72void	 usage(void);
73void	*xmalloc(char *, size_t);
74
75DBTYPE type;				/* Database type. */
76void *infop;				/* Iflags. */
77u_long lineno;				/* Current line in test script. */
78u_int flags;				/* Current DB flags. */
79int ofd = STDOUT_FILENO;		/* Standard output fd. */
80
81DB *XXdbp;				/* Global for gdb. */
82int XXlineno;				/* Fast breakpoint for gdb. */
83
84int
85main(argc, argv)
86	int argc;
87	char *argv[];
88{
89	extern int optind;
90	extern char *optarg;
91	enum S command, state;
92	DB *dbp;
93	DBT data, key, keydata;
94	size_t len;
95	int ch, oflags, sflag;
96	char *fname, *infoarg, *p, *t, buf[8 * 1024];
97
98	infoarg = NULL;
99	fname = NULL;
100	oflags = O_CREAT | O_RDWR;
101	sflag = 0;
102	while ((ch = getopt(argc, argv, "f:i:lo:s")) != -1)
103		switch (ch) {
104		case 'f':
105			fname = optarg;
106			break;
107		case 'i':
108			infoarg = optarg;
109			break;
110		case 'l':
111			oflags |= DB_LOCK;
112			break;
113		case 'o':
114			if ((ofd = open(optarg,
115			    O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
116				err("%s: %s", optarg, strerror(errno));
117			break;
118		case 's':
119			sflag = 1;
120			break;
121		case '?':
122		default:
123			usage();
124		}
125	argc -= optind;
126	argv += optind;
127
128	if (argc != 2)
129		usage();
130
131	/* Set the type. */
132	type = dbtype(*argv++);
133
134	/* Open the descriptor file. */
135        if (strcmp(*argv, "-") && freopen(*argv, "r", stdin) == NULL)
136	    err("%s: %s", *argv, strerror(errno));
137
138	/* Set up the db structure as necessary. */
139	if (infoarg == NULL)
140		infop = NULL;
141	else
142		for (p = strtok(infoarg, ",\t "); p != NULL;
143		    p = strtok(0, ",\t "))
144			if (*p != '\0')
145				infop = setinfo(type, p);
146
147	/*
148	 * Open the DB.  Delete any preexisting copy, you almost never
149	 * want it around, and it often screws up tests.
150	 */
151	if (fname == NULL) {
152		p = getenv("TMPDIR");
153		if (p == NULL)
154			p = "/var/tmp";
155		(void)snprintf(buf, sizeof(buf), "%s/__dbtest", p);
156		fname = buf;
157		(void)unlink(buf);
158	} else  if (!sflag)
159		(void)unlink(fname);
160
161	if ((dbp = dbopen(fname,
162	    oflags, S_IRUSR | S_IWUSR, type, infop)) == NULL)
163		err("dbopen: %s", strerror(errno));
164	XXdbp = dbp;
165
166	state = COMMAND;
167	for (lineno = 1;
168	    (p = fgets(buf, sizeof(buf), stdin)) != NULL; ++lineno) {
169		/* Delete the newline, displaying the key/data is easier. */
170		if (ofd == STDOUT_FILENO && (t = strchr(p, '\n')) != NULL)
171			*t = '\0';
172		if ((len = strlen(buf)) == 0 || isspace(*p) || *p == '#')
173			continue;
174
175		/* Convenient gdb break point. */
176		if (XXlineno == lineno)
177			XXlineno = 1;
178		switch (*p) {
179		case 'c':			/* compare */
180			if (state != COMMAND)
181				err("line %lu: not expecting command", lineno);
182			state = KEY;
183			command = COMPARE;
184			break;
185		case 'e':			/* echo */
186			if (state != COMMAND)
187				err("line %lu: not expecting command", lineno);
188			/* Don't display the newline, if CR at EOL. */
189			if (p[len - 2] == '\r')
190				--len;
191			if (write(ofd, p + 1, len - 1) != len - 1 ||
192			    write(ofd, "\n", 1) != 1)
193				err("write: %s", strerror(errno));
194			break;
195		case 'g':			/* get */
196			if (state != COMMAND)
197				err("line %lu: not expecting command", lineno);
198			state = KEY;
199			command = GET;
200			break;
201		case 'p':			/* put */
202			if (state != COMMAND)
203				err("line %lu: not expecting command", lineno);
204			state = KEY;
205			command = PUT;
206			break;
207		case 'r':			/* remove */
208			if (state != COMMAND)
209				err("line %lu: not expecting command", lineno);
210                        if (flags == R_CURSOR) {
211				rem(dbp, &key);
212				state = COMMAND;
213                        } else {
214				state = KEY;
215				command = REMOVE;
216			}
217			break;
218		case 'S':			/* sync */
219			if (state != COMMAND)
220				err("line %lu: not expecting command", lineno);
221			synk(dbp);
222			state = COMMAND;
223			break;
224		case 's':			/* seq */
225			if (state != COMMAND)
226				err("line %lu: not expecting command", lineno);
227			if (flags == R_CURSOR) {
228				state = KEY;
229				command = SEQ;
230			} else
231				seq(dbp, &key);
232			break;
233		case 'f':
234			flags = setflags(p + 1);
235			break;
236		case 'D':			/* data file */
237			if (state != DATA)
238				err("line %lu: not expecting data", lineno);
239			data.data = rfile(p + 1, &data.size);
240			goto ldata;
241		case 'd':			/* data */
242			if (state != DATA)
243				err("line %lu: not expecting data", lineno);
244			data.data = xmalloc(p + 1, len - 1);
245			data.size = len - 1;
246ldata:			switch (command) {
247			case COMPARE:
248				compare(&keydata, &data);
249				break;
250			case PUT:
251				put(dbp, &key, &data);
252				break;
253			default:
254				err("line %lu: command doesn't take data",
255				    lineno);
256			}
257			if (type != DB_RECNO)
258				free(key.data);
259			free(data.data);
260			state = COMMAND;
261			break;
262		case 'K':			/* key file */
263			if (state != KEY)
264				err("line %lu: not expecting a key", lineno);
265			if (type == DB_RECNO)
266				err("line %lu: 'K' not available for recno",
267				    lineno);
268			key.data = rfile(p + 1, &key.size);
269			goto lkey;
270		case 'k':			/* key */
271			if (state != KEY)
272				err("line %lu: not expecting a key", lineno);
273			if (type == DB_RECNO) {
274				static recno_t recno;
275				recno = atoi(p + 1);
276				key.data = &recno;
277				key.size = sizeof(recno);
278			} else {
279				key.data = xmalloc(p + 1, len - 1);
280				key.size = len - 1;
281			}
282lkey:			switch (command) {
283			case COMPARE:
284				getdata(dbp, &key, &keydata);
285				state = DATA;
286				break;
287			case GET:
288				get(dbp, &key);
289				if (type != DB_RECNO)
290					free(key.data);
291				state = COMMAND;
292				break;
293			case PUT:
294				state = DATA;
295				break;
296			case REMOVE:
297				rem(dbp, &key);
298				if ((type != DB_RECNO) && (flags != R_CURSOR))
299					free(key.data);
300				state = COMMAND;
301				break;
302			case SEQ:
303				seq(dbp, &key);
304				if ((type != DB_RECNO) && (flags != R_CURSOR))
305					free(key.data);
306				state = COMMAND;
307				break;
308			default:
309				err("line %lu: command doesn't take a key",
310				    lineno);
311			}
312			break;
313		case 'o':
314			dump(dbp, p[1] == 'r');
315			break;
316		default:
317			err("line %lu: %s: unknown command character",
318			    lineno, p);
319		}
320	}
321#ifdef STATISTICS
322	/*
323	 * -l must be used (DB_LOCK must be set) for this to be
324	 * used, otherwise a page will be locked and it will fail.
325	 */
326	if (type == DB_BTREE && oflags & DB_LOCK)
327		__bt_stat(dbp);
328#endif
329	if (dbp->close(dbp))
330		err("db->close: %s", strerror(errno));
331	(void)close(ofd);
332	exit(0);
333}
334
335#define	NOOVERWRITE	"put failed, would overwrite key\n"
336
337void
338compare(db1, db2)
339	DBT *db1, *db2;
340{
341	size_t len;
342	u_char *p1, *p2;
343
344	if (db1->size != db2->size)
345		printf("compare failed: key->data len %lu != data len %lu\n",
346		    db1->size, db2->size);
347
348	len = MIN(db1->size, db2->size);
349	for (p1 = db1->data, p2 = db2->data; len--;)
350		if (*p1++ != *p2++) {
351			printf("compare failed at offset %d\n",
352			    p1 - (u_char *)db1->data);
353			break;
354		}
355}
356
357void
358get(dbp, kp)
359	DB *dbp;
360	DBT *kp;
361{
362	DBT data;
363
364	switch (dbp->get(dbp, kp, &data, flags)) {
365	case 0:
366		(void)write(ofd, data.data, data.size);
367		if (ofd == STDOUT_FILENO)
368			(void)write(ofd, "\n", 1);
369		break;
370	case -1:
371		err("line %lu: get: %s", lineno, strerror(errno));
372		/* NOTREACHED */
373	case 1:
374#define	NOSUCHKEY	"get failed, no such key\n"
375		if (ofd != STDOUT_FILENO)
376			(void)write(ofd, NOSUCHKEY, sizeof(NOSUCHKEY) - 1);
377		else
378			(void)fprintf(stderr, "%d: %.*s: %s",
379			    lineno, MIN(kp->size, 20), kp->data, NOSUCHKEY);
380#undef	NOSUCHKEY
381		break;
382	}
383}
384
385void
386getdata(dbp, kp, dp)
387	DB *dbp;
388	DBT *kp, *dp;
389{
390	switch (dbp->get(dbp, kp, dp, flags)) {
391	case 0:
392		return;
393	case -1:
394		err("line %lu: getdata: %s", lineno, strerror(errno));
395		/* NOTREACHED */
396	case 1:
397		err("line %lu: getdata failed, no such key", lineno);
398		/* NOTREACHED */
399	}
400}
401
402void
403put(dbp, kp, dp)
404	DB *dbp;
405	DBT *kp, *dp;
406{
407	switch (dbp->put(dbp, kp, dp, flags)) {
408	case 0:
409		break;
410	case -1:
411		err("line %lu: put: %s", lineno, strerror(errno));
412		/* NOTREACHED */
413	case 1:
414		(void)write(ofd, NOOVERWRITE, sizeof(NOOVERWRITE) - 1);
415		break;
416	}
417}
418
419void
420rem(dbp, kp)
421	DB *dbp;
422	DBT *kp;
423{
424	switch (dbp->del(dbp, kp, flags)) {
425	case 0:
426		break;
427	case -1:
428		err("line %lu: rem: %s", lineno, strerror(errno));
429		/* NOTREACHED */
430	case 1:
431#define	NOSUCHKEY	"rem failed, no such key\n"
432		if (ofd != STDOUT_FILENO)
433			(void)write(ofd, NOSUCHKEY, sizeof(NOSUCHKEY) - 1);
434		else if (flags != R_CURSOR)
435			(void)fprintf(stderr, "%d: %.*s: %s",
436			    lineno, MIN(kp->size, 20), kp->data, NOSUCHKEY);
437		else
438			(void)fprintf(stderr,
439			    "%d: rem of cursor failed\n", lineno);
440#undef	NOSUCHKEY
441		break;
442	}
443}
444
445void
446synk(dbp)
447	DB *dbp;
448{
449	switch (dbp->sync(dbp, flags)) {
450	case 0:
451		break;
452	case -1:
453		err("line %lu: synk: %s", lineno, strerror(errno));
454		/* NOTREACHED */
455	}
456}
457
458void
459seq(dbp, kp)
460	DB *dbp;
461	DBT *kp;
462{
463	DBT data;
464
465	switch (dbp->seq(dbp, kp, &data, flags)) {
466	case 0:
467		(void)write(ofd, data.data, data.size);
468		if (ofd == STDOUT_FILENO)
469			(void)write(ofd, "\n", 1);
470		break;
471	case -1:
472		err("line %lu: seq: %s", lineno, strerror(errno));
473		/* NOTREACHED */
474	case 1:
475#define	NOSUCHKEY	"seq failed, no such key\n"
476		if (ofd != STDOUT_FILENO)
477			(void)write(ofd, NOSUCHKEY, sizeof(NOSUCHKEY) - 1);
478		else if (flags == R_CURSOR)
479			(void)fprintf(stderr, "%d: %.*s: %s",
480			    lineno, MIN(kp->size, 20), kp->data, NOSUCHKEY);
481		else
482			(void)fprintf(stderr,
483			    "%d: seq (%s) failed\n", lineno, sflags(flags));
484#undef	NOSUCHKEY
485		break;
486	}
487}
488
489void
490dump(dbp, rev)
491	DB *dbp;
492	int rev;
493{
494	DBT key, data;
495	int flags, nflags;
496
497	if (rev) {
498		flags = R_LAST;
499		nflags = R_PREV;
500	} else {
501		flags = R_FIRST;
502		nflags = R_NEXT;
503	}
504	for (;; flags = nflags)
505		switch (dbp->seq(dbp, &key, &data, flags)) {
506		case 0:
507			(void)write(ofd, data.data, data.size);
508			if (ofd == STDOUT_FILENO)
509				(void)write(ofd, "\n", 1);
510			break;
511		case 1:
512			goto done;
513		case -1:
514			err("line %lu: (dump) seq: %s",
515			    lineno, strerror(errno));
516			/* NOTREACHED */
517		}
518done:	return;
519}
520
521u_int
522setflags(s)
523	char *s;
524{
525	char *p, *index();
526
527	for (; isspace(*s); ++s);
528	if (*s == '\n' || *s == '\0')
529		return (0);
530	if ((p = index(s, '\n')) != NULL)
531		*p = '\0';
532	if (!strcmp(s, "R_CURSOR"))		return (R_CURSOR);
533	if (!strcmp(s, "R_FIRST"))		return (R_FIRST);
534	if (!strcmp(s, "R_IAFTER")) 		return (R_IAFTER);
535	if (!strcmp(s, "R_IBEFORE")) 		return (R_IBEFORE);
536	if (!strcmp(s, "R_LAST")) 		return (R_LAST);
537	if (!strcmp(s, "R_NEXT")) 		return (R_NEXT);
538	if (!strcmp(s, "R_NOOVERWRITE"))	return (R_NOOVERWRITE);
539	if (!strcmp(s, "R_PREV"))		return (R_PREV);
540	if (!strcmp(s, "R_SETCURSOR"))		return (R_SETCURSOR);
541
542	err("line %lu: %s: unknown flag", lineno, s);
543	/* NOTREACHED */
544}
545
546char *
547sflags(flags)
548	int flags;
549{
550	switch (flags) {
551	case R_CURSOR:		return ("R_CURSOR");
552	case R_FIRST:		return ("R_FIRST");
553	case R_IAFTER:		return ("R_IAFTER");
554	case R_IBEFORE:		return ("R_IBEFORE");
555	case R_LAST:		return ("R_LAST");
556	case R_NEXT:		return ("R_NEXT");
557	case R_NOOVERWRITE:	return ("R_NOOVERWRITE");
558	case R_PREV:		return ("R_PREV");
559	case R_SETCURSOR:	return ("R_SETCURSOR");
560	}
561
562	return ("UNKNOWN!");
563}
564
565DBTYPE
566dbtype(s)
567	char *s;
568{
569	if (!strcmp(s, "btree"))
570		return (DB_BTREE);
571	if (!strcmp(s, "hash"))
572		return (DB_HASH);
573	if (!strcmp(s, "recno"))
574		return (DB_RECNO);
575	err("%s: unknown type (use btree, hash or recno)", s);
576	/* NOTREACHED */
577}
578
579void *
580setinfo(type, s)
581	DBTYPE type;
582	char *s;
583{
584	static BTREEINFO ib;
585	static HASHINFO ih;
586	static RECNOINFO rh;
587	char *eq, *index();
588
589	if ((eq = index(s, '=')) == NULL)
590		err("%s: illegal structure set statement", s);
591	*eq++ = '\0';
592	if (!isdigit(*eq))
593		err("%s: structure set statement must be a number", s);
594
595	switch (type) {
596	case DB_BTREE:
597		if (!strcmp("flags", s)) {
598			ib.flags = atoi(eq);
599			return (&ib);
600		}
601		if (!strcmp("cachesize", s)) {
602			ib.cachesize = atoi(eq);
603			return (&ib);
604		}
605		if (!strcmp("maxkeypage", s)) {
606			ib.maxkeypage = atoi(eq);
607			return (&ib);
608		}
609		if (!strcmp("minkeypage", s)) {
610			ib.minkeypage = atoi(eq);
611			return (&ib);
612		}
613		if (!strcmp("lorder", s)) {
614			ib.lorder = atoi(eq);
615			return (&ib);
616		}
617		if (!strcmp("psize", s)) {
618			ib.psize = atoi(eq);
619			return (&ib);
620		}
621		break;
622	case DB_HASH:
623		if (!strcmp("bsize", s)) {
624			ih.bsize = atoi(eq);
625			return (&ih);
626		}
627		if (!strcmp("ffactor", s)) {
628			ih.ffactor = atoi(eq);
629			return (&ih);
630		}
631		if (!strcmp("nelem", s)) {
632			ih.nelem = atoi(eq);
633			return (&ih);
634		}
635		if (!strcmp("cachesize", s)) {
636			ih.cachesize = atoi(eq);
637			return (&ih);
638		}
639		if (!strcmp("lorder", s)) {
640			ih.lorder = atoi(eq);
641			return (&ih);
642		}
643		break;
644	case DB_RECNO:
645		if (!strcmp("flags", s)) {
646			rh.flags = atoi(eq);
647			return (&rh);
648		}
649		if (!strcmp("cachesize", s)) {
650			rh.cachesize = atoi(eq);
651			return (&rh);
652		}
653		if (!strcmp("lorder", s)) {
654			rh.lorder = atoi(eq);
655			return (&rh);
656		}
657		if (!strcmp("reclen", s)) {
658			rh.reclen = atoi(eq);
659			return (&rh);
660		}
661		if (!strcmp("bval", s)) {
662			rh.bval = atoi(eq);
663			return (&rh);
664		}
665		if (!strcmp("psize", s)) {
666			rh.psize = atoi(eq);
667			return (&rh);
668		}
669		break;
670	}
671	err("%s: unknown structure value", s);
672	/* NOTREACHED */
673}
674
675void *
676rfile(name, lenp)
677	char *name;
678	size_t *lenp;
679{
680	struct stat sb;
681	void *p;
682	int fd;
683	char *np, *index();
684
685	for (; isspace(*name); ++name);
686	if ((np = index(name, '\n')) != NULL)
687		*np = '\0';
688	if ((fd = open(name, O_RDONLY, 0)) < 0 ||
689	    fstat(fd, &sb))
690		err("%s: %s\n", name, strerror(errno));
691#ifdef NOT_PORTABLE
692	if (sb.st_size > (off_t)SIZE_T_MAX)
693		err("%s: %s\n", name, strerror(E2BIG));
694#endif
695	if ((p = (void *)malloc((u_int)sb.st_size)) == NULL)
696		err("%s", strerror(errno));
697	(void)read(fd, p, (int)sb.st_size);
698	*lenp = sb.st_size;
699	(void)close(fd);
700	return (p);
701}
702
703void *
704xmalloc(text, len)
705	char *text;
706	size_t len;
707{
708	void *p;
709
710	if ((p = (void *)malloc(len)) == NULL)
711		err("%s", strerror(errno));
712	memmove(p, text, len);
713	return (p);
714}
715
716void
717usage()
718{
719	(void)fprintf(stderr,
720	    "usage: dbtest [-l] [-f file] [-i info] [-o file] type script\n");
721	exit(1);
722}
723
724#include <stdarg.h>
725
726void
727err(const char *fmt, ...)
728{
729	va_list ap;
730
731	va_start(ap, fmt);
732	(void)fprintf(stderr, "dbtest: ");
733	(void)vfprintf(stderr, fmt, ap);
734	va_end(ap);
735	(void)fprintf(stderr, "\n");
736	exit(1);
737	/* NOTREACHED */
738}
739