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