Annotation of parser3/src/lib/sdbm/sdbm.c, revision 1.1
1.1 ! paf 1: /* ====================================================================
! 2: * The Apache Software License, Version 1.1
! 3: *
! 4: * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
! 5: * 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: *
! 11: * 1. Redistributions of source code must retain the above copyright
! 12: * notice, this list of conditions and the following disclaimer.
! 13: *
! 14: * 2. Redistributions in binary form must reproduce the above copyright
! 15: * notice, this list of conditions and the following disclaimer in
! 16: * the documentation and/or other materials provided with the
! 17: * distribution.
! 18: *
! 19: * 3. The end-user documentation included with the redistribution,
! 20: * if any, must include the following acknowledgment:
! 21: * "This product includes software developed by the
! 22: * Apache Software Foundation (http://www.apache.org/)."
! 23: * Alternately, this acknowledgment may appear in the software itself,
! 24: * if and wherever such third-party acknowledgments normally appear.
! 25: *
! 26: * 4. The names "Apache" and "Apache Software Foundation" must
! 27: * not be used to endorse or promote products derived from this
! 28: * software without prior written permission. For written
! 29: * permission, please contact apache@apache.org.
! 30: *
! 31: * 5. Products derived from this software may not be called "Apache",
! 32: * nor may "Apache" appear in their name, without prior written
! 33: * permission of the Apache Software Foundation.
! 34: *
! 35: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
! 36: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
! 37: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
! 38: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
! 39: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
! 40: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
! 41: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
! 42: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
! 43: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
! 44: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
! 45: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
! 46: * SUCH DAMAGE.
! 47: * ====================================================================
! 48: *
! 49: * This software consists of voluntary contributions made by many
! 50: * individuals on behalf of the Apache Software Foundation. For more
! 51: * information on the Apache Software Foundation, please see
! 52: * <http://www.apache.org/>.
! 53: */
! 54:
! 55: /*
! 56: * sdbm - ndbm work-alike hashed database library
! 57: * based on Per-Aake Larson's Dynamic Hashing algorithms. BIT 18 (1978).
! 58: * author: oz@nexus.yorku.ca
! 59: * ex-public domain, ported to APR for Apache 2
! 60: * core routines
! 61: */
! 62:
! 63: #include "apr.h"
! 64: #include "apr_file_io.h"
! 65: #include "apr_strings.h"
! 66: #include "apr_errno.h"
! 67: #include "apr_sdbm.h"
! 68:
! 69: #include "sdbm_tune.h"
! 70: #include "sdbm_pair.h"
! 71: #include "sdbm_private.h"
! 72:
! 73: #include <string.h> /* for memset() */
! 74: #include <stdlib.h> /* for malloc() and free() */
! 75:
! 76: /*
! 77: * forward
! 78: */
! 79: static int getdbit (apr_sdbm_t *, long);
! 80: static apr_status_t setdbit(apr_sdbm_t *, long);
! 81: static apr_status_t getpage(apr_sdbm_t *db, long);
! 82: static apr_status_t getnext(apr_sdbm_datum_t *key, apr_sdbm_t *db);
! 83: static apr_status_t makroom(apr_sdbm_t *, long, int);
! 84:
! 85: /*
! 86: * useful macros
! 87: */
! 88: #define bad(x) ((x).dptr == NULL || (x).dsize <= 0)
! 89: #define exhash(item) sdbm_hash((item).dptr, (item).dsize)
! 90:
! 91: /* ### Does anything need these externally? */
! 92: #define sdbm_dirfno(db) ((db)->dirf)
! 93: #define sdbm_pagfno(db) ((db)->pagf)
! 94:
! 95: #define OFF_PAG(off) (apr_off_t) (off) * PBLKSIZ
! 96: #define OFF_DIR(off) (apr_off_t) (off) * DBLKSIZ
! 97:
! 98: static long masks[] = {
! 99: 000000000000, 000000000001, 000000000003, 000000000007,
! 100: 000000000017, 000000000037, 000000000077, 000000000177,
! 101: 000000000377, 000000000777, 000000001777, 000000003777,
! 102: 000000007777, 000000017777, 000000037777, 000000077777,
! 103: 000000177777, 000000377777, 000000777777, 000001777777,
! 104: 000003777777, 000007777777, 000017777777, 000037777777,
! 105: 000077777777, 000177777777, 000377777777, 000777777777,
! 106: 001777777777, 003777777777, 007777777777, 017777777777
! 107: };
! 108:
! 109: const apr_sdbm_datum_t sdbm_nullitem = { NULL, 0 };
! 110:
! 111: static apr_status_t database_cleanup(void *data)
! 112: {
! 113: apr_sdbm_t *db = data;
! 114:
! 115: /*
! 116: * Can't rely on apr_sdbm_unlock, since it will merely
! 117: * decrement the refcnt if several locks are held.
! 118: */
! 119: if (db->flags & (SDBM_SHARED_LOCK | SDBM_EXCLUSIVE_LOCK))
! 120: (void) apr_file_unlock(db->dirf);
! 121: (void) apr_file_close(db->dirf);
! 122: (void) apr_file_close(db->pagf);
! 123: free(db);
! 124:
! 125: return APR_SUCCESS;
! 126: }
! 127:
! 128: static apr_status_t prep(apr_sdbm_t **pdb, const char *dirname, const char *pagname,
! 129: apr_int32_t flags, apr_fileperms_t perms, apr_pool_t *p)
! 130: {
! 131: apr_sdbm_t *db;
! 132: apr_status_t status;
! 133:
! 134: *pdb = NULL;
! 135:
! 136: db = malloc(sizeof(*db));
! 137: memset(db, 0, sizeof(*db));
! 138:
! 139: db->pool = p;
! 140:
! 141: /*
! 142: * adjust user flags so that WRONLY becomes RDWR,
! 143: * as required by this package. Also set our internal
! 144: * flag for RDONLY if needed.
! 145: */
! 146: if (!(flags & APR_WRITE)) {
! 147: db->flags |= SDBM_RDONLY;
! 148: }
! 149:
! 150: /*
! 151: * adjust the file open flags so that we handle locking
! 152: * on our own (don't rely on any locking behavior within
! 153: * an apr_file_t, in case it's ever introduced, and set
! 154: * our own flag.
! 155: */
! 156: if (flags & APR_SHARELOCK) {
! 157: db->flags |= SDBM_SHARED;
! 158: flags &= ~APR_SHARELOCK;
! 159: }
! 160:
! 161: flags |= APR_BINARY | APR_READ;
! 162:
! 163: /*
! 164: * open the files in sequence, and stat the dirfile.
! 165: * If we fail anywhere, undo everything, return NULL.
! 166: */
! 167:
! 168: if ((status = apr_file_open(&db->dirf, dirname, flags, perms, p))
! 169: != APR_SUCCESS)
! 170: goto error;
! 171:
! 172: if ((status = apr_file_open(&db->pagf, pagname, flags, perms, p))
! 173: != APR_SUCCESS)
! 174: goto error;
! 175:
! 176: if ((status = apr_sdbm_lock(db, (db->flags & SDBM_RDONLY)
! 177: ? APR_FLOCK_SHARED
! 178: : APR_FLOCK_EXCLUSIVE))
! 179: != APR_SUCCESS)
! 180: goto error;
! 181:
! 182: /* apr_pcalloc zeroed the buffers
! 183: * apr_sdbm_lock stated the dirf->size and invalidated the cache
! 184: */
! 185:
! 186: /*
! 187: * if we are opened in SHARED mode, unlock ourself
! 188: */
! 189: if (db->flags & SDBM_SHARED)
! 190: if ((status = apr_sdbm_unlock(db)) != APR_SUCCESS)
! 191: goto error;
! 192:
! 193: /* make sure that we close the database at some point */
! 194: apr_pool_cleanup_register(p, db, database_cleanup, apr_pool_cleanup_null);
! 195:
! 196: /* Done! */
! 197: *pdb = db;
! 198: return APR_SUCCESS;
! 199:
! 200: error:
! 201: if (db->dirf && db->pagf)
! 202: (void) apr_sdbm_unlock(db);
! 203: if (db->dirf != NULL)
! 204: (void) apr_file_close(db->dirf);
! 205: if (db->pagf != NULL) {
! 206: (void) apr_file_close(db->pagf);
! 207: }
! 208: free(db);
! 209: return status;
! 210: }
! 211:
! 212: APU_DECLARE(apr_status_t) apr_sdbm_open(apr_sdbm_t **db, const char *file,
! 213: apr_int32_t flags,
! 214: apr_fileperms_t perms, apr_pool_t *p)
! 215: {
! 216: char *dirname = apr_pstrcat(p, file, APR_SDBM_DIRFEXT, NULL);
! 217: char *pagname = apr_pstrcat(p, file, APR_SDBM_PAGFEXT, NULL);
! 218:
! 219: return prep(db, dirname, pagname, flags, perms, p);
! 220: }
! 221:
! 222: APU_DECLARE(apr_status_t) apr_sdbm_close(apr_sdbm_t *db)
! 223: {
! 224: return apr_pool_cleanup_run(db->pool, db, database_cleanup);
! 225: }
! 226:
! 227: APU_DECLARE(apr_status_t) apr_sdbm_fetch(apr_sdbm_t *db, apr_sdbm_datum_t *val,
! 228: apr_sdbm_datum_t key)
! 229: {
! 230: apr_status_t status;
! 231:
! 232: if (db == NULL || bad(key))
! 233: return APR_EINVAL;
! 234:
! 235: if ((status = apr_sdbm_lock(db, APR_FLOCK_SHARED)) != APR_SUCCESS)
! 236: return status;
! 237:
! 238: if ((status = getpage(db, exhash(key))) == APR_SUCCESS) {
! 239: *val = getpair(db->pagbuf, key);
! 240: /* ### do we want a not-found result? */
! 241: }
! 242:
! 243: (void) apr_sdbm_unlock(db);
! 244:
! 245: return status;
! 246: }
! 247:
! 248: static apr_status_t write_page(apr_sdbm_t *db, const char *buf, long pagno)
! 249: {
! 250: apr_status_t status;
! 251: apr_off_t off = OFF_PAG(pagno);
! 252:
! 253: if ((status = apr_file_seek(db->pagf, APR_SET, &off)) == APR_SUCCESS)
! 254: status = apr_file_write_full(db->pagf, buf, PBLKSIZ, NULL);
! 255:
! 256: return status;
! 257: }
! 258:
! 259: APU_DECLARE(apr_status_t) apr_sdbm_delete(apr_sdbm_t *db,
! 260: const apr_sdbm_datum_t key)
! 261: {
! 262: apr_status_t status;
! 263:
! 264: if (db == NULL || bad(key))
! 265: return APR_EINVAL;
! 266: if (apr_sdbm_rdonly(db))
! 267: return APR_EINVAL;
! 268:
! 269: if ((status = apr_sdbm_lock(db, APR_FLOCK_EXCLUSIVE)) != APR_SUCCESS)
! 270: return status;
! 271:
! 272: if ((status = getpage(db, exhash(key))) == APR_SUCCESS) {
! 273: if (!delpair(db->pagbuf, key))
! 274: /* ### should we define some APRUTIL codes? */
! 275: status = APR_EGENERAL;
! 276: else
! 277: status = write_page(db, db->pagbuf, db->pagbno);
! 278: }
! 279:
! 280: (void) apr_sdbm_unlock(db);
! 281:
! 282: return status;
! 283: }
! 284:
! 285: APU_DECLARE(apr_status_t) apr_sdbm_store(apr_sdbm_t *db, apr_sdbm_datum_t key,
! 286: apr_sdbm_datum_t val, int flags)
! 287: {
! 288: int need;
! 289: register long hash;
! 290: apr_status_t status;
! 291:
! 292: if (db == NULL || bad(key))
! 293: return APR_EINVAL;
! 294: if (apr_sdbm_rdonly(db))
! 295: return APR_EINVAL;
! 296: need = key.dsize + val.dsize;
! 297: /*
! 298: * is the pair too big (or too small) for this database ??
! 299: */
! 300: if (need < 0 || need > PAIRMAX)
! 301: return APR_EINVAL;
! 302:
! 303: if ((status = apr_sdbm_lock(db, APR_FLOCK_EXCLUSIVE)) != APR_SUCCESS)
! 304: return status;
! 305:
! 306: if ((status = getpage(db, (hash = exhash(key)))) == APR_SUCCESS) {
! 307:
! 308: /*
! 309: * if we need to replace, delete the key/data pair
! 310: * first. If it is not there, ignore.
! 311: */
! 312: if (flags == APR_SDBM_REPLACE)
! 313: (void) delpair(db->pagbuf, key);
! 314: else if (!(flags & APR_SDBM_INSERTDUP) && duppair(db->pagbuf, key)) {
! 315: status = APR_EEXIST;
! 316: goto error;
! 317: }
! 318: /*
! 319: * if we do not have enough room, we have to split.
! 320: */
! 321: if (!fitpair(db->pagbuf, need))
! 322: if ((status = makroom(db, hash, need)) != APR_SUCCESS)
! 323: goto error;
! 324: /*
! 325: * we have enough room or split is successful. insert the key,
! 326: * and update the page file.
! 327: */
! 328: (void) putpair(db->pagbuf, key, val);
! 329:
! 330: status = write_page(db, db->pagbuf, db->pagbno);
! 331: }
! 332:
! 333: error:
! 334: (void) apr_sdbm_unlock(db);
! 335:
! 336: return status;
! 337: }
! 338:
! 339: /*
! 340: * makroom - make room by splitting the overfull page
! 341: * this routine will attempt to make room for SPLTMAX times before
! 342: * giving up.
! 343: */
! 344: static apr_status_t makroom(apr_sdbm_t *db, long hash, int need)
! 345: {
! 346: long newp;
! 347: char twin[PBLKSIZ];
! 348: char *pag = db->pagbuf;
! 349: char *new = twin;
! 350: register int smax = SPLTMAX;
! 351: apr_status_t status;
! 352:
! 353: do {
! 354: /*
! 355: * split the current page
! 356: */
! 357: (void) splpage(pag, new, db->hmask + 1);
! 358: /*
! 359: * address of the new page
! 360: */
! 361: newp = (hash & db->hmask) | (db->hmask + 1);
! 362:
! 363: /*
! 364: * write delay, read avoidence/cache shuffle:
! 365: * select the page for incoming pair: if key is to go to the new page,
! 366: * write out the previous one, and copy the new one over, thus making
! 367: * it the current page. If not, simply write the new page, and we are
! 368: * still looking at the page of interest. current page is not updated
! 369: * here, as sdbm_store will do so, after it inserts the incoming pair.
! 370: */
! 371: if (hash & (db->hmask + 1)) {
! 372: if ((status = write_page(db, db->pagbuf, db->pagbno))
! 373: != APR_SUCCESS)
! 374: return status;
! 375:
! 376: db->pagbno = newp;
! 377: (void) memcpy(pag, new, PBLKSIZ);
! 378: }
! 379: else {
! 380: if ((status = write_page(db, new, newp)) != APR_SUCCESS)
! 381: return status;
! 382: }
! 383:
! 384: if ((status = setdbit(db, db->curbit)) != APR_SUCCESS)
! 385: return status;
! 386: /*
! 387: * see if we have enough room now
! 388: */
! 389: if (fitpair(pag, need))
! 390: return APR_SUCCESS;
! 391: /*
! 392: * try again... update curbit and hmask as getpage would have
! 393: * done. because of our update of the current page, we do not
! 394: * need to read in anything. BUT we have to write the current
! 395: * [deferred] page out, as the window of failure is too great.
! 396: */
! 397: db->curbit = 2 * db->curbit
! 398: + ((hash & (db->hmask + 1)) ? 2 : 1);
! 399: db->hmask |= db->hmask + 1;
! 400:
! 401: if ((status = write_page(db, db->pagbuf, db->pagbno))
! 402: != APR_SUCCESS)
! 403: return status;
! 404:
! 405: } while (--smax);
! 406:
! 407: /*
! 408: * if we are here, this is real bad news. After SPLTMAX splits,
! 409: * we still cannot fit the key. say goodnight.
! 410: */
! 411: #if 0
! 412: (void) write(2, "sdbm: cannot insert after SPLTMAX attempts.\n", 44);
! 413: #endif
! 414: /* ### ENOSPC not really appropriate but better than nothing */
! 415: return APR_ENOSPC;
! 416:
! 417: }
! 418:
! 419: /* Reads 'len' bytes from file 'f' at offset 'off' into buf.
! 420: * 'off' is given relative to the start of the file.
! 421: * If EOF is returned while reading, this is taken as success.
! 422: */
! 423: static apr_status_t read_from(apr_file_t *f, void *buf,
! 424: apr_off_t off, apr_size_t len)
! 425: {
! 426: apr_status_t status;
! 427:
! 428: if ((status = apr_file_seek(f, APR_SET, &off)) != APR_SUCCESS ||
! 429: ((status = apr_file_read_full(f, buf, len, NULL)) != APR_SUCCESS)) {
! 430: /* if EOF is reached, pretend we read all zero's */
! 431: if (status == APR_EOF) {
! 432: memset(buf, 0, len);
! 433: status = APR_SUCCESS;
! 434: }
! 435: }
! 436:
! 437: return status;
! 438: }
! 439:
! 440: /*
! 441: * the following two routines will break if
! 442: * deletions aren't taken into account. (ndbm bug)
! 443: */
! 444: APU_DECLARE(apr_status_t) apr_sdbm_firstkey(apr_sdbm_t *db,
! 445: apr_sdbm_datum_t *key)
! 446: {
! 447: apr_status_t status;
! 448:
! 449: if ((status = apr_sdbm_lock(db, APR_FLOCK_SHARED)) != APR_SUCCESS)
! 450: return status;
! 451:
! 452: /*
! 453: * start at page 0
! 454: */
! 455: if ((status = read_from(db->pagf, db->pagbuf, OFF_PAG(0), PBLKSIZ))
! 456: == APR_SUCCESS) {
! 457: db->pagbno = 0;
! 458: db->blkptr = 0;
! 459: db->keyptr = 0;
! 460: status = getnext(key, db);
! 461: }
! 462:
! 463: (void) apr_sdbm_unlock(db);
! 464:
! 465: return status;
! 466: }
! 467:
! 468: APU_DECLARE(apr_status_t) apr_sdbm_nextkey(apr_sdbm_t *db,
! 469: apr_sdbm_datum_t *key)
! 470: {
! 471: apr_status_t status;
! 472:
! 473: if ((status = apr_sdbm_lock(db, APR_FLOCK_SHARED)) != APR_SUCCESS)
! 474: return status;
! 475:
! 476: status = getnext(key, db);
! 477:
! 478: (void) apr_sdbm_unlock(db);
! 479:
! 480: return status;
! 481: }
! 482:
! 483: /*
! 484: * all important binary tree traversal
! 485: */
! 486: static apr_status_t getpage(apr_sdbm_t *db, long hash)
! 487: {
! 488: register int hbit;
! 489: register long dbit;
! 490: register long pagb;
! 491: apr_status_t status;
! 492:
! 493: dbit = 0;
! 494: hbit = 0;
! 495: while (dbit < db->maxbno && getdbit(db, dbit))
! 496: dbit = 2 * dbit + ((hash & (1 << hbit++)) ? 2 : 1);
! 497:
! 498: debug(("dbit: %d...", dbit));
! 499:
! 500: db->curbit = dbit;
! 501: db->hmask = masks[hbit];
! 502:
! 503: pagb = hash & db->hmask;
! 504: /*
! 505: * see if the block we need is already in memory.
! 506: * note: this lookaside cache has about 10% hit rate.
! 507: */
! 508: if (pagb != db->pagbno) {
! 509: /*
! 510: * note: here, we assume a "hole" is read as 0s.
! 511: * if not, must zero pagbuf first.
! 512: * ### joe: this assumption was surely never correct? but
! 513: * ### we make it so in read_from anyway.
! 514: */
! 515: if ((status = read_from(db->pagf, db->pagbuf, OFF_PAG(pagb), PBLKSIZ))
! 516: != APR_SUCCESS)
! 517: return status;
! 518:
! 519: if (!chkpage(db->pagbuf))
! 520: return APR_ENOSPC; /* ### better error? */
! 521: db->pagbno = pagb;
! 522:
! 523: debug(("pag read: %d\n", pagb));
! 524: }
! 525: return APR_SUCCESS;
! 526: }
! 527:
! 528: static int getdbit(apr_sdbm_t *db, long dbit)
! 529: {
! 530: register long c;
! 531: register long dirb;
! 532:
! 533: c = dbit / BYTESIZ;
! 534: dirb = c / DBLKSIZ;
! 535:
! 536: if (dirb != db->dirbno) {
! 537: if (read_from(db->dirf, db->dirbuf, OFF_DIR(dirb), DBLKSIZ)
! 538: != APR_SUCCESS)
! 539: return 0;
! 540:
! 541: db->dirbno = dirb;
! 542:
! 543: debug(("dir read: %d\n", dirb));
! 544: }
! 545:
! 546: return db->dirbuf[c % DBLKSIZ] & (1 << dbit % BYTESIZ);
! 547: }
! 548:
! 549: static apr_status_t setdbit(apr_sdbm_t *db, long dbit)
! 550: {
! 551: register long c;
! 552: register long dirb;
! 553: apr_status_t status;
! 554: apr_off_t off;
! 555:
! 556: c = dbit / BYTESIZ;
! 557: dirb = c / DBLKSIZ;
! 558:
! 559: if (dirb != db->dirbno) {
! 560: if ((status = read_from(db->dirf, db->dirbuf, OFF_DIR(dirb), DBLKSIZ))
! 561: != APR_SUCCESS)
! 562: return status;
! 563:
! 564: db->dirbno = dirb;
! 565:
! 566: debug(("dir read: %d\n", dirb));
! 567: }
! 568:
! 569: db->dirbuf[c % DBLKSIZ] |= (1 << dbit % BYTESIZ);
! 570:
! 571: if (dbit >= db->maxbno)
! 572: db->maxbno += DBLKSIZ * BYTESIZ;
! 573:
! 574: off = OFF_DIR(dirb);
! 575: if ((status = apr_file_seek(db->dirf, APR_SET, &off)) == APR_SUCCESS)
! 576: status = apr_file_write_full(db->dirf, db->dirbuf, DBLKSIZ, NULL);
! 577:
! 578: return status;
! 579: }
! 580:
! 581: /*
! 582: * getnext - get the next key in the page, and if done with
! 583: * the page, try the next page in sequence
! 584: */
! 585: static apr_status_t getnext(apr_sdbm_datum_t *key, apr_sdbm_t *db)
! 586: {
! 587: apr_status_t status;
! 588: for (;;) {
! 589: db->keyptr++;
! 590: *key = getnkey(db->pagbuf, db->keyptr);
! 591: if (key->dptr != NULL)
! 592: return APR_SUCCESS;
! 593: /*
! 594: * we either run out, or there is nothing on this page..
! 595: * try the next one... If we lost our position on the
! 596: * file, we will have to seek.
! 597: */
! 598: db->keyptr = 0;
! 599: if (db->pagbno != db->blkptr++) {
! 600: apr_off_t off = OFF_PAG(db->blkptr);
! 601: if ((status = apr_file_seek(db->pagf, APR_SET, &off)
! 602: != APR_SUCCESS))
! 603: return status;
! 604: }
! 605:
! 606: db->pagbno = db->blkptr;
! 607: /* ### EOF acceptable here too? */
! 608: if ((status = apr_file_read_full(db->pagf, db->pagbuf, PBLKSIZ, NULL))
! 609: != APR_SUCCESS)
! 610: return status;
! 611: if (!chkpage(db->pagbuf))
! 612: return APR_EGENERAL; /* ### need better error */
! 613: }
! 614:
! 615: /* NOTREACHED */
! 616: }
! 617:
! 618:
! 619: APU_DECLARE(int) apr_sdbm_rdonly(apr_sdbm_t *db)
! 620: {
! 621: /* ### Should we return true if the first lock is a share lock,
! 622: * to reflect that apr_sdbm_store and apr_sdbm_delete will fail?
! 623: */
! 624: return (db->flags & SDBM_RDONLY) != 0;
! 625: }
! 626:
E-mail: