1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2000,2008 Oracle. All rights reserved. 5 * 6 * $Id: db_server_cxxutil.cpp,v 12.9 2008/01/08 20:58:50 bostic Exp $ 7 */ 8 9#include "db_config.h" 10 11#include "db_int.h" 12#include "db_cxx.h" 13#include "db_server.h" 14#include "dbinc_auto/clib_ext.h" 15 16extern "C" { 17#include "dbinc/db_server_int.h" 18#include "dbinc_auto/rpc_server_ext.h" 19#include "dbinc_auto/common_ext.h" 20 21extern int __dbsrv_main __P((void)); 22} 23 24static int add_home __P((char *)); 25static int add_passwd __P((char *)); 26static int env_recover __P((char *)); 27static void __dbclear_child __P((ct_entry *)); 28 29static LIST_HEAD(cthead, ct_entry) __dbsrv_head; 30static LIST_HEAD(homehead, home_entry) __dbsrv_home; 31static long __dbsrv_defto = DB_SERVER_TIMEOUT; 32static long __dbsrv_maxto = DB_SERVER_MAXTIMEOUT; 33static long __dbsrv_idleto = DB_SERVER_IDLETIMEOUT; 34static char *logfile = NULL; 35static char *prog; 36 37static void usage __P((char *)); 38static void version_check __P((void)); 39 40int __dbsrv_verbose = 0; 41 42int 43main( 44 int argc, 45 char **argv) 46{ 47 extern char *optarg; 48 CLIENT *cl; 49 int ch, ret; 50 char *passwd; 51 52 prog = argv[0]; 53 54 version_check(); 55 56 /* 57 * Check whether another server is running or not. There 58 * is a race condition where two servers could be racing to 59 * register with the portmapper. The goal of this check is to 60 * forbid running additional servers (like those started from 61 * the test suite) if the user is already running one. 62 * 63 * XXX 64 * This does not solve nor prevent two servers from being 65 * started at the same time and running recovery at the same 66 * time on the same environments. 67 */ 68 if ((cl = clnt_create("localhost", 69 DB_RPC_SERVERPROG, DB_RPC_SERVERVERS, "tcp")) != NULL) { 70 fprintf(stderr, 71 "%s: Berkeley DB RPC server already running.\n", prog); 72 clnt_destroy(cl); 73 return (EXIT_FAILURE); 74 } 75 76 LIST_INIT(&__dbsrv_home); 77 while ((ch = getopt(argc, argv, "h:I:L:P:t:T:Vv")) != EOF) 78 switch (ch) { 79 case 'h': 80 (void)add_home(optarg); 81 break; 82 case 'I': 83 if (__db_getlong(NULL, prog, 84 optarg, 1, LONG_MAX, &__dbsrv_idleto)) 85 return (EXIT_FAILURE); 86 break; 87 case 'L': 88 logfile = optarg; 89 break; 90 case 'P': 91 passwd = strdup(optarg); 92 memset(optarg, 0, strlen(optarg)); 93 if (passwd == NULL) { 94 fprintf(stderr, "%s: strdup: %s\n", 95 prog, strerror(errno)); 96 return (EXIT_FAILURE); 97 } 98 if ((ret = add_passwd(passwd)) != 0) { 99 fprintf(stderr, "%s: strdup: %s\n", 100 prog, strerror(ret)); 101 return (EXIT_FAILURE); 102 } 103 break; 104 case 't': 105 if (__db_getlong(NULL, prog, 106 optarg, 1, LONG_MAX, &__dbsrv_defto)) 107 return (EXIT_FAILURE); 108 break; 109 case 'T': 110 if (__db_getlong(NULL, prog, 111 optarg, 1, LONG_MAX, &__dbsrv_maxto)) 112 return (EXIT_FAILURE); 113 break; 114 case 'V': 115 printf("%s\n", db_version(NULL, NULL, NULL)); 116 return (EXIT_SUCCESS); 117 case 'v': 118 __dbsrv_verbose = 1; 119 break; 120 default: 121 usage(prog); 122 } 123 /* 124 * Check default timeout against maximum timeout 125 */ 126 if (__dbsrv_defto > __dbsrv_maxto) 127 __dbsrv_defto = __dbsrv_maxto; 128 129 /* 130 * Check default timeout against idle timeout 131 * It would be bad to timeout environments sooner than txns. 132 */ 133 if (__dbsrv_defto > __dbsrv_idleto) 134 fprintf(stderr, 135 "%s: WARNING: Idle timeout %ld is less than resource timeout %ld\n", 136 prog, __dbsrv_idleto, __dbsrv_defto); 137 138 LIST_INIT(&__dbsrv_head); 139 140 /* 141 * If a client crashes during an RPC, our reply to it 142 * generates a SIGPIPE. Ignore SIGPIPE so we don't exit unnecessarily. 143 */ 144#ifdef SIGPIPE 145 signal(SIGPIPE, SIG_IGN); 146#endif 147 148 if (logfile != NULL && __db_util_logset("berkeley_db_svc", logfile)) 149 return (EXIT_FAILURE); 150 151 /* 152 * Now that we are ready to start, run recovery on all the 153 * environments specified. 154 */ 155 if (env_recover(prog) != 0) 156 return (EXIT_FAILURE); 157 158 /* 159 * We've done our setup, now call the generated server loop 160 */ 161 if (__dbsrv_verbose) 162 printf("%s: Ready to receive requests\n", prog); 163 __dbsrv_main(); 164 165 /* NOTREACHED */ 166 abort(); 167} 168 169static void 170usage(char *prog) 171{ 172 fprintf(stderr, "usage: %s %s\n\t%s\n", prog, 173 "[-Vv] [-h home] [-P passwd]", 174 "[-I idletimeout] [-L logfile] [-t def_timeout] [-T maxtimeout]"); 175 exit(EXIT_FAILURE); 176} 177 178static void 179version_check() 180{ 181 int v_major, v_minor, v_patch; 182 183 /* Make sure we're loaded with the right version of the DB library. */ 184 (void)db_version(&v_major, &v_minor, &v_patch); 185 if (v_major != DB_VERSION_MAJOR || 186 v_minor != DB_VERSION_MINOR || v_patch != DB_VERSION_PATCH) { 187 fprintf(stderr, 188 "%s: version %d.%d.%d doesn't match library version %d.%d.%d\n", 189 prog, DB_VERSION_MAJOR, DB_VERSION_MINOR, 190 DB_VERSION_PATCH, v_major, v_minor, v_patch); 191 exit(EXIT_FAILURE); 192 } 193} 194 195extern "C" void 196__dbsrv_settimeout( 197 ct_entry *ctp, 198 u_int32_t to) 199{ 200 if (to > (u_int32_t)__dbsrv_maxto) 201 ctp->ct_timeout = __dbsrv_maxto; 202 else if (to <= 0) 203 ctp->ct_timeout = __dbsrv_defto; 204 else 205 ctp->ct_timeout = to; 206} 207 208extern "C" void 209__dbsrv_timeout(int force) 210{ 211 static long to_hint = -1; 212 time_t t; 213 long to; 214 ct_entry *ctp, *nextctp; 215 216 if ((t = time(NULL)) == -1) 217 return; 218 219 /* 220 * Check hint. If hint is further in the future 221 * than now, no work to do. 222 */ 223 if (!force && to_hint > 0 && t < to_hint) 224 return; 225 to_hint = -1; 226 /* 227 * Timeout transactions or cursors holding DB resources. 228 * Do this before timing out envs to properly release resources. 229 * 230 * !!! 231 * We can just loop through this list looking for cursors and txns. 232 * We do not need to verify txn and cursor relationships at this 233 * point because we maintain the list in LIFO order *and* we 234 * maintain activity in the ultimate txn parent of any cursor 235 * so either everything in a txn is timing out, or nothing. 236 * So, since we are LIFO, we will correctly close/abort all the 237 * appropriate handles, in the correct order. 238 */ 239 for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; ctp = nextctp) { 240 nextctp = LIST_NEXT(ctp, entries); 241 switch (ctp->ct_type) { 242 case CT_TXN: 243 to = *(ctp->ct_activep) + ctp->ct_timeout; 244 /* TIMEOUT */ 245 if (to < t) { 246 if (__dbsrv_verbose) 247 printf("Timing out txn id %ld\n", 248 ctp->ct_id); 249 (void)((DbTxn *)ctp->ct_anyp)->abort(); 250 __dbdel_ctp(ctp); 251 /* 252 * If we timed out an txn, we may have closed 253 * all sorts of ctp's. 254 * So start over with a guaranteed good ctp. 255 */ 256 nextctp = LIST_FIRST(&__dbsrv_head); 257 } else if ((to_hint > 0 && to_hint > to) || 258 to_hint == -1) 259 to_hint = to; 260 break; 261 case CT_CURSOR: 262 case (CT_JOINCUR | CT_CURSOR): 263 to = *(ctp->ct_activep) + ctp->ct_timeout; 264 /* TIMEOUT */ 265 if (to < t) { 266 if (__dbsrv_verbose) 267 printf("Timing out cursor %ld\n", 268 ctp->ct_id); 269 (void)__dbc_close_int(ctp); 270 /* 271 * Start over with a guaranteed good ctp. 272 */ 273 nextctp = LIST_FIRST(&__dbsrv_head); 274 } else if ((to_hint > 0 && to_hint > to) || 275 to_hint == -1) 276 to_hint = to; 277 break; 278 default: 279 break; 280 } 281 } 282 /* 283 * Timeout idle handles. 284 * If we are forcing a timeout, we'll close all env handles. 285 */ 286 for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; ctp = nextctp) { 287 nextctp = LIST_NEXT(ctp, entries); 288 if (ctp->ct_type != CT_ENV) 289 continue; 290 to = *(ctp->ct_activep) + ctp->ct_idle; 291 /* TIMEOUT */ 292 if (to < t || force) { 293 if (__dbsrv_verbose) 294 printf("Timing out env id %ld\n", ctp->ct_id); 295 (void)__env_close_int(ctp->ct_id, 0, 1); 296 /* 297 * If we timed out an env, we may have closed 298 * all sorts of ctp's (maybe even all of them. 299 * So start over with a guaranteed good ctp. 300 */ 301 nextctp = LIST_FIRST(&__dbsrv_head); 302 } 303 } 304} 305 306/* 307 * RECURSIVE FUNCTION. We need to clear/free any number of levels of nested 308 * layers. 309 */ 310static void 311__dbclear_child(ct_entry *parent) 312{ 313 ct_entry *ctp, *nextctp; 314 315 for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; 316 ctp = nextctp) { 317 nextctp = LIST_NEXT(ctp, entries); 318 if (ctp->ct_type == 0) 319 continue; 320 if (ctp->ct_parent == parent) { 321 __dbclear_child(ctp); 322 /* 323 * Need to do this here because le_next may 324 * have changed with the recursive call and we 325 * don't want to point to a removed entry. 326 */ 327 nextctp = LIST_NEXT(ctp, entries); 328 __dbclear_ctp(ctp); 329 } 330 } 331} 332 333extern "C" void 334__dbclear_ctp(ct_entry *ctp) 335{ 336 LIST_REMOVE(ctp, entries); 337 __os_free(NULL, ctp); 338} 339 340extern "C" void 341__dbdel_ctp(ct_entry *parent) 342{ 343 __dbclear_child(parent); 344 __dbclear_ctp(parent); 345} 346 347extern "C" ct_entry * 348new_ct_ent(int *errp) 349{ 350 time_t t; 351 ct_entry *ctp, *octp; 352 int ret; 353 354 if ((ret = __os_malloc(NULL, sizeof(ct_entry), &ctp)) != 0) { 355 *errp = ret; 356 return (NULL); 357 } 358 memset(ctp, 0, sizeof(ct_entry)); 359 /* 360 * Get the time as ID. We may service more than one request per 361 * second however. If we are, then increment id value until we 362 * find an unused one. We insert entries in LRU fashion at the 363 * head of the list. So, if the first entry doesn't match, then 364 * we know for certain that we can use our entry. 365 */ 366 if ((t = time(NULL)) == -1) { 367 *errp = __os_get_errno(); 368 __os_free(NULL, ctp); 369 return (NULL); 370 } 371 octp = LIST_FIRST(&__dbsrv_head); 372 if (octp != NULL && octp->ct_id >= t) 373 t = octp->ct_id + 1; 374 ctp->ct_id = t; 375 ctp->ct_idle = __dbsrv_idleto; 376 ctp->ct_activep = &ctp->ct_active; 377 ctp->ct_origp = NULL; 378 ctp->ct_refcount = 1; 379 380 LIST_INSERT_HEAD(&__dbsrv_head, ctp, entries); 381 return (ctp); 382} 383 384extern "C" ct_entry * 385get_tableent(long id) 386{ 387 ct_entry *ctp; 388 389 for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; 390 ctp = LIST_NEXT(ctp, entries)) 391 if (ctp->ct_id == id) 392 return (ctp); 393 return (NULL); 394} 395 396extern "C" ct_entry * 397__dbsrv_sharedb(ct_entry *db_ctp, 398 const char *name, const char *subdb, DBTYPE type, u_int32_t flags) 399{ 400 ct_entry *ctp; 401 402 /* 403 * Check if we can share a db handle. Criteria for sharing are: 404 * If any of the non-sharable flags are set, we cannot share. 405 * Must be a db ctp, obviously. 406 * Must share the same env parent. 407 * Must be the same type, or current one DB_UNKNOWN. 408 * Must be same byteorder, or current one must not care. 409 * All flags must match. 410 * Must be same name, but don't share in-memory databases. 411 * Must be same subdb name. 412 */ 413 if (flags & DB_SERVER_DBNOSHARE) 414 return (NULL); 415 for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; 416 ctp = LIST_NEXT(ctp, entries)) { 417 /* 418 * Skip ourselves. 419 */ 420 if (ctp == db_ctp) 421 continue; 422 if (ctp->ct_type != CT_DB) 423 continue; 424 if (ctp->ct_envparent != db_ctp->ct_envparent) 425 continue; 426 if (type != DB_UNKNOWN && ctp->ct_dbdp.type != type) 427 continue; 428 if (ctp->ct_dbdp.dbflags != LF_ISSET(DB_SERVER_DBFLAGS)) 429 continue; 430 if (db_ctp->ct_dbdp.setflags != 0 && 431 ctp->ct_dbdp.setflags != db_ctp->ct_dbdp.setflags) 432 continue; 433 if (name == NULL || ctp->ct_dbdp.db == NULL || 434 strcmp(name, ctp->ct_dbdp.db) != 0) 435 continue; 436 if (subdb != ctp->ct_dbdp.subdb && 437 (subdb == NULL || ctp->ct_dbdp.subdb == NULL || 438 strcmp(subdb, ctp->ct_dbdp.subdb) != 0)) 439 continue; 440 /* 441 * If we get here, then we match. 442 */ 443 ctp->ct_refcount++; 444 return (ctp); 445 } 446 447 return (NULL); 448} 449 450extern "C" ct_entry * 451__dbsrv_shareenv(ct_entry *env_ctp, home_entry *home, u_int32_t flags) 452{ 453 ct_entry *ctp; 454 455 /* 456 * Check if we can share an env. Criteria for sharing are: 457 * Must be an env ctp, obviously. 458 * Must share the same home env. 459 * All flags must match. 460 */ 461 for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; 462 ctp = LIST_NEXT(ctp, entries)) { 463 /* 464 * Skip ourselves. 465 */ 466 if (ctp == env_ctp) 467 continue; 468 if (ctp->ct_type != CT_ENV) 469 continue; 470 if (ctp->ct_envdp.home != home) 471 continue; 472 if (ctp->ct_envdp.envflags != flags) 473 continue; 474 if (ctp->ct_envdp.onflags != env_ctp->ct_envdp.onflags) 475 continue; 476 if (ctp->ct_envdp.offflags != env_ctp->ct_envdp.offflags) 477 continue; 478 /* 479 * If we get here, then we match. The only thing left to 480 * check is the timeout. Since the server timeout set by 481 * the client is a hint, for sharing we'll give them the 482 * benefit of the doubt and grant them the longer timeout. 483 */ 484 if (ctp->ct_timeout < env_ctp->ct_timeout) 485 ctp->ct_timeout = env_ctp->ct_timeout; 486 ctp->ct_refcount++; 487 return (ctp); 488 } 489 490 return (NULL); 491} 492 493extern "C" void 494__dbsrv_active(ct_entry *ctp) 495{ 496 time_t t; 497 ct_entry *envctp; 498 499 if (ctp == NULL) 500 return; 501 if ((t = time(NULL)) == -1) 502 return; 503 *(ctp->ct_activep) = t; 504 if ((envctp = ctp->ct_envparent) == NULL) 505 return; 506 *(envctp->ct_activep) = t; 507 return; 508} 509 510extern "C" int 511__db_close_int(long id, u_int32_t flags) 512{ 513 Db *dbp; 514 int ret; 515 ct_entry *ctp; 516 517 ret = 0; 518 ctp = get_tableent(id); 519 if (ctp == NULL) 520 return (DB_NOSERVER_ID); 521 DB_ASSERT(NULL, ctp->ct_type == CT_DB); 522 if (__dbsrv_verbose && ctp->ct_refcount != 1) 523 printf("Deref'ing dbp id %ld, refcount %d\n", 524 id, ctp->ct_refcount); 525 if (--ctp->ct_refcount != 0) 526 return (ret); 527 dbp = ctp->ct_dbp; 528 if (__dbsrv_verbose) 529 printf("Closing dbp id %ld\n", id); 530 531 ret = dbp->close(flags); 532 __dbdel_ctp(ctp); 533 return (ret); 534} 535 536extern "C" int 537__dbc_close_int(ct_entry *dbc_ctp) 538{ 539 Dbc *dbc; 540 int ret; 541 ct_entry *ctp; 542 543 dbc = (Dbc *)dbc_ctp->ct_anyp; 544 545 ret = dbc->close(); 546 /* 547 * If this cursor is a join cursor then we need to fix up the 548 * cursors that it was joined from so that they are independent again. 549 */ 550 if (dbc_ctp->ct_type & CT_JOINCUR) 551 for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; 552 ctp = LIST_NEXT(ctp, entries)) { 553 /* 554 * Test if it is a join cursor, and if it is part 555 * of this one. 556 */ 557 if ((ctp->ct_type & CT_JOIN) && 558 ctp->ct_activep == &dbc_ctp->ct_active) { 559 ctp->ct_type &= ~CT_JOIN; 560 ctp->ct_activep = ctp->ct_origp; 561 __dbsrv_active(ctp); 562 } 563 } 564 __dbclear_ctp(dbc_ctp); 565 return (ret); 566 567} 568 569extern "C" int 570__env_close_int(long id, u_int32_t flags, int force) 571{ 572 DbEnv *dbenv; 573 int ret; 574 ct_entry *ctp, *dbctp, *nextctp; 575 576 ret = 0; 577 ctp = get_tableent(id); 578 if (ctp == NULL) 579 return (DB_NOSERVER_ID); 580 DB_ASSERT(NULL, ctp->ct_type == CT_ENV); 581 if (__dbsrv_verbose && ctp->ct_refcount != 1) 582 printf("Deref'ing env id %ld, refcount %d\n", 583 id, ctp->ct_refcount); 584 /* 585 * If we are timing out, we need to force the close, no matter 586 * what the refcount. 587 */ 588 if (--ctp->ct_refcount != 0 && !force) 589 return (ret); 590 dbenv = ctp->ct_envp; 591 if (__dbsrv_verbose) 592 printf("Closing env id %ld\n", id); 593 594 /* 595 * If we're timing out an env, we want to close all of its 596 * database handles as well. All of the txns and cursors 597 * must have been timed out prior to timing out the env. 598 */ 599 if (force) 600 for (dbctp = LIST_FIRST(&__dbsrv_head); 601 dbctp != NULL; dbctp = nextctp) { 602 nextctp = LIST_NEXT(dbctp, entries); 603 if (dbctp->ct_type != CT_DB) 604 continue; 605 if (dbctp->ct_envparent != ctp) 606 continue; 607 /* 608 * We found a DB handle that is part of this 609 * environment. Close it. 610 */ 611 __db_close_int(dbctp->ct_id, 0); 612 /* 613 * If we timed out a dbp, we may have removed 614 * multiple ctp entries. Start over with a 615 * guaranteed good ctp. 616 */ 617 nextctp = LIST_FIRST(&__dbsrv_head); 618 } 619 620 ret = dbenv->close(flags); 621 __dbdel_ctp(ctp); 622 return (ret); 623} 624 625static int 626add_home(char *home) 627{ 628 home_entry *hp, *homep; 629 int ret; 630 631 if ((ret = __os_malloc(NULL, sizeof(home_entry), &hp)) != 0) 632 return (ret); 633 if ((ret = __os_malloc(NULL, strlen(home)+1, &hp->home)) != 0) 634 return (ret); 635 memcpy(hp->home, home, strlen(home)+1); 636 hp->dir = home; 637 hp->passwd = NULL; 638 /* 639 * This loop is to remove any trailing path separators, 640 * to assure hp->name points to the last component. 641 */ 642 hp->name = __db_rpath(home); 643 if (hp->name != NULL) { 644 *(hp->name) = '\0'; 645 hp->name++; 646 } else 647 hp->name = home; 648 while (*(hp->name) == '\0') { 649 hp->name = __db_rpath(home); 650 *(hp->name) = '\0'; 651 hp->name++; 652 } 653 /* 654 * Now we have successfully added it. Make sure there are no 655 * identical names. 656 */ 657 for (homep = LIST_FIRST(&__dbsrv_home); homep != NULL; 658 homep = LIST_NEXT(homep, entries)) 659 if (strcmp(homep->name, hp->name) == 0) { 660 printf("Already added home name %s, at directory %s\n", 661 hp->name, homep->dir); 662 return (-1); 663 } 664 LIST_INSERT_HEAD(&__dbsrv_home, hp, entries); 665 if (__dbsrv_verbose) 666 printf("Added home %s in dir %s\n", hp->name, hp->dir); 667 return (0); 668} 669 670static int 671add_passwd(char *passwd) 672{ 673 home_entry *hp; 674 675 /* 676 * We add the passwd to the last given home dir. If there 677 * isn't a home dir, or the most recent one already has a 678 * passwd, then there is a user error. 679 */ 680 hp = LIST_FIRST(&__dbsrv_home); 681 if (hp == NULL || hp->passwd != NULL) 682 return (EINVAL); 683 /* 684 * We've already strdup'ed the passwd above, so we don't need 685 * to malloc new space, just point to it. 686 */ 687 hp->passwd = passwd; 688 return (0); 689} 690 691extern "C" home_entry * 692get_fullhome(char *name) 693{ 694 home_entry *hp; 695 696 if (name == NULL) 697 return (NULL); 698 699 for (hp = LIST_FIRST(&__dbsrv_home); hp != NULL; 700 hp = LIST_NEXT(hp, entries)) 701 if (strcmp(name, hp->name) == 0) 702 return (hp); 703 return (NULL); 704} 705 706static int 707env_recover(char *progname) 708{ 709 DbEnv *dbenv; 710 home_entry *hp; 711 u_int32_t flags; 712 int exitval, ret; 713 714 for (hp = LIST_FIRST(&__dbsrv_home); hp != NULL; 715 hp = LIST_NEXT(hp, entries)) { 716 exitval = 0; 717 dbenv = new DbEnv(DB_CXX_NO_EXCEPTIONS); 718 if (__dbsrv_verbose == 1) 719 (void)dbenv->set_verbose(DB_VERB_RECOVERY, 1); 720 dbenv->set_errfile(stderr); 721 dbenv->set_errpfx(progname); 722 if (hp->passwd != NULL) 723 (void)dbenv->set_encrypt(hp->passwd, DB_ENCRYPT_AES); 724 725 /* 726 * Initialize the env with DB_RECOVER. That is all we 727 * have to do to run recovery. 728 */ 729 if (__dbsrv_verbose) 730 printf("Running recovery on %s\n", hp->home); 731 flags = DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | 732 DB_INIT_TXN | DB_USE_ENVIRON | DB_RECOVER; 733 if ((ret = dbenv->open(hp->home, flags, 0)) != 0) { 734 dbenv->err(ret, "DbEnv->open"); 735 goto error; 736 } 737 738 if (0) { 739error: exitval = 1; 740 } 741 if ((ret = dbenv->close(0)) != 0) { 742 exitval = 1; 743 fprintf(stderr, "%s: dbenv->close: %s\n", 744 progname, db_strerror(ret)); 745 } 746 if (exitval) 747 return (exitval); 748 } 749 return (0); 750} 751