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 40 static 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 61 enum S { COMMAND, COMPARE, GET, PUT, REMOVE, SEQ, SEQFLAG, KEY, DATA }; 62 63 static void compare(DBT *, DBT *); 64 static DBTYPE dbtype(const char *); 65 static void dump(DB *, int); 66 static void get(DB *, DBT *); 67 static void getdata(DB *, DBT *, DBT *); 68 static void put(DB *, DBT *, DBT *); 69 static void rem(DB *, DBT *); 70 static const char *sflags(int); 71 static void synk(DB *); 72 static void *rfile(char *, size_t *); 73 static void seq(DB *, DBT *); 74 static u_int setflags(char *); 75 static void *setinfo(DBTYPE, char *); 76 static void usage(void) __attribute__((__noreturn__)); 77 static void *xcopy(void *, size_t); 78 static void chkcmd(enum S); 79 static void chkdata(enum S); 80 static void chkkey(enum S); 81 82 #ifdef STATISTICS 83 extern void __bt_stat(DB *); 84 #endif 85 86 static DBTYPE type; /* Database type. */ 87 static void *infop; /* Iflags. */ 88 static size_t lineno; /* Current line in test script. */ 89 static u_int flags; /* Current DB flags. */ 90 static int ofd = STDOUT_FILENO; /* Standard output fd. */ 91 92 static DB *XXdbp; /* Global for gdb. */ 93 static size_t XXlineno; /* Fast breakpoint for gdb. */ 94 95 int 96 main(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; 252 ldata: 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 } 286 lkey: 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 341 static void 342 compare(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 360 static void 361 get(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 388 static void 389 getdata(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 403 static void 404 put(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 418 static void 419 rem(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 443 static void 444 synk(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 455 static void 456 seq(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 485 static void 486 dump(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 } 511 done: return; 512 } 513 514 static u_int 515 setflags(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 538 static const char * 539 sflags(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 556 static DBTYPE 557 dbtype(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 569 static void * 570 setinfo(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 663 static void * 664 rfile(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 692 static void * 693 xcopy(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 703 static void 704 chkcmd(enum S state) 705 { 706 if (state != COMMAND) 707 errx(1, "line %zu: not expecting command", lineno); 708 } 709 710 static void 711 chkdata(enum S state) 712 { 713 if (state != DATA) 714 errx(1, "line %zu: not expecting data", lineno); 715 } 716 717 static void 718 chkkey(enum S state) 719 { 720 if (state != KEY) 721 errx(1, "line %zu: not expecting a key", lineno); 722 } 723 724 static void 725 usage(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