20.8. Source CodeWe start with the apue_db.h header shown first. This header is included by the library source code and all applications that call the library.For the remainder of this text, we depart from the style of the previous examples in several ways. First, because the source code example is longer than usual, we number the lines. This makes it easier to link the discussion with the corresponding source code. Second, we place the description of the source code immediately below the source code on the same page.This style was inspired by John Lions in his book documenting the UNIX Version 6 operating system source code [Lions 1977, 1996]. It simplifies the task of studying large amounts of source code.Note that we do not bother to number blank lines. Although this departs from the normal behavior of such tools as pr(1), we have nothing interesting to say about blank lines.1 #ifndef _APUE_DB_H 2 #define _APUE_DB_H 3 typedef void * DBHANDLE; 4 DBHANDLE db_open(const char *, int, ...); 5 void db_close(DBHANDLE); 6 char *db_fetch(DBHANDLE, const char *); 7 int db_store(DBHANDLE, const char *, const char *, int); 8 int db_delete(DBHANDLE, const char *); 9 void db_rewind(DBHANDLE); 10 char *db_nextrec(DBHANDLE, char *); 11 /* 12 * Flags for db_store(). 13 */ 14 #define DB_INSERT 1 /* insert new record only */ 15 #define DB_REPLACE 2 /* replace existing record */ 16 #define DB_STORE 3 /* replace or insert */ 17 /* 18 * Implementation limits. 19 */ 20 #define IDXLEN_MIN 6 /* key, sep, start, sep, length, \n */ 21 #define IDXLEN_MAX 1024 /* arbitrary */ 22 #define DATLEN_MIN 2 /* data byte, newline */ 23 #define DATLEN_MAX 1024 /* arbitrary */ 24 #endif /* _APUE_DB_H */
2 #include "apue_db.h" 3 #include <fcntl.h> /* open & db_open flags */ 4 #include <stdarg.h> 5 #include <errno.h> 6 #include <sys/uio.h> /* struct iovec */ 7 /* 8 * Internal index file constants. 9 * These are used to construct records in the 10 * index file and data file. 11 */ 12 #define IDXLEN_SZ 4 /* index record length (ASCII chars) */ 13 #define SEP ':' /* separator char in index record */ 14 #define SPACE ' ' /* space character */ 15 #define NEWLINE '\n' /* newline character */ 16 /* 17 * The following definitions are for hash chains and free 18 * list chain in the index file. 19 */ 20 #define PTR_SZ 6 /* size of ptr field in hash chain */ 21 #define PTR_MAX 999999 /* max file offset = 10**PTR_SZ - 1 */ 22 #define NHASH_DEF 137 /* default hash table size */ 23 #define FREE_OFF 0 /* free list offset in index file */ 24 #define HASH_OFF PTR_SZ /* hash table offset in index file */ 25 typedef unsigned long DBHASH; /* hash values */ 26 typedef unsigned long COUNT; /* unsigned counter */
28 * Library's private representation of the database. 29 */ 30 typedef struct { 31 int idxfd; /* fd for index file */ 32 int datfd; /* fd for data file */ 33 char *idxbuf; /* malloc'ed buffer for index record */ 34 char *datbuf; /* malloc'ed buffer for data record*/ 35 char *name; /* name db was opened under */ 36 off_t idxoff; /* offset in index file of index record */ 37 /* key is at (idxoff + PTR_SZ + IDXLEN_SZ) */ 38 size_t idxlen; /* length of index record */ 39 /* excludes IDXLEN_SZ bytes at front of record */ 40 /* includes newline at end of index record */ 41 off_t datoff; /* offset in data file of data record */ 42 size_t datlen; /* length of data record */ 43 /* includes newline at end */ 44 off_t ptrval; /* contents of chain ptr in index record */ 45 off_t ptroff; /* chain ptr offset pointing to this idx record */ 46 off_t chainoff; /* offset of hash chain for this index record */ 47 off_t hashoff; /* offset in index file of hash table */ 48 DBHASH nhash; /* current hash table size */ 49 COUNT cnt_delok; /* delete OK */ 50 COUNT cnt_delerr; /* delete error */ 51 COUNT cnt_fetchok; /* fetch OK */ 52 COUNT cnt_fetcherr; /* fetch error */ 53 COUNT cnt_nextrec; /* nextrec */ 54 COUNT cnt_stor1; /* store: DB_INSERT, no empty, appended */ 55 COUNT cnt_stor2; /* store: DB_INSERT, found empty, reused */ 56 COUNT cnt_stor3; /* store: DB_REPLACE, diff len, appended */ 57 COUNT cnt_stor4; /* store: DB_REPLACE, same len, overwrote */ 58 COUNT cnt_storerr; /* store error */ 59 } DB;
61 * Internal functions. 62 */ 63 static DB *_db_alloc(int); 64 static void _db_dodelete(DB *); 65 static int _db_find_and_lock(DB *, const char *, int); 66 static int _db_findfree(DB *, int, int); 67 static void _db_free(DB *); 68 static DBHASH _db_hash(DB *, const char *); 69 static char *_db_readdat(DB *); 70 static off_t _db_readidx(DB *, off_t); 71 static off_t _db_readptr(DB *, off_t); 72 static void _db_writedat(DB *, const char *, off_t, int); 73 static void _db_writeidx(DB *, const char *, off_t, int, off_t); 74 static void _db_writeptr(DB *, off_t, off_t); 75 /* 76 * Open or create a database. Same arguments as open(2). 77 */ 78 DBHANDLE 79 db_open(const char *pathname, int oflag, ...) 80 { 81 DB *db; 82 int len, mode; 83 size_t i; 84 char asciiptr[PTR_SZ + 1], 85 hash[(NHASH_DEF + 1) * PTR_SZ + 2]; 86 /* +2 for newline and null */ 87 struct stat statbuff; 88 /* 89 * Allocate a DB structure, and the buffers it needs. 90 */ 91 len = strlen(pathname); 92 if ((db = _db_alloc(len)) == NULL) 93 err_dump("db_open: _db_alloc error for DB"); 95 db->hashoff = HASH_OFF; /* offset in index file of hash table */ 96 strcpy(db->name, pathname); 97 strcat(db->name, ".idx"); 98 if (oflag & O_CREAT) { 99 va_list ap; 100 va_start(ap, oflag); 101 mode = va_arg(ap, int); 102 va_end(ap); 103 /* 104 * Open index file and data file. 105 */ 106 db->idxfd = open(db->name, oflag, mode); 107 strcpy(db->name + len, ".dat"); 108 db->datfd = open(db->name, oflag, mode); 109 } else { 110 /* 111 * Open index file and data file. 112 */ 113 db->idxfd = open(db->name, oflag); 114 strcpy(db->name + len, ".dat"); 115 db->datfd = open(db->name, oflag); 116 } 117 if (db->idxfd < 0 || db->datfd < 0) { 118 _db_free(db); 119 return(NULL); 120 } 122 /* 123 * If the database was created, we have to initialize 124 * it. Write lock the entire file so that we can stat 125 * it, check its size, and initialize it, atomically. 126 */ 127 if (writew_lock(db->idxfd, 0, SEEK_SET, 0) < 0) 128 err_dump("db_open: writew_lock error"); 129 if (fstat(db->idxfd, &statbuff) < 0) 130 err_sys("db_open: fstat error"); 131 if (statbuff.st_size == 0) { 132 /* 133 * We have to build a list of (NHASH_DEF + 1) chain 134 * ptrs with a value of 0. The +1 is for the free 135 * list pointer that precedes the hash table. 136 */ 137 sprintf(asciiptr, "%*d", PTR_SZ, 0);
139 for (i = 0; i < NHASH_DEF + 1; i++) 140 strcat(hash, asciiptr); 141 strcat(hash, "\n"); 142 i = strlen(hash); 143 if (write(db->idxfd, hash, i) != i) 144 err_dump("db_open: index file init write error"); 145 } 146 if (un_lock(db->idxfd, 0, SEEK_SET, 0) < 0) 147 err_dump("db_open: un_lock error"); 148 } 149 db_rewind(db); 150 return(db); 151 } 152 /* 153 * Allocate & initialize a DB structure and its buffers. 154 */ 155 static DB * 156 _db_alloc(int namelen) 157 { 158 DB *db; 159 /* 160 * Use calloc, to initialize the structure to zero. 161 */ 162 if ((db = calloc(1, sizeof(DB))) == NULL) 163 err_dump("_db_alloc: calloc error for DB"); 164 db->idxfd = db->datfd = -1; /* descriptors */ 165 /* 166 * Allocate room for the name. 167 * +5 for ".idx" or ".dat" plus null at end. 168 */ 169 if ((db->name = malloc(namelen + 5)) == NULL) 170 err_dump("_db_alloc: malloc error for name"); 172 * Allocate an index buffer and a data buffer. 173 * +2 for newline and null at end. 174 */ 175 if ((db->idxbuf = malloc(IDXLEN_MAX + 2)) == NULL) 176 err_dump("_db_alloc: malloc error for index buffer"); 177 if ((db->datbuf = malloc(DATLEN_MAX + 2)) == NULL) 178 err_dump("_db_alloc: malloc error for data buffer"); 179 return(db); 180 } 181 /* 182 * Relinquish access to the database. 183 */ 184 void 185 db_close(DBHANDLE h) 186 { 187 _db_free((DB *)h); /* closes fds, free buffers & struct */ 188 } 189 /* 190 * Free up a DB structure, and all the malloc'ed buffers it 191 * may point to. Also close the file descriptors if still open. 192 */ 193 static void 194 _db_free(DB *db) 195 { 196 if (db->idxfd >= 0) 197 close(db->idxfd); 198 if (db->datfd >= 0) 199 close(db->datfd); 201 free(db->idxbuf); 202 if (db->datbuf != NULL) 203 free(db->datbuf); 204 if (db->name != NULL) 205 free(db->name); 206 free(db); 207 } 208 /* 209 * Fetch a record. Return a pointer to the null-terminated data. 210 */ 211 char * 212 db_fetch(DBHANDLE h, const char *key) 213 { 214 DB *db = h; 215 char *ptr; 216 if (_db_find_and_lock(db, key, 0) < 0) { 217 ptr = NULL; /* error, record not found */ 218 db->cnt_fetcherr++; 219 } else { 220 ptr = _db_readdat(db); /* return pointer to data */ 221 db->cnt_fetchok++; 222 } 223 /* 224 * Unlock the hash chain that _db_find_and_lock locked. 225 */ 226 if (un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) 227 err_dump("db_fetch: un_lock error"); 228 return(ptr); 229 } 231 * Find the specified record. Called by db_delete, db_fetch, 232 * and db_store. Returns with the hash chain locked. 233 */ 234 static int 235 _db_find_and_lock(DB *db, const char *key, int writelock) 236 { 237 off_t offset, nextoffset; 238 /* 239 * Calculate the hash value for this key, then calculate the 240 * byte offset of corresponding chain ptr in hash table. 241 * This is where our search starts. First we calculate the 242 * offset in the hash table for this key. 243 */ 244 db->chainoff = (_db_hash(db, key) * PTR_SZ) + db->hashoff; 245 db->ptroff = db->chainoff; 246 /* 247 * We lock the hash chain here. The caller must unlock it 248 * when done. Note we lock and unlock only the first byte. 249 */ 250 if (writelock) { 251 if (writew_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) 252 err_dump("_db_find_and_lock: writew_lock error"); 253 } else { 254 if (readw_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) 255 err_dump("_db_find_and_lock: readw_lock error"); 256 } 257 /* 258 * Get the offset in the index file of first record 259 * on the hash chain (can be 0). 260 */ 261 offset = _db_readptr(db, db->ptroff); 263 nextoffset = _db_readidx(db, offset); 264 if (strcmp(db->idxbuf, key) == 0) 265 break; /* found a match */ 266 db->ptroff = offset; /* offset of this (unequal) record */ 267 offset = nextoffset; /* next one to compare */ 268 } 269 /* 270 * offset == 0 on error (record not found). 271 */ 272 return(offset == 0 ? -1 : 0); 273 } 274 /* 275 * Calculate the hash value for a key. 276 */ 277 static DBHASH 278 _db_hash(DB *db, const char *key) 279 { 280 DBHASH hval = 0; 281 char c; 282 int i; 283 for (i = 1; (c = *key++) != 0; i++) 284 hval += c * i; /* ascii char times its 1-based index */ 285 return(hval % db->nhash); 286 }
288 * Read a chain ptr field from anywhere in the index file: 289 * the free list pointer, a hash table chain ptr, or an 290 * index record chain ptr. 291 */ 292 static off_t 293 _db_readptr(DB *db, off_t offset) 294 { 295 char asciiptr[PTR_SZ + 1]; 296 if (lseek(db->idxfd, offset, SEEK_SET) == -1) 297 err_dump("_db_readptr: lseek error to ptr field"); 298 if (read(db->idxfd, asciiptr, PTR_SZ) != PTR_SZ) 299 err_dump("_db_readptr: read error of ptr field"); 300 asciiptr[PTR_SZ] = 0; /* null terminate */ 301 return(atol(asciiptr)); 302 } 303 /* 304 * Read the next index record. We start at the specified offset 305 * in the index file. We read the index record into db->idxbuf 306 * and replace the separators with null bytes. If all is OK we 307 * set db->datoff and db->datlen to the offset and length of the 308 * corresponding data record in the data file. 309 */ 310 static off_t 311 _db_readidx(DB *db, off_t offset) 312 { 313 ssize_t i; 314 char *ptr1, *ptr2; 315 char asciiptr[PTR_SZ + 1], asciilen[IDXLEN_SZ + 1]; 316 struct iovec iov[2]; 318 * Position index file and record the offset. db_nextrec 319 * calls us with offset==0, meaning read from current offset. 320 * We still need to call lseek to record the current offset. 321 */ 322 if ((db->idxoff = lseek(db->idxfd, offset, 323 offset == 0 ? SEEK_CUR : SEEK_SET)) == -1) 324 err_dump("_db_readidx: lseek error"); 325 /* 326 * Read the ascii chain ptr and the ascii length at 327 * the front of the index record. This tells us the 328 * remaining size of the index record. 329 */ 330 iov[0].iov_base = asciiptr; 331 iov[0].iov_len = PTR_SZ; 332 iov[1].iov_base = asciilen; 333 iov[1].iov_len = IDXLEN_SZ; 334 if ((i = readv(db->idxfd, &iov[0], 2)) != PTR_SZ + IDXLEN_SZ) { 335 if (i == 0 && offset == 0) 336 return(-1); /* EOF for db_nextrec */ 337 err_dump("_db_readidx: readv error of index record"); 338 } 339 /* 340 * This is our return value; always >= 0. 341 */ 342 asciiptr[PTR_SZ] = 0; /* null terminate */ 343 db->ptrval = atol(asciiptr); /* offset of next key in chain */ 344 asciilen[IDXLEN_SZ] = 0; /* null terminate */ 345 if ((db->idxlen = atoi(asciilen)) < IDXLEN_MIN || 346 db->idxlen > IDXLEN_MAX) 347 err_dump("_db_readidx: invalid length"); 349 * Now read the actual index record. We read it into the key 350 * buffer that we malloced when we opened the database. 351 */ 352 if ((i = read(db->idxfd, db->idxbuf, db->idxlen)) != db->idxlen) 353 err_dump("_db_readidx: read error of index record"); 354 if (db->idxbuf[db->idxlen-1] != NEWLINE) /* sanity check */ 355 err_dump("_db_readidx: missing newline"); 356 db->idxbuf[db->idxlen-1] = 0; /* replace newline with null */ 357 /* 358 * Find the separators in the index record. 359 */ 360 if ((ptr1 = strchr(db->idxbuf, SEP)) == NULL) 361 err_dump("_db_readidx: missing first separator"); 362 *ptr1++ = 0; /* replace SEP with null */ 363 if ((ptr2 = strchr(ptr1, SEP)) == NULL) 364 err_dump("_db_readidx: missing second separator"); 365 *ptr2++ = 0; /* replace SEP with null */ 366 if (strchr(ptr2, SEP) != NULL) 367 err_dump("_db_readidx: too many separators"); 368 /* 369 * Get the starting offset and length of the data record. 370 */ 371 if ((db->datoff = atol(ptr1)) < 0) 372 err_dump("_db_readidx: starting offset < 0"); 373 if ((db->datlen = atol(ptr2)) <= 0 || db->datlen > DATLEN_MAX) 374 err_dump("_db_readidx: invalid length"); 375 return(db->ptrval); /* return offset of next key in chain */ 376 } 378 * Read the current data record into the data buffer. 379 * Return a pointer to the null-terminated data buffer. 380 */ 381 static char * 382 _db_readdat(DB *db) 383 { 384 if (lseek(db->datfd, db->datoff, SEEK_SET) == -1) 385 err_dump("_db_readdat: lseek error"); 386 if (read(db->datfd, db->datbuf, db->datlen) != db->datlen) 387 err_dump("_db_readdat: read error"); 388 if (db->datbuf[db->datlen-1] != NEWLINE) /* sanity check */ 389 err_dump("_db_readdat: missing newline"); 390 db->datbuf[db->datlen-1] = 0; /* replace newline with null */ 391 return(db->datbuf); /* return pointer to data record */ 392 } 393 /* 394 * Delete the specified record. 395 */ 396 int 397 db_delete(DBHANDLE h, const char *key) 398 { 399 DB *db = h; 400 int rc = 0; /* assume record will be found */ 401 if (_db_find_and_lock(db, key, 1) == 0) { 402 _db_dodelete(db); 403 db->cnt_delok++; 404 } else { 405 rc = -1; /* not found */ 406 db->cnt_delerr++; 407 } 408 if (un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) 409 err_dump("db_delete: un_lock error"); 410 return(rc); 411 } 413 * Delete the current record specified by the DB structure. 414 * This function is called by db_delete and db_store, after 415 * the record has been located by _db_find_and_lock. 416 */ 417 static void 418 _db_dodelete(DB *db) 419 { 420 int i; 421 char *ptr; 422 off_t freeptr, saveptr; 423 /* 424 * Set data buffer and key to all blanks. 425 */ 426 for (ptr = db->datbuf, i = 0; i < db->datlen - 1; i++) 427 *ptr++ = SPACE; 428 *ptr = 0; /* null terminate for _db_writedat */ 429 ptr = db->idxbuf; 430 while (*ptr) 431 *ptr++ = SPACE; 432 /* 433 * We have to lock the free list. 434 */ 435 if (writew_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0) 436 err_dump("_db_dodelete: writew_lock error"); 437 /* 438 * Write the data record with all blanks. 439 */ 440 _db_writedat(db, db->datbuf, db->datoff, SEEK_SET); 442 * Read the free list pointer. Its value becomes the 443 * chain ptr field of the deleted index record. This means 444 * the deleted record becomes the head of the free list. 445 */ 446 freeptr = _db_readptr(db, FREE_OFF); 447 /* 448 * Save the contents of index record chain ptr, 449 * before it's rewritten by _db_writeidx. 450 */ 451 saveptr = db->ptrval; 452 /* 453 * Rewrite the index record. This also rewrites the length 454 * of the index record, the data offset, and the data length, 455 * none of which has changed, but that's OK. 456 */ 457 _db_writeidx(db, db->idxbuf, db->idxoff, SEEK_SET, freeptr); 458 /* 459 * Write the new free list pointer. 460 */ 461 _db_writeptr(db, FREE_OFF, db->idxoff); 462 /* 463 * Rewrite the chain ptr that pointed to this record being 464 * deleted. Recall that _db_find_and_lock sets db->ptroff to 465 * point to this chain ptr. We set this chain ptr to the 466 * contents of the deleted record's chain ptr, saveptr. 467 */ 468 _db_writeptr(db, db->ptroff, saveptr); 469 if (un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0) 470 err_dump("_db_dodelete: un_lock error"); 471 }
473 * Write a data record. Called by _db_dodelete (to write 474 * the record with blanks) and db_store. 475 */ 476 static void 477 _db_writedat(DB *db, const char *data, off_t offset, int whence) 478 { 479 struct iovec iov[2]; 480 static char newline = NEWLINE; 481 /* 482 * If we're appending, we have to lock before doing the lseek 483 * and write to make the two an atomic operation. If we're 484 * overwriting an existing record, we don't have to lock. 485 */ 486 if (whence == SEEK_END) /* we're appending, lock entire file */ 487 if (writew_lock(db->datfd, 0, SEEK_SET, 0) < 0) 488 err_dump("_db_writedat: writew_lock error"); 489 if ((db->datoff = lseek(db->datfd, offset, whence)) == -1) 490 err_dump("_db_writedat: lseek error"); 491 db->datlen = strlen(data) + 1; /* datlen includes newline */ 492 iov[0].iov_base = (char *) data; 493 iov[0].iov_len = db->datlen - 1; 494 iov[1].iov_base = &newline; 495 iov[1].iov_len = 1; 496 if (writev(db->datfd, &iov[0], 2) != db->datlen) 497 err_dump("_db_writedat: writev error of data record"); 498 if (whence == SEEK_END) 499 if (un_lock(db->datfd, 0, SEEK_SET, 0) < 0) 500 err_dump("_db_writedat: un_lock error"); 501 } 503 * Write an index record. _db_writedat is called before 504 * this function to set the datoff and datlen fields in the 505 * DB structure, which we need to write the index record. 506 */ 507 static void 508 _db_writeidx(DB *db, const char *key, 509 off_t offset, int whence, off_t ptrval) 510 { 511 struct iovec iov[2]; 512 char asciiptrlen[PTR_SZ + IDXLEN_SZ +1]; 513 int len; 514 char *fmt; 515 if ((db->ptrval = ptrval) < 0 || ptrval > PTR_MAX) 516 err_quit("_db_writeidx: invalid ptr: %d", ptrval); 517 if (sizeof(off_t) == sizeof(long long)) 518 fmt = "%s%c%lld%c%d\n"; 519 else 520 fmt = "%s%c%ld%c%d\n"; 521 sprintf(db->idxbuf, fmt, key, SEP, db->datoff, SEP, db->datlen); 522 if ((len = strlen(db->idxbuf)) < IDXLEN_MIN || len > IDXLEN_MAX) 523 err_dump("_db_writeidx: invalid length"); 524 sprintf(asciiptrlen, "%*ld%*d", PTR_SZ, ptrval, IDXLEN_SZ, len); 525 /* 526 * If we're appending, we have to lock before doing the lseek 527 * and write to make the two an atomic operation. If we're 528 * overwriting an existing record, we don't have to lock. 529 */ 530 if (whence == SEEK_END) /* we're appending */ 531 if (writew_lock(db->idxfd, ((db->nhash+1)*PTR_SZ)+1, 532 SEEK_SET, 0) < 0) 533 err_dump("_db_writeidx: writew_lock error"); 535 * Position the index file and record the offset. 536 */ 537 if ((db->idxoff = lseek(db->idxfd, offset, whence)) == -1) 538 err_dump("_db_writeidx: lseek error"); 539 iov[0].iov_base = asciiptrlen; 540 iov[0].iov_len = PTR_SZ + IDXLEN_SZ; 541 iov[1].iov_base = db->idxbuf; 542 iov[1].iov_len = len; 543 if (writev(db->idxfd, &iov[0], 2) != PTR_SZ + IDXLEN_SZ + len) 544 err_dump("_db_writeidx: writev error of index record"); 545 if (whence == SEEK_END) 546 if (un_lock(db->idxfd, ((db->nhash+1)*PTR_SZ)+1, 547 SEEK_SET, 0) < 0) 548 err_dump("_db_writeidx: un_lock error"); 549 } 550 /* 551 * Write a chain ptr field somewhere in the index file: 552 * the free list, the hash table, or in an index record. 553 */ 554 static void 555 _db_writeptr(DB *db, off_t offset, off_t ptrval) 556 { 557 char asciiptr[PTR_SZ + 1]; 558 if (ptrval < 0 || ptrval > PTR_MAX) 559 err_quit("_db_writeptr: invalid ptr: %d", ptrval); 560 sprintf(asciiptr, "%*ld", PTR_SZ, ptrval); 561 if (lseek(db->idxfd, offset, SEEK_SET) == -1) 562 err_dump("_db_writeptr: lseek error to ptr field"); 563 if (write(db->idxfd, asciiptr, PTR_SZ) != PTR_SZ) 564 err_dump("_db_writeptr: write error of ptr field"); 565 } 567 * Store a record in the database. Return 0 if OK, 1 if record 568 * exists and DB_INSERT specified, -1 on error. 569 */ 570 int 571 db_store(DBHANDLE h, const char *key, const char *data, int flag) 572 { 573 DB *db = h; 574 int rc, keylen, datlen; 575 off_t ptrval; 576 if (flag != DB_INSERT && flag != DB_REPLACE && 577 flag != DB_STORE) { 578 errno = EINVAL; 579 return(-1); 580 } 581 keylen = strlen(key); 582 datlen = strlen(data) + 1; /* +1 for newline at end */ 583 if (datlen < DATLEN_MIN || datlen > DATLEN_MAX) 584 err_dump("db_store: invalid data length"); 585 /* 586 * _db_find_and_lock calculates which hash table this new record 587 * goes into (db->chainoff), regardless of whether it already 588 * exists or not. The following calls to _db_writeptr change the 589 * hash table entry for this chain to point to the new record. 590 * The new record is added to the front of the hash chain. 591 */ 592 if (_db_find_and_lock(db, key, 1) < 0) { /* record not found */ 593 if (flag == DB_REPLACE) { 594 rc = -1; 595 db->cnt_storerr++; 596 errno = ENOENT; /* error, record does not exist */ 597 goto doreturn; 598 } 600 * _db_find_and_lock locked the hash chain for us; read 601 * the chain ptr to the first index record on hash chain. 602 */ 603 ptrval = _db_readptr(db, db->chainoff); 604 if (_db_findfree(db, keylen, datlen) < 0) { 605 /* 606 * Can't find an empty record big enough. Append the 607 * new record to the ends of the index and data files. 608 */ 609 _db_writedat(db, data, 0, SEEK_END); 610 _db_writeidx(db, key, 0, SEEK_END, ptrval); 611 /* 612 * db->idxoff was set by _db_writeidx. The new 613 * record goes to the front of the hash chain. 614 */ 615 _db_writeptr(db, db->chainoff, db->idxoff); 616 db->cnt_stor1++; 617 } else { 618 /* 619 * Reuse an empty record. _db_findfree removed it from 620 * the free list and set both db->datoff and db->idxoff. 621 * Reused record goes to the front of the hash chain. 622 */ 623 _db_writedat(db, data, db->datoff, SEEK_SET); 624 _db_writeidx(db, key, db->idxoff, SEEK_SET, ptrval); 625 _db_writeptr(db, db->chainoff, db->idxoff); 626 db->cnt_stor2++; 627 } 629 if (flag == DB_INSERT) { 630 rc = 1; /* error, record already in db */ 631 db->cnt_storerr++; 632 goto doreturn; 633 } 634 /* 635 * We are replacing an existing record. We know the new 636 * key equals the existing key, but we need to check if 637 * the data records are the same size. 638 */ 639 if (datlen != db->datlen) { 640 _db_dodelete(db); /* delete the existing record */ 641 /* 642 * Reread the chain ptr in the hash table 643 * (it may change with the deletion). 644 */ 645 ptrval = _db_readptr(db, db->chainoff); 646 /* 647 * Append new index and data records to end of files. 648 */ 649 _db_writedat(db, data, 0, SEEK_END); 650 _db_writeidx(db, key, 0, SEEK_END, ptrval); 651 /* 652 * New record goes to the front of the hash chain. 653 */ 654 _db_writeptr(db, db->chainoff, db->idxoff); 655 db->cnt_stor3++; 656 } else { 658 * Same size data, just replace data record. 659 */ 660 _db_writedat(db, data, db->datoff, SEEK_SET); 661 db->cnt_stor4++; 662 } 663 } 664 rc = 0; /* OK */ 665 doreturn: /* unlock hash chain locked by _db_find_and_lock */ 666 if (un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0) 667 err_dump("db_store: un_lock error"); 668 return(rc); 669 } 670 /* 671 * Try to find a free index record and accompanying data record 672 * of the correct sizes. We're only called by db_store. 673 */ 674 static int 675 _db_findfree(DB *db, int keylen, int datlen) 676 { 677 int rc; 678 off_t offset, nextoffset, saveoffset; 679 /* 680 * Lock the free list. 681 */ 682 if (writew_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0) 683 err_dump("_db_findfree: writew_lock error"); 684 /* 685 * Read the free list pointer. 686 */ 687 saveoffset = FREE_OFF; 688 offset = _db_readptr(db, saveoffset); 690 nextoffset = _db_readidx(db, offset); 691 if (strlen(db->idxbuf) == keylen && db->datlen == datlen) 692 break; /* found a match */ 693 saveoffset = offset; 694 offset = nextoffset; 695 } 696 if (offset == 0) { 697 rc = -1; /* no match found */ 698 } else { 699 /* 700 * Found a free record with matching sizes. 701 * The index record was read in by _db_readidx above, 702 * which sets db->ptrval. Also, saveoffset points to 703 * the chain ptr that pointed to this empty record on 704 * the free list. We set this chain ptr to db->ptrval, 705 * which removes the empty record from the free list. 706 */ 707 _db_writeptr(db, saveoffset, db->ptrval); 708 rc = 0; 709 /* 710 * Notice also that _db_readidx set both db->idxoff 711 * and db->datoff. This is used by the caller, db_store, 712 * to write the new index record and data record. 713 */ 714 } 715 /* 716 * Unlock the free list. 717 */ 718 if (un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0) 719 err_dump("_db_findfree: un_lock error"); 720 return(rc); 721 }
723 * Rewind the index file for db_nextrec. 724 * Automatically called by db_open. 725 * Must be called before first db_nextrec. 726 */ 727 void 728 db_rewind(DBHANDLE h) 729 { 730 DB *db = h; 731 off_t offset; 732 offset = (db->nhash + 1) * PTR_SZ; /* +1 for free list ptr */ 733 /* 734 * We're just setting the file offset for this process 735 * to the start of the index records; no need to lock. 736 * +1 below for newline at end of hash table. 737 */ 738 if ((db->idxoff = lseek(db->idxfd, offset+1, SEEK_SET)) == -1) 739 err_dump("db_rewind: lseek error"); 740 } 741 /* 742 * Return the next sequential record. 743 * We just step our way through the index file, ignoring deleted 744 * records. db_rewind must be called before this function is 745 * called the first time. 746 */ 747 char * 748 db_nextrec(DBHANDLE h, char *key) 749 { 750 DB *db = h; 751 char c; 752 char *ptr;
754 * We read lock the free list so that we don't read 755 * a record in the middle of its being deleted. 756 */ 757 if (readw_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0) 758 err_dump("db_nextrec: readw_lock error"); 759 do { 760 /* 761 * Read next sequential index record. 762 */ 763 if (_db_readidx(db, 0) < 0) { 764 ptr = NULL; /* end of index file, EOF */ 765 goto doreturn; 766 } 767 /* 768 * Check if key is all blank (empty record). 769 */ 770 ptr = db->idxbuf; 771 while ((c = *ptr++) != 0 && c == SPACE) 772 ; /* skip until null byte or nonblank */ 773 } while (c == 0); /* loop until a nonblank key is found */ 774 if (key != NULL) 775 strcpy(key, db->idxbuf); /* return key */ 776 ptr = _db_readdat(db); /* return pointer to data buffer */ 777 db->cnt_nextrec++; 778 doreturn: 779 if (un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0) 780 err_dump("db_nextrec: un_lock error"); 781 return(ptr); 782 } The normal use of db_rewind and db_nextrec is in a loop of the formdb_rewind(db); while ((ptr = db_nextrec(db, key)) != NULL) { /* process record */ } As we warned earlier, there is no order to the returned records; they are not in key order.If the database is being modified while db_nextrec is called from a loop, the records returned by db_nextrec are simply a snapshot of a changing database at some point in time. db_nextrec always returns a "correct" record when it is called; that is, it won't return a record that was deleted. But it is possible for a record returned by db_nextrec to be deleted immediately after db_nextrec returns. Similarly, if a deleted record is reused right after db_nextrec skips over the deleted record, we won't see that new record unless we rewind the database and go through it again. If it's important to obtain an accurate "frozen" snapshot of the database using db_nextrec, there must be no insertions or deletions going on at the same time.Look at the locking used by db_nextrec. We're not going through any hash chain, and we can't determine the hash chain that a record belongs on. Therefore, it is possible for an index record to be in the process of being deleted when db_nextrec is reading the record. To prevent this, db_nextrec read-locks the free list, thereby avoiding any interaction with _db_dodelete and _db_findfree.Before we conclude our study of the db.c source file, we need to describe the locking when new index records or data records are appended to the end of the file. In cases 1 and 3, db_store calls both _db_writeidx and _db_writedat with a third argument of 0 and a fourth argument of SEEK_END. This fourth argument is the flag to these two functions, indicating that the new record is being appended to the file. The technique used by _db_writeidx is to write-lock the index file from the end of the hash chain to the end of file. This won't interfere with any other readers or writers of the database (since they will lock a hash chain), but it does prevent other callers of db_store from trying to append at the same time. The technique used by _db_writedat is to write-lock the entire data file. Again, this won't interfere with other readers or writers of the database (since they don't even try to lock the data file), but it does prevent other callers of db_store from trying to append to the data file at the same time. (See Exercise 20.3.) |