Annotation of parser3/src/lib/sdbm/sdbm.c, revision 1.9
1.8 moko 1: /* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
2: * applicable.
1.1 paf 3: *
1.8 moko 4: * Licensed under the Apache License, Version 2.0 (the "License");
5: * you may not use this file except in compliance with the License.
6: * You may obtain a copy of the License at
1.1 paf 7: *
1.8 moko 8: * http://www.apache.org/licenses/LICENSE-2.0
1.1 paf 9: *
1.8 moko 10: * Unless required by applicable law or agreed to in writing, software
11: * distributed under the License is distributed on an "AS IS" BASIS,
12: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13: * See the License for the specific language governing permissions and
14: * limitations under the License.
1.1 paf 15: */
16:
17: /*
18: * sdbm - ndbm work-alike hashed database library
19: * based on Per-Aake Larson's Dynamic Hashing algorithms. BIT 18 (1978).
20: * author: oz@nexus.yorku.ca
21: * ex-public domain, ported to APR for Apache 2
22: * core routines
23: */
24:
1.5 moko 25: #include "pa_apr.h"
26: #include "pa_file_io.h"
27: #include "pa_strings.h"
28: #include "pa_errno.h"
29: #include "pa_sdbm.h"
1.1 paf 30:
31: #include "sdbm_tune.h"
32: #include "sdbm_pair.h"
33: #include "sdbm_private.h"
34:
35: /*
36: * forward
37: */
1.5 moko 38: static int getdbit (pa_sdbm_t *, long);
39: static pa_status_t setdbit(pa_sdbm_t *, long);
40: static pa_status_t getpage(pa_sdbm_t *db, long);
41: static pa_status_t getnext(pa_sdbm_datum_t *key, pa_sdbm_t *db);
42: static pa_status_t makroom(pa_sdbm_t *, long, int);
1.1 paf 43:
44: /*
45: * useful macros
46: */
47: #define bad(x) ((x).dptr == NULL || (x).dsize <= 0)
48: #define exhash(item) sdbm_hash((item).dptr, (item).dsize)
49:
50: /* ### Does anything need these externally? */
51: #define sdbm_dirfno(db) ((db)->dirf)
52: #define sdbm_pagfno(db) ((db)->pagf)
53:
1.5 moko 54: #define OFF_PAG(off) (pa_off_t) (off) * PBLKSIZ
55: #define OFF_DIR(off) (pa_off_t) (off) * DBLKSIZ
1.1 paf 56:
57: static long masks[] = {
58: 000000000000, 000000000001, 000000000003, 000000000007,
59: 000000000017, 000000000037, 000000000077, 000000000177,
60: 000000000377, 000000000777, 000000001777, 000000003777,
61: 000000007777, 000000017777, 000000037777, 000000077777,
62: 000000177777, 000000377777, 000000777777, 000001777777,
63: 000003777777, 000007777777, 000017777777, 000037777777,
64: 000077777777, 000177777777, 000377777777, 000777777777,
65: 001777777777, 003777777777, 007777777777, 017777777777
66: };
67:
1.5 moko 68: const pa_sdbm_datum_t sdbm_nullitem = { NULL, 0 };
1.1 paf 69:
1.5 moko 70: static pa_status_t database_cleanup(void *data)
1.1 paf 71: {
1.5 moko 72: pa_sdbm_t *db = data;
1.1 paf 73:
74: /*
1.5 moko 75: * Can't rely on pa_sdbm_unlock, since it will merely
1.1 paf 76: * decrement the refcnt if several locks are held.
77: */
78: if (db->flags & (SDBM_SHARED_LOCK | SDBM_EXCLUSIVE_LOCK))
1.5 moko 79: (void) pa_file_unlock(db->dirf);
80: (void) pa_file_close(db->dirf);
81: (void) pa_file_close(db->pagf);
1.4 misha 82: // free(db); // libcg will do it
1.1 paf 83:
1.5 moko 84: return PA_SUCCESS;
1.1 paf 85: }
86:
1.5 moko 87: static pa_status_t prep(pa_sdbm_t **pdb, const char *dirname, const char *pagname,
88: pa_int32_t flags, pa_fileperms_t perms, pa_pool_t *p)
1.1 paf 89: {
1.5 moko 90: pa_sdbm_t *db;
91: pa_status_t status;
1.1 paf 92:
93: *pdb = NULL;
94:
1.4 misha 95: // db = malloc(sizeof(*db));
96: // memset(db, 0, sizeof(*db));
1.6 moko 97: db = pa_sdbm_malloc(sizeof(*db));
1.1 paf 98:
99: db->pool = p;
100:
101: /*
102: * adjust user flags so that WRONLY becomes RDWR,
103: * as required by this package. Also set our internal
104: * flag for RDONLY if needed.
105: */
1.5 moko 106: if (!(flags & PA_WRITE)) {
1.1 paf 107: db->flags |= SDBM_RDONLY;
108: }
109:
110: /*
111: * adjust the file open flags so that we handle locking
112: * on our own (don't rely on any locking behavior within
1.5 moko 113: * an pa_file_t, in case it's ever introduced, and set
1.1 paf 114: * our own flag.
115: */
1.5 moko 116: if (flags & PA_SHARELOCK) {
1.1 paf 117: db->flags |= SDBM_SHARED;
1.5 moko 118: flags &= ~PA_SHARELOCK;
1.1 paf 119: }
120:
1.5 moko 121: flags |= PA_BINARY | PA_READ;
1.1 paf 122:
123: /*
124: * open the files in sequence, and stat the dirfile.
125: * If we fail anywhere, undo everything, return NULL.
126: */
127:
1.5 moko 128: if ((status = pa_file_open(&db->dirf, dirname, flags, perms, p))
129: != PA_SUCCESS)
1.1 paf 130: goto error;
131:
1.5 moko 132: if ((status = pa_file_open(&db->pagf, pagname, flags, perms, p))
133: != PA_SUCCESS)
1.1 paf 134: goto error;
135:
1.5 moko 136: if ((status = pa_sdbm_lock(db, (db->flags & SDBM_RDONLY)
137: ? PA_FLOCK_SHARED
138: : PA_FLOCK_EXCLUSIVE))
139: != PA_SUCCESS)
1.1 paf 140: goto error;
141:
1.5 moko 142: /* pa_pcalloc zeroed the buffers
143: * pa_sdbm_lock stated the dirf->size and invalidated the cache
1.1 paf 144: */
145:
146: /*
147: * if we are opened in SHARED mode, unlock ourself
148: */
149: if (db->flags & SDBM_SHARED)
1.5 moko 150: if ((status = pa_sdbm_unlock(db)) != PA_SUCCESS)
1.1 paf 151: goto error;
152:
153: /* make sure that we close the database at some point */
1.5 moko 154: //pa_pool_cleanup_register(p, db, database_cleanup, pa_pool_cleanup_null);
1.1 paf 155:
156: /* Done! */
157: *pdb = db;
1.5 moko 158: return PA_SUCCESS;
1.1 paf 159:
160: error:
161: if (db->dirf && db->pagf)
1.5 moko 162: (void) pa_sdbm_unlock(db);
1.1 paf 163: if (db->dirf != NULL)
1.5 moko 164: (void) pa_file_close(db->dirf);
1.1 paf 165: if (db->pagf != NULL) {
1.5 moko 166: (void) pa_file_close(db->pagf);
1.1 paf 167: }
1.4 misha 168: // free(db); // libcg will do it
1.1 paf 169: return status;
170: }
171:
1.5 moko 172: pa_status_t pa_sdbm_open(pa_sdbm_t **db, const char *file,
173: pa_int32_t flags,
174: pa_fileperms_t perms, pa_pool_t *p)
1.1 paf 175: {
1.9 ! moko 176: char *dirname = pa_pstrcat(file, PA_SDBM_DIRFEXT);
! 177: char *pagname = pa_pstrcat(file, PA_SDBM_PAGFEXT);
1.1 paf 178:
179: return prep(db, dirname, pagname, flags, perms, p);
180: }
181:
1.5 moko 182: pa_status_t pa_sdbm_close(pa_sdbm_t *db)
1.1 paf 183: {
1.5 moko 184: database_cleanup(db); return PA_SUCCESS;
185: //return pa_pool_cleanup_run(db->pool, db, database_cleanup);
1.1 paf 186: }
187:
1.5 moko 188: pa_status_t pa_sdbm_fetch(pa_sdbm_t *db, pa_sdbm_datum_t *val,
189: pa_sdbm_datum_t key)
1.1 paf 190: {
1.5 moko 191: pa_status_t status;
1.1 paf 192:
193: if (db == NULL || bad(key))
1.5 moko 194: return PA_EINVAL;
1.1 paf 195:
1.5 moko 196: if ((status = pa_sdbm_lock(db, PA_FLOCK_SHARED)) != PA_SUCCESS)
1.1 paf 197: return status;
198:
1.5 moko 199: if ((status = getpage(db, exhash(key))) == PA_SUCCESS) {
1.1 paf 200: *val = getpair(db->pagbuf, key);
201: /* ### do we want a not-found result? */
202: }
203:
1.5 moko 204: (void) pa_sdbm_unlock(db);
1.1 paf 205:
206: return status;
207: }
208:
1.5 moko 209: static pa_status_t write_page(pa_sdbm_t *db, const char *buf, long pagno)
1.1 paf 210: {
1.5 moko 211: pa_status_t status;
212: pa_off_t off = OFF_PAG(pagno);
1.1 paf 213:
1.5 moko 214: if ((status = pa_file_seek(db->pagf, PA_SET, &off)) == PA_SUCCESS)
215: status = pa_file_write_full(db->pagf, buf, PBLKSIZ, NULL);
1.1 paf 216:
217: return status;
218: }
219:
1.5 moko 220: pa_status_t pa_sdbm_delete(pa_sdbm_t *db,
221: const pa_sdbm_datum_t key)
1.1 paf 222: {
1.5 moko 223: pa_status_t status;
1.1 paf 224:
225: if (db == NULL || bad(key))
1.5 moko 226: return PA_EINVAL;
227: if (pa_sdbm_rdonly(db))
228: return PA_EINVAL;
1.1 paf 229:
1.5 moko 230: if ((status = pa_sdbm_lock(db, PA_FLOCK_EXCLUSIVE)) != PA_SUCCESS)
1.1 paf 231: return status;
232:
1.5 moko 233: if ((status = getpage(db, exhash(key))) == PA_SUCCESS) {
1.1 paf 234: if (!delpair(db->pagbuf, key))
235: /* ### should we define some APRUTIL codes? */
1.5 moko 236: status = PA_SUCCESS; /* PAF: were PA_EGENERAL, contradicting comment in .h file :( */
1.1 paf 237: else
238: status = write_page(db, db->pagbuf, db->pagbno);
239: }
240:
1.5 moko 241: (void) pa_sdbm_unlock(db);
1.1 paf 242:
243: return status;
244: }
245:
1.5 moko 246: pa_status_t pa_sdbm_store(pa_sdbm_t *db, pa_sdbm_datum_t key,
247: pa_sdbm_datum_t val, int flags)
1.1 paf 248: {
249: int need;
250: register long hash;
1.5 moko 251: pa_status_t status;
1.1 paf 252:
253: if (db == NULL || bad(key))
1.5 moko 254: return PA_EINVAL;
255: if (pa_sdbm_rdonly(db))
256: return PA_EINVAL;
1.1 paf 257: need = key.dsize + val.dsize;
258: /*
259: * is the pair too big (or too small) for this database ??
260: */
261: if (need < 0 || need > PAIRMAX)
1.5 moko 262: return PA_EINVAL;
1.1 paf 263:
1.5 moko 264: if ((status = pa_sdbm_lock(db, PA_FLOCK_EXCLUSIVE)) != PA_SUCCESS)
1.1 paf 265: return status;
266:
1.5 moko 267: if ((status = getpage(db, (hash = exhash(key)))) == PA_SUCCESS) {
1.1 paf 268:
269: /*
270: * if we need to replace, delete the key/data pair
271: * first. If it is not there, ignore.
272: */
1.5 moko 273: if (flags == PA_SDBM_REPLACE)
1.1 paf 274: (void) delpair(db->pagbuf, key);
1.5 moko 275: else if (!(flags & PA_SDBM_INSERTDUP) && duppair(db->pagbuf, key)) {
276: status = PA_EEXIST;
1.1 paf 277: goto error;
278: }
279: /*
280: * if we do not have enough room, we have to split.
281: */
282: if (!fitpair(db->pagbuf, need))
1.5 moko 283: if ((status = makroom(db, hash, need)) != PA_SUCCESS)
1.1 paf 284: goto error;
285: /*
286: * we have enough room or split is successful. insert the key,
287: * and update the page file.
288: */
289: (void) putpair(db->pagbuf, key, val);
290:
291: status = write_page(db, db->pagbuf, db->pagbno);
292: }
293:
294: error:
1.5 moko 295: (void) pa_sdbm_unlock(db);
1.1 paf 296:
297: return status;
298: }
299:
300: /*
301: * makroom - make room by splitting the overfull page
302: * this routine will attempt to make room for SPLTMAX times before
303: * giving up.
304: */
1.5 moko 305: static pa_status_t makroom(pa_sdbm_t *db, long hash, int need)
1.1 paf 306: {
307: long newp;
308: char twin[PBLKSIZ];
309: char *pag = db->pagbuf;
310: char *new = twin;
311: register int smax = SPLTMAX;
1.5 moko 312: pa_status_t status;
1.1 paf 313:
314: do {
315: /*
316: * split the current page
317: */
318: (void) splpage(pag, new, db->hmask + 1);
319: /*
320: * address of the new page
321: */
322: newp = (hash & db->hmask) | (db->hmask + 1);
323:
324: /*
325: * write delay, read avoidence/cache shuffle:
326: * select the page for incoming pair: if key is to go to the new page,
327: * write out the previous one, and copy the new one over, thus making
328: * it the current page. If not, simply write the new page, and we are
329: * still looking at the page of interest. current page is not updated
330: * here, as sdbm_store will do so, after it inserts the incoming pair.
331: */
332: if (hash & (db->hmask + 1)) {
333: if ((status = write_page(db, db->pagbuf, db->pagbno))
1.5 moko 334: != PA_SUCCESS)
1.1 paf 335: return status;
336:
337: db->pagbno = newp;
338: (void) memcpy(pag, new, PBLKSIZ);
339: }
340: else {
1.5 moko 341: if ((status = write_page(db, new, newp)) != PA_SUCCESS)
1.1 paf 342: return status;
343: }
344:
1.5 moko 345: if ((status = setdbit(db, db->curbit)) != PA_SUCCESS)
1.1 paf 346: return status;
347: /*
348: * see if we have enough room now
349: */
350: if (fitpair(pag, need))
1.5 moko 351: return PA_SUCCESS;
1.1 paf 352: /*
353: * try again... update curbit and hmask as getpage would have
354: * done. because of our update of the current page, we do not
355: * need to read in anything. BUT we have to write the current
356: * [deferred] page out, as the window of failure is too great.
357: */
358: db->curbit = 2 * db->curbit
359: + ((hash & (db->hmask + 1)) ? 2 : 1);
360: db->hmask |= db->hmask + 1;
361:
362: if ((status = write_page(db, db->pagbuf, db->pagbno))
1.5 moko 363: != PA_SUCCESS)
1.1 paf 364: return status;
365:
366: } while (--smax);
367:
368: /*
369: * if we are here, this is real bad news. After SPLTMAX splits,
370: * we still cannot fit the key. say goodnight.
371: */
372: #if 0
373: (void) write(2, "sdbm: cannot insert after SPLTMAX attempts.\n", 44);
374: #endif
375: /* ### ENOSPC not really appropriate but better than nothing */
1.5 moko 376: return PA_ENOSPC;
1.1 paf 377:
378: }
379:
380: /* Reads 'len' bytes from file 'f' at offset 'off' into buf.
381: * 'off' is given relative to the start of the file.
382: * If EOF is returned while reading, this is taken as success.
383: */
1.5 moko 384: static pa_status_t read_from(pa_file_t *f, void *buf,
385: pa_off_t off, pa_size_t len)
1.1 paf 386: {
1.5 moko 387: pa_status_t status;
1.1 paf 388:
1.5 moko 389: if ((status = pa_file_seek(f, PA_SET, &off)) != PA_SUCCESS ||
390: ((status = pa_file_read_full(f, buf, len, NULL)) != PA_SUCCESS)) {
1.1 paf 391: /* if EOF is reached, pretend we read all zero's */
1.5 moko 392: if (status == PA_EOF) {
1.1 paf 393: memset(buf, 0, len);
1.5 moko 394: status = PA_SUCCESS;
1.1 paf 395: }
396: }
397:
398: return status;
399: }
400:
401: /*
402: * the following two routines will break if
403: * deletions aren't taken into account. (ndbm bug)
404: */
1.5 moko 405: pa_status_t pa_sdbm_firstkey(pa_sdbm_t *db,
406: pa_sdbm_datum_t *key)
1.1 paf 407: {
1.5 moko 408: pa_status_t status;
1.1 paf 409:
1.5 moko 410: if ((status = pa_sdbm_lock(db, PA_FLOCK_SHARED)) != PA_SUCCESS)
1.1 paf 411: return status;
412:
413: /*
414: * start at page 0
415: */
416: if ((status = read_from(db->pagf, db->pagbuf, OFF_PAG(0), PBLKSIZ))
1.5 moko 417: == PA_SUCCESS) {
1.1 paf 418: db->pagbno = 0;
419: db->blkptr = 0;
420: db->keyptr = 0;
421: status = getnext(key, db);
422: }
423:
1.5 moko 424: (void) pa_sdbm_unlock(db);
1.1 paf 425:
426: return status;
427: }
428:
1.5 moko 429: pa_status_t pa_sdbm_nextkey(pa_sdbm_t *db,
430: pa_sdbm_datum_t *key)
1.1 paf 431: {
1.5 moko 432: pa_status_t status;
1.1 paf 433:
1.5 moko 434: if ((status = pa_sdbm_lock(db, PA_FLOCK_SHARED)) != PA_SUCCESS)
1.1 paf 435: return status;
436:
437: status = getnext(key, db);
438:
1.5 moko 439: (void) pa_sdbm_unlock(db);
1.1 paf 440:
441: return status;
442: }
443:
444: /*
445: * all important binary tree traversal
446: */
1.5 moko 447: static pa_status_t getpage(pa_sdbm_t *db, long hash)
1.1 paf 448: {
449: register int hbit;
450: register long dbit;
451: register long pagb;
1.5 moko 452: pa_status_t status;
1.1 paf 453:
454: dbit = 0;
455: hbit = 0;
456: while (dbit < db->maxbno && getdbit(db, dbit))
457: dbit = 2 * dbit + ((hash & (1 << hbit++)) ? 2 : 1);
458:
459: debug(("dbit: %d...", dbit));
460:
461: db->curbit = dbit;
462: db->hmask = masks[hbit];
463:
464: pagb = hash & db->hmask;
465: /*
466: * see if the block we need is already in memory.
467: * note: this lookaside cache has about 10% hit rate.
468: */
469: if (pagb != db->pagbno) {
470: /*
471: * note: here, we assume a "hole" is read as 0s.
472: * if not, must zero pagbuf first.
473: * ### joe: this assumption was surely never correct? but
474: * ### we make it so in read_from anyway.
475: */
476: if ((status = read_from(db->pagf, db->pagbuf, OFF_PAG(pagb), PBLKSIZ))
1.5 moko 477: != PA_SUCCESS)
1.1 paf 478: return status;
479:
480: if (!chkpage(db->pagbuf))
1.5 moko 481: return PA_ENOSPC; /* ### better error? */
1.1 paf 482: db->pagbno = pagb;
483:
484: debug(("pag read: %d\n", pagb));
485: }
1.5 moko 486: return PA_SUCCESS;
1.1 paf 487: }
488:
1.5 moko 489: static int getdbit(pa_sdbm_t *db, long dbit)
1.1 paf 490: {
491: register long c;
492: register long dirb;
493:
494: c = dbit / BYTESIZ;
495: dirb = c / DBLKSIZ;
496:
497: if (dirb != db->dirbno) {
498: if (read_from(db->dirf, db->dirbuf, OFF_DIR(dirb), DBLKSIZ)
1.5 moko 499: != PA_SUCCESS)
1.1 paf 500: return 0;
501:
502: db->dirbno = dirb;
503:
504: debug(("dir read: %d\n", dirb));
505: }
506:
507: return db->dirbuf[c % DBLKSIZ] & (1 << dbit % BYTESIZ);
508: }
509:
1.5 moko 510: static pa_status_t setdbit(pa_sdbm_t *db, long dbit)
1.1 paf 511: {
512: register long c;
513: register long dirb;
1.5 moko 514: pa_status_t status;
515: pa_off_t off;
1.1 paf 516:
517: c = dbit / BYTESIZ;
518: dirb = c / DBLKSIZ;
519:
520: if (dirb != db->dirbno) {
521: if ((status = read_from(db->dirf, db->dirbuf, OFF_DIR(dirb), DBLKSIZ))
1.5 moko 522: != PA_SUCCESS)
1.1 paf 523: return status;
524:
525: db->dirbno = dirb;
526:
527: debug(("dir read: %d\n", dirb));
528: }
529:
530: db->dirbuf[c % DBLKSIZ] |= (1 << dbit % BYTESIZ);
531:
532: if (dbit >= db->maxbno)
533: db->maxbno += DBLKSIZ * BYTESIZ;
534:
535: off = OFF_DIR(dirb);
1.5 moko 536: if ((status = pa_file_seek(db->dirf, PA_SET, &off)) == PA_SUCCESS)
537: status = pa_file_write_full(db->dirf, db->dirbuf, DBLKSIZ, NULL);
1.1 paf 538:
539: return status;
540: }
541:
542: /*
543: * getnext - get the next key in the page, and if done with
544: * the page, try the next page in sequence
545: */
1.5 moko 546: static pa_status_t getnext(pa_sdbm_datum_t *key, pa_sdbm_t *db)
1.1 paf 547: {
1.5 moko 548: pa_status_t status;
1.1 paf 549: for (;;) {
550: db->keyptr++;
551: *key = getnkey(db->pagbuf, db->keyptr);
552: if (key->dptr != NULL)
1.5 moko 553: return PA_SUCCESS;
1.1 paf 554: /*
555: * we either run out, or there is nothing on this page..
556: * try the next one... If we lost our position on the
557: * file, we will have to seek.
558: */
559: db->keyptr = 0;
560: if (db->pagbno != db->blkptr++) {
1.5 moko 561: pa_off_t off = OFF_PAG(db->blkptr);
562: if ((status = pa_file_seek(db->pagf, PA_SET, &off)
563: != PA_SUCCESS))
1.1 paf 564: return status;
565: }
566:
567: db->pagbno = db->blkptr;
568: /* ### EOF acceptable here too? */
1.5 moko 569: if ((status = pa_file_read_full(db->pagf, db->pagbuf, PBLKSIZ, NULL))
570: != PA_SUCCESS)
1.1 paf 571: return status;
572: if (!chkpage(db->pagbuf))
1.5 moko 573: return PA_EGENERAL; /* ### need better error */
1.1 paf 574: }
575:
576: /* NOTREACHED */
577: }
578:
579:
1.5 moko 580: int pa_sdbm_rdonly(pa_sdbm_t *db)
1.1 paf 581: {
582: /* ### Should we return true if the first lock is a share lock,
1.5 moko 583: * to reflect that pa_sdbm_store and pa_sdbm_delete will fail?
1.1 paf 584: */
585: return (db->flags & SDBM_RDONLY) != 0;
586: }
587:
E-mail: