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