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