1 /* $NetBSD: dict_sdbm.c,v 1.1.1.1 2009/06/23 10:08:59 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_sdbm 3 6 /* SUMMARY 7 /* dictionary manager interface to SDBM files 8 /* SYNOPSIS 9 /* #include <dict_sdbm.h> 10 /* 11 /* DICT *dict_sdbm_open(path, open_flags, dict_flags) 12 /* const char *name; 13 /* const char *path; 14 /* int open_flags; 15 /* int dict_flags; 16 /* DESCRIPTION 17 /* dict_sdbm_open() opens the named SDBM database and makes it available 18 /* via the generic interface described in dict_open(3). 19 /* DIAGNOSTICS 20 /* Fatal errors: cannot open file, file write error, out of memory. 21 /* SEE ALSO 22 /* dict(3) generic dictionary manager 23 /* sdbm(3) data base subroutines 24 /* LICENSE 25 /* .ad 26 /* .fi 27 /* The Secure Mailer license must be distributed with this software. 28 /* AUTHOR(S) 29 /* Wietse Venema 30 /* IBM T.J. Watson Research 31 /* P.O. Box 704 32 /* Yorktown Heights, NY 10598, USA 33 /*--*/ 34 35 #include "sys_defs.h" 36 37 /* System library. */ 38 39 #include <sys/stat.h> 40 #include <string.h> 41 #include <unistd.h> 42 #ifdef HAS_SDBM 43 #include <sdbm.h> 44 #endif 45 46 /* Utility library. */ 47 48 #include <msg.h> 49 #include <mymalloc.h> 50 #include <htable.h> 51 #include <iostuff.h> 52 #include <vstring.h> 53 #include <myflock.h> 54 #include <stringops.h> 55 #include <dict.h> 56 #include <dict_sdbm.h> 57 58 #ifdef HAS_SDBM 59 60 /* Application-specific. */ 61 62 typedef struct { 63 DICT dict; /* generic members */ 64 SDBM *dbm; /* open database */ 65 VSTRING *key_buf; /* key buffer */ 66 VSTRING *val_buf; /* result buffer */ 67 } DICT_SDBM; 68 69 #define SCOPY(buf, data, size) \ 70 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) 71 72 /* dict_sdbm_lookup - find database entry */ 73 74 static const char *dict_sdbm_lookup(DICT *dict, const char *name) 75 { 76 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 77 datum dbm_key; 78 datum dbm_value; 79 const char *result = 0; 80 81 /* 82 * Sanity check. 83 */ 84 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 85 msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 86 87 dict_errno = 0; 88 89 /* 90 * Optionally fold the key. 91 */ 92 if (dict->flags & DICT_FLAG_FOLD_FIX) { 93 if (dict->fold_buf == 0) 94 dict->fold_buf = vstring_alloc(10); 95 vstring_strcpy(dict->fold_buf, name); 96 name = lowercase(vstring_str(dict->fold_buf)); 97 } 98 99 /* 100 * Acquire an exclusive lock. 101 */ 102 if ((dict->flags & DICT_FLAG_LOCK) 103 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 104 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); 105 106 /* 107 * See if this DBM file was written with one null byte appended to key 108 * and value. 109 */ 110 if (dict->flags & DICT_FLAG_TRY1NULL) { 111 dbm_key.dptr = (void *) name; 112 dbm_key.dsize = strlen(name) + 1; 113 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); 114 if (dbm_value.dptr != 0) { 115 dict->flags &= ~DICT_FLAG_TRY0NULL; 116 result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); 117 } 118 } 119 120 /* 121 * See if this DBM file was written with no null byte appended to key and 122 * value. 123 */ 124 if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 125 dbm_key.dptr = (void *) name; 126 dbm_key.dsize = strlen(name); 127 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); 128 if (dbm_value.dptr != 0) { 129 dict->flags &= ~DICT_FLAG_TRY1NULL; 130 result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); 131 } 132 } 133 134 /* 135 * Release the exclusive lock. 136 */ 137 if ((dict->flags & DICT_FLAG_LOCK) 138 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 139 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); 140 141 return (result); 142 } 143 144 /* dict_sdbm_update - add or update database entry */ 145 146 static void dict_sdbm_update(DICT *dict, const char *name, const char *value) 147 { 148 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 149 datum dbm_key; 150 datum dbm_value; 151 int status; 152 153 /* 154 * Sanity check. 155 */ 156 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 157 msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 158 159 /* 160 * Optionally fold the key. 161 */ 162 if (dict->flags & DICT_FLAG_FOLD_FIX) { 163 if (dict->fold_buf == 0) 164 dict->fold_buf = vstring_alloc(10); 165 vstring_strcpy(dict->fold_buf, name); 166 name = lowercase(vstring_str(dict->fold_buf)); 167 } 168 dbm_key.dptr = (void *) name; 169 dbm_value.dptr = (void *) value; 170 dbm_key.dsize = strlen(name); 171 dbm_value.dsize = strlen(value); 172 173 /* 174 * If undecided about appending a null byte to key and value, choose a 175 * default depending on the platform. 176 */ 177 if ((dict->flags & DICT_FLAG_TRY1NULL) 178 && (dict->flags & DICT_FLAG_TRY0NULL)) { 179 #ifdef DBM_NO_TRAILING_NULL 180 dict->flags &= ~DICT_FLAG_TRY1NULL; 181 #else 182 dict->flags &= ~DICT_FLAG_TRY0NULL; 183 #endif 184 } 185 186 /* 187 * Optionally append a null byte to key and value. 188 */ 189 if (dict->flags & DICT_FLAG_TRY1NULL) { 190 dbm_key.dsize++; 191 dbm_value.dsize++; 192 } 193 194 /* 195 * Acquire an exclusive lock. 196 */ 197 if ((dict->flags & DICT_FLAG_LOCK) 198 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 199 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); 200 201 /* 202 * Do the update. 203 */ 204 if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value, 205 (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) 206 msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name); 207 if (status) { 208 if (dict->flags & DICT_FLAG_DUP_IGNORE) 209 /* void */ ; 210 else if (dict->flags & DICT_FLAG_DUP_WARN) 211 msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name); 212 else 213 msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name); 214 } 215 216 /* 217 * Release the exclusive lock. 218 */ 219 if ((dict->flags & DICT_FLAG_LOCK) 220 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 221 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); 222 } 223 224 /* dict_sdbm_delete - delete one entry from the dictionary */ 225 226 static int dict_sdbm_delete(DICT *dict, const char *name) 227 { 228 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 229 datum dbm_key; 230 int status = 1; 231 232 /* 233 * Sanity check. 234 */ 235 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 236 msg_panic("dict_sdbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 237 238 /* 239 * Optionally fold the key. 240 */ 241 if (dict->flags & DICT_FLAG_FOLD_FIX) { 242 if (dict->fold_buf == 0) 243 dict->fold_buf = vstring_alloc(10); 244 vstring_strcpy(dict->fold_buf, name); 245 name = lowercase(vstring_str(dict->fold_buf)); 246 } 247 248 /* 249 * Acquire an exclusive lock. 250 */ 251 if ((dict->flags & DICT_FLAG_LOCK) 252 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 253 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); 254 255 /* 256 * See if this DBM file was written with one null byte appended to key 257 * and value. 258 */ 259 if (dict->flags & DICT_FLAG_TRY1NULL) { 260 dbm_key.dptr = (void *) name; 261 dbm_key.dsize = strlen(name) + 1; 262 sdbm_clearerr(dict_sdbm->dbm); 263 if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { 264 if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */ 265 msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name); 266 status = 1; /* not found */ 267 } else { 268 dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ 269 } 270 } 271 272 /* 273 * See if this DBM file was written with no null byte appended to key and 274 * value. 275 */ 276 if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 277 dbm_key.dptr = (void *) name; 278 dbm_key.dsize = strlen(name); 279 sdbm_clearerr(dict_sdbm->dbm); 280 if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { 281 if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */ 282 msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name); 283 status = 1; /* not found */ 284 } else { 285 dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ 286 } 287 } 288 289 /* 290 * Release the exclusive lock. 291 */ 292 if ((dict->flags & DICT_FLAG_LOCK) 293 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 294 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); 295 296 return (status); 297 } 298 299 /* traverse the dictionary */ 300 301 static int dict_sdbm_sequence(DICT *dict, const int function, 302 const char **key, const char **value) 303 { 304 const char *myname = "dict_sdbm_sequence"; 305 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 306 datum dbm_key; 307 datum dbm_value; 308 309 /* 310 * Acquire a shared lock. 311 */ 312 if ((dict->flags & DICT_FLAG_LOCK) 313 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 314 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); 315 316 /* 317 * Determine and execute the seek function. It returns the key. 318 */ 319 sdbm_clearerr(dict_sdbm->dbm); 320 switch (function) { 321 case DICT_SEQ_FUN_FIRST: 322 dbm_key = sdbm_firstkey(dict_sdbm->dbm); 323 break; 324 case DICT_SEQ_FUN_NEXT: 325 dbm_key = sdbm_nextkey(dict_sdbm->dbm); 326 break; 327 default: 328 msg_panic("%s: invalid function: %d", myname, function); 329 } 330 331 if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { 332 333 /* 334 * Copy the key so that it is guaranteed null terminated. 335 */ 336 *key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize); 337 338 /* 339 * Fetch the corresponding value. 340 */ 341 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); 342 343 if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { 344 345 /* 346 * Copy the value so that it is guaranteed null terminated. 347 */ 348 *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); 349 } else { 350 351 /* 352 * Determine if we have hit the last record or an error 353 * condition. 354 */ 355 if (sdbm_error(dict_sdbm->dbm)) 356 msg_fatal("error seeking %s: %m", dict_sdbm->dict.name); 357 return (1); /* no error: eof/not found 358 * (should not happen!) */ 359 } 360 } else { 361 362 /* 363 * Determine if we have hit the last record or an error condition. 364 */ 365 if (sdbm_error(dict_sdbm->dbm)) 366 msg_fatal("error seeking %s: %m", dict_sdbm->dict.name); 367 return (1); /* no error: eof/not found */ 368 } 369 370 /* 371 * Release the shared lock. 372 */ 373 if ((dict->flags & DICT_FLAG_LOCK) 374 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 375 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); 376 377 return (0); 378 } 379 380 /* dict_sdbm_close - disassociate from data base */ 381 382 static void dict_sdbm_close(DICT *dict) 383 { 384 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 385 386 sdbm_close(dict_sdbm->dbm); 387 if (dict_sdbm->key_buf) 388 vstring_free(dict_sdbm->key_buf); 389 if (dict_sdbm->val_buf) 390 vstring_free(dict_sdbm->val_buf); 391 if (dict->fold_buf) 392 vstring_free(dict->fold_buf); 393 dict_free(dict); 394 } 395 396 /* dict_sdbm_open - open SDBM data base */ 397 398 DICT *dict_sdbm_open(const char *path, int open_flags, int dict_flags) 399 { 400 DICT_SDBM *dict_sdbm; 401 struct stat st; 402 SDBM *dbm; 403 char *dbm_path; 404 int lock_fd; 405 406 /* 407 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in 408 * the time domain) locking while accessing individual database records. 409 * 410 * Programs such as postmap/postalias use their own large-grained (in the 411 * time domain) locks while rewriting the entire file. 412 */ 413 if (dict_flags & DICT_FLAG_LOCK) { 414 dbm_path = concatenate(path, ".dir", (char *) 0); 415 if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) 416 msg_fatal("open database %s: %m", dbm_path); 417 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 418 msg_fatal("shared-lock database %s for open: %m", dbm_path); 419 } 420 421 /* 422 * XXX sdbm_open() has no const in prototype. 423 */ 424 if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0) 425 msg_fatal("open database %s.{dir,pag}: %m", path); 426 427 if (dict_flags & DICT_FLAG_LOCK) { 428 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 429 msg_fatal("unlock database %s for open: %m", dbm_path); 430 if (close(lock_fd) < 0) 431 msg_fatal("close database %s: %m", dbm_path); 432 } 433 dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm)); 434 dict_sdbm->dict.lookup = dict_sdbm_lookup; 435 dict_sdbm->dict.update = dict_sdbm_update; 436 dict_sdbm->dict.delete = dict_sdbm_delete; 437 dict_sdbm->dict.sequence = dict_sdbm_sequence; 438 dict_sdbm->dict.close = dict_sdbm_close; 439 dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm); 440 dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm); 441 if (fstat(dict_sdbm->dict.stat_fd, &st) < 0) 442 msg_fatal("dict_sdbm_open: fstat: %m"); 443 dict_sdbm->dict.mtime = st.st_mtime; 444 445 /* 446 * Warn if the source file is newer than the indexed file, except when 447 * the source file changed only seconds ago. 448 */ 449 if ((dict_flags & DICT_FLAG_LOCK) != 0 450 && stat(path, &st) == 0 451 && st.st_mtime > dict_sdbm->dict.mtime 452 && st.st_mtime < time((time_t *) 0) - 100) 453 msg_warn("database %s is older than source file %s", dbm_path, path); 454 455 close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC); 456 close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC); 457 dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; 458 if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) 459 dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); 460 if (dict_flags & DICT_FLAG_FOLD_FIX) 461 dict_sdbm->dict.fold_buf = vstring_alloc(10); 462 dict_sdbm->dbm = dbm; 463 dict_sdbm->key_buf = 0; 464 dict_sdbm->val_buf = 0; 465 466 if ((dict_flags & DICT_FLAG_LOCK)) 467 myfree(dbm_path); 468 469 return (DICT_DEBUG (&dict_sdbm->dict)); 470 } 471 472 #endif 473