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