1 /* $NetBSD: dict_cdb.c,v 1.1.1.1 2009/06/23 10:08:59 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_cdb 3 6 /* SUMMARY 7 /* dictionary manager interface to CDB files 8 /* SYNOPSIS 9 /* #include <dict_cdb.h> 10 /* 11 /* DICT *dict_cdb_open(path, open_flags, dict_flags) 12 /* const char *path; 13 /* int open_flags; 14 /* int dict_flags; 15 /* 16 /* DESCRIPTION 17 /* dict_cdb_open() opens the specified CDB database. The result is 18 /* a pointer to a structure that can be used to access the dictionary 19 /* using the generic methods documented in dict_open(3). 20 /* 21 /* Arguments: 22 /* .IP path 23 /* The database pathname, not including the ".cdb" suffix. 24 /* .IP open_flags 25 /* Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC. 26 /* .IP dict_flags 27 /* Flags used by the dictionary interface. 28 /* SEE ALSO 29 /* dict(3) generic dictionary manager 30 /* DIAGNOSTICS 31 /* Fatal errors: cannot open file, write error, out of memory. 32 /* LICENSE 33 /* .ad 34 /* .fi 35 /* The Secure Mailer license must be distributed with this software. 36 /* AUTHOR(S) 37 /* Michael Tokarev <mjt@tls.msk.ru> based on dict_db.c by 38 /* Wietse Venema 39 /* IBM T.J. Watson Research 40 /* P.O. Box 704 41 /* Yorktown Heights, NY 10598, USA 42 /*--*/ 43 44 #include "sys_defs.h" 45 46 /* System library. */ 47 48 #include <sys/stat.h> 49 #include <limits.h> 50 #include <string.h> 51 #include <unistd.h> 52 #include <stdio.h> 53 54 /* Utility library. */ 55 56 #include "msg.h" 57 #include "mymalloc.h" 58 #include "vstring.h" 59 #include "stringops.h" 60 #include "iostuff.h" 61 #include "myflock.h" 62 #include "stringops.h" 63 #include "dict.h" 64 #include "dict_cdb.h" 65 66 #ifdef HAS_CDB 67 68 #include <cdb.h> 69 #ifndef TINYCDB_VERSION 70 #include <cdb_make.h> 71 #endif 72 #ifndef cdb_fileno 73 #define cdb_fileno(c) ((c)->fd) 74 #endif 75 76 #ifndef CDB_SUFFIX 77 #define CDB_SUFFIX ".cdb" 78 #endif 79 #ifndef CDB_TMP_SUFFIX 80 #define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp" 81 #endif 82 83 /* Application-specific. */ 84 85 typedef struct { 86 DICT dict; /* generic members */ 87 struct cdb cdb; /* cdb structure */ 88 } DICT_CDBQ; /* query interface */ 89 90 typedef struct { 91 DICT dict; /* generic members */ 92 struct cdb_make cdbm; /* cdb_make structure */ 93 char *cdb_path; /* cdb pathname (.cdb) */ 94 char *tmp_path; /* temporary pathname (.tmp) */ 95 } DICT_CDBM; /* rebuild interface */ 96 97 /* dict_cdbq_lookup - find database entry, query mode */ 98 99 static const char *dict_cdbq_lookup(DICT *dict, const char *name) 100 { 101 DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict; 102 unsigned vlen; 103 int status = 0; 104 static char *buf; 105 static unsigned len; 106 const char *result = 0; 107 108 dict_errno = 0; 109 110 /* CDB is constant, so do not try to acquire a lock. */ 111 112 /* 113 * Optionally fold the key. 114 */ 115 if (dict->flags & DICT_FLAG_FOLD_FIX) { 116 if (dict->fold_buf == 0) 117 dict->fold_buf = vstring_alloc(10); 118 vstring_strcpy(dict->fold_buf, name); 119 name = lowercase(vstring_str(dict->fold_buf)); 120 } 121 122 /* 123 * See if this CDB file was written with one null byte appended to key 124 * and value. 125 */ 126 if (dict->flags & DICT_FLAG_TRY1NULL) { 127 status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1); 128 if (status > 0) 129 dict->flags &= ~DICT_FLAG_TRY0NULL; 130 } 131 132 /* 133 * See if this CDB file was written with no null byte appended to key and 134 * value. 135 */ 136 if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 137 status = cdb_find(&dict_cdbq->cdb, name, strlen(name)); 138 if (status > 0) 139 dict->flags &= ~DICT_FLAG_TRY1NULL; 140 } 141 if (status < 0) 142 msg_fatal("error reading %s: %m", dict->name); 143 144 if (status) { 145 vlen = cdb_datalen(&dict_cdbq->cdb); 146 if (len < vlen) { 147 if (buf == 0) 148 buf = mymalloc(vlen + 1); 149 else 150 buf = myrealloc(buf, vlen + 1); 151 len = vlen; 152 } 153 if (cdb_read(&dict_cdbq->cdb, buf, vlen, 154 cdb_datapos(&dict_cdbq->cdb)) < 0) 155 msg_fatal("error reading %s: %m", dict->name); 156 buf[vlen] = '\0'; 157 result = buf; 158 } 159 /* No locking so not release the lock. */ 160 161 return (result); 162 } 163 164 /* dict_cdbq_close - close data base, query mode */ 165 166 static void dict_cdbq_close(DICT *dict) 167 { 168 DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict; 169 170 cdb_free(&dict_cdbq->cdb); 171 close(dict->stat_fd); 172 if (dict->fold_buf) 173 vstring_free(dict->fold_buf); 174 dict_free(dict); 175 } 176 177 /* dict_cdbq_open - open data base, query mode */ 178 179 static DICT *dict_cdbq_open(const char *path, int dict_flags) 180 { 181 DICT_CDBQ *dict_cdbq; 182 struct stat st; 183 char *cdb_path; 184 int fd; 185 186 cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); 187 188 if ((fd = open(cdb_path, O_RDONLY)) < 0) 189 msg_fatal("open database %s: %m", cdb_path); 190 191 dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB, 192 cdb_path, sizeof(*dict_cdbq)); 193 #if defined(TINYCDB_VERSION) 194 if (cdb_init(&(dict_cdbq->cdb), fd) != 0) 195 msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path); 196 #else 197 cdb_init(&(dict_cdbq->cdb), fd); 198 #endif 199 dict_cdbq->dict.lookup = dict_cdbq_lookup; 200 dict_cdbq->dict.close = dict_cdbq_close; 201 dict_cdbq->dict.stat_fd = fd; 202 if (fstat(fd, &st) < 0) 203 msg_fatal("dict_dbq_open: fstat: %m"); 204 dict_cdbq->dict.mtime = st.st_mtime; 205 close_on_exec(fd, CLOSE_ON_EXEC); 206 207 /* 208 * Warn if the source file is newer than the indexed file, except when 209 * the source file changed only seconds ago. 210 */ 211 if (stat(path, &st) == 0 212 && st.st_mtime > dict_cdbq->dict.mtime 213 && st.st_mtime < time((time_t *) 0) - 100) 214 msg_warn("database %s is older than source file %s", cdb_path, path); 215 216 /* 217 * If undecided about appending a null byte to key and value, choose to 218 * try both in query mode. 219 */ 220 if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 221 dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL; 222 dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED; 223 if (dict_flags & DICT_FLAG_FOLD_FIX) 224 dict_cdbq->dict.fold_buf = vstring_alloc(10); 225 226 myfree(cdb_path); 227 return (&dict_cdbq->dict); 228 } 229 230 /* dict_cdbm_update - add database entry, create mode */ 231 232 static void dict_cdbm_update(DICT *dict, const char *name, const char *value) 233 { 234 DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict; 235 unsigned ksize, vsize; 236 int r; 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 ksize = strlen(name); 248 vsize = strlen(value); 249 250 /* 251 * Optionally append a null byte to key and value. 252 */ 253 if (dict->flags & DICT_FLAG_TRY1NULL) { 254 ksize++; 255 vsize++; 256 } 257 258 /* 259 * Do the add operation. No locking is done. 260 */ 261 #ifdef TINYCDB_VERSION 262 #ifndef CDB_PUT_ADD 263 #error please upgrate tinycdb to at least 0.5 version 264 #endif 265 if (dict->flags & DICT_FLAG_DUP_IGNORE) 266 r = CDB_PUT_ADD; 267 else if (dict->flags & DICT_FLAG_DUP_REPLACE) 268 r = CDB_PUT_REPLACE; 269 else 270 r = CDB_PUT_INSERT; 271 r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r); 272 if (r < 0) 273 msg_fatal("error writing %s: %m", dict_cdbm->tmp_path); 274 else if (r > 0) { 275 if (dict->flags & (DICT_FLAG_DUP_IGNORE | DICT_FLAG_DUP_REPLACE)) 276 /* void */ ; 277 else if (dict->flags & DICT_FLAG_DUP_WARN) 278 msg_warn("%s: duplicate entry: \"%s\"", 279 dict_cdbm->dict.name, name); 280 else 281 msg_fatal("%s: duplicate entry: \"%s\"", 282 dict_cdbm->dict.name, name); 283 } 284 #else 285 if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0) 286 msg_fatal("error writing %s: %m", dict_cdbm->tmp_path); 287 #endif 288 } 289 290 /* dict_cdbm_close - close data base and rename file.tmp to file.cdb */ 291 292 static void dict_cdbm_close(DICT *dict) 293 { 294 DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict; 295 int fd = cdb_fileno(&dict_cdbm->cdbm); 296 297 /* 298 * Note: if FCNTL locking is used, closing any file descriptor on a 299 * locked file cancels all locks that the process may have on that file. 300 * CDB is FCNTL locking safe, because it uses the same file descriptor 301 * for database I/O and locking. 302 */ 303 if (cdb_make_finish(&dict_cdbm->cdbm) < 0) 304 msg_fatal("finish database %s: %m", dict_cdbm->tmp_path); 305 if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0) 306 msg_fatal("rename database from %s to %s: %m", 307 dict_cdbm->tmp_path, dict_cdbm->cdb_path); 308 if (close(fd) < 0) /* releases a lock */ 309 msg_fatal("close database %s: %m", dict_cdbm->cdb_path); 310 myfree(dict_cdbm->cdb_path); 311 myfree(dict_cdbm->tmp_path); 312 if (dict->fold_buf) 313 vstring_free(dict->fold_buf); 314 dict_free(dict); 315 } 316 317 /* dict_cdbm_open - create database as file.tmp */ 318 319 static DICT *dict_cdbm_open(const char *path, int dict_flags) 320 { 321 DICT_CDBM *dict_cdbm; 322 char *cdb_path; 323 char *tmp_path; 324 int fd; 325 struct stat st0, st1; 326 327 cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); 328 tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0); 329 330 /* 331 * Repeat until we have opened *and* locked *existing* file. Since the 332 * new (tmp) file will be renamed to be .cdb file, locking here is 333 * somewhat funny to work around possible race conditions. Note that we 334 * can't open a file with O_TRUNC as we can't know if another process 335 * isn't creating it at the same time. 336 */ 337 for (;;) { 338 if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0 339 || fstat(fd, &st0) < 0) 340 msg_fatal("open database %s: %m", tmp_path); 341 342 /* 343 * Get an exclusive lock - we're going to change the database so we 344 * can't have any spectators. 345 */ 346 if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 347 msg_fatal("lock %s: %m", tmp_path); 348 349 if (stat(tmp_path, &st1) < 0) 350 msg_fatal("stat(%s): %m", tmp_path); 351 352 /* 353 * Compare file's state before and after lock: should be the same, 354 * and nlinks should be >0, or else we opened non-existing file... 355 */ 356 if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev 357 && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink 358 && st0.st_nlink > 0) 359 break; /* successefully opened */ 360 361 close(fd); 362 363 } 364 365 #ifndef NO_FTRUNCATE 366 if (st0.st_size) 367 ftruncate(fd, 0); 368 #endif 369 370 dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path, 371 sizeof(*dict_cdbm)); 372 if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0) 373 msg_fatal("initialize database %s: %m", tmp_path); 374 dict_cdbm->dict.close = dict_cdbm_close; 375 dict_cdbm->dict.update = dict_cdbm_update; 376 dict_cdbm->cdb_path = cdb_path; 377 dict_cdbm->tmp_path = tmp_path; 378 close_on_exec(fd, CLOSE_ON_EXEC); 379 380 /* 381 * If undecided about appending a null byte to key and value, choose a 382 * default to not append a null byte when creating a cdb. 383 */ 384 if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 385 dict_flags |= DICT_FLAG_TRY0NULL; 386 else if ((dict_flags & DICT_FLAG_TRY1NULL) 387 && (dict_flags & DICT_FLAG_TRY0NULL)) 388 dict_flags &= ~DICT_FLAG_TRY0NULL; 389 dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; 390 if (dict_flags & DICT_FLAG_FOLD_FIX) 391 dict_cdbm->dict.fold_buf = vstring_alloc(10); 392 393 return (&dict_cdbm->dict); 394 } 395 396 /* dict_cdb_open - open data base for query mode or create mode */ 397 398 DICT *dict_cdb_open(const char *path, int open_flags, int dict_flags) 399 { 400 switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) { 401 case O_RDONLY: /* query mode */ 402 return dict_cdbq_open(path, dict_flags); 403 case O_WRONLY | O_CREAT | O_TRUNC: /* create mode */ 404 case O_RDWR | O_CREAT | O_TRUNC: /* sloppiness */ 405 return dict_cdbm_open(path, dict_flags); 406 default: 407 msg_fatal("dict_cdb_open: inappropriate open flags for cdb database" 408 " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC"); 409 } 410 } 411 412 #endif /* HAS_CDB */ 413