1 /* $NetBSD: dict_cdb.c,v 1.2 2017/02/14 01:16:49 christos 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 #include "warn_stat.h" 66 67 #ifdef HAS_CDB 68 69 #include <cdb.h> 70 #ifndef TINYCDB_VERSION 71 #include <cdb_make.h> 72 #endif 73 #ifndef cdb_fileno 74 #define cdb_fileno(c) ((c)->fd) 75 #endif 76 77 #ifndef CDB_SUFFIX 78 #define CDB_SUFFIX ".cdb" 79 #endif 80 #ifndef CDB_TMP_SUFFIX 81 #define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp" 82 #endif 83 84 /* Application-specific. */ 85 86 typedef struct { 87 DICT dict; /* generic members */ 88 struct cdb cdb; /* cdb structure */ 89 } DICT_CDBQ; /* query interface */ 90 91 typedef struct { 92 DICT dict; /* generic members */ 93 struct cdb_make cdbm; /* cdb_make structure */ 94 char *cdb_path; /* cdb pathname (.cdb) */ 95 char *tmp_path; /* temporary pathname (.tmp) */ 96 } DICT_CDBM; /* rebuild interface */ 97 98 /* dict_cdbq_lookup - find database entry, query mode */ 99 100 static const char *dict_cdbq_lookup(DICT *dict, const char *name) 101 { 102 DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict; 103 unsigned vlen; 104 int status = 0; 105 static char *buf; 106 static unsigned len; 107 const char *result = 0; 108 109 dict->error = 0; 110 111 /* CDB is constant, so do not try to acquire a lock. */ 112 113 /* 114 * Optionally fold the key. 115 */ 116 if (dict->flags & DICT_FLAG_FOLD_FIX) { 117 if (dict->fold_buf == 0) 118 dict->fold_buf = vstring_alloc(10); 119 vstring_strcpy(dict->fold_buf, name); 120 name = lowercase(vstring_str(dict->fold_buf)); 121 } 122 123 /* 124 * See if this CDB file was written with one null byte appended to key 125 * and value. 126 */ 127 if (dict->flags & DICT_FLAG_TRY1NULL) { 128 status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1); 129 if (status > 0) 130 dict->flags &= ~DICT_FLAG_TRY0NULL; 131 } 132 133 /* 134 * See if this CDB file was written with no null byte appended to key and 135 * value. 136 */ 137 if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 138 status = cdb_find(&dict_cdbq->cdb, name, strlen(name)); 139 if (status > 0) 140 dict->flags &= ~DICT_FLAG_TRY1NULL; 141 } 142 if (status < 0) 143 msg_fatal("error reading %s: %m", dict->name); 144 145 if (status) { 146 vlen = cdb_datalen(&dict_cdbq->cdb); 147 if (len < vlen) { 148 if (buf == 0) 149 buf = mymalloc(vlen + 1); 150 else 151 buf = myrealloc(buf, vlen + 1); 152 len = vlen; 153 } 154 if (cdb_read(&dict_cdbq->cdb, buf, vlen, 155 cdb_datapos(&dict_cdbq->cdb)) < 0) 156 msg_fatal("error reading %s: %m", dict->name); 157 buf[vlen] = '\0'; 158 result = buf; 159 } 160 /* No locking so not release the lock. */ 161 162 return (result); 163 } 164 165 /* dict_cdbq_close - close data base, query mode */ 166 167 static void dict_cdbq_close(DICT *dict) 168 { 169 DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict; 170 171 cdb_free(&dict_cdbq->cdb); 172 close(dict->stat_fd); 173 if (dict->fold_buf) 174 vstring_free(dict->fold_buf); 175 dict_free(dict); 176 } 177 178 /* dict_cdbq_open - open data base, query mode */ 179 180 static DICT *dict_cdbq_open(const char *path, int dict_flags) 181 { 182 DICT_CDBQ *dict_cdbq; 183 struct stat st; 184 char *cdb_path; 185 int fd; 186 187 /* 188 * Let the optimizer worry about eliminating redundant code. 189 */ 190 #define DICT_CDBQ_OPEN_RETURN(d) do { \ 191 DICT *__d = (d); \ 192 myfree(cdb_path); \ 193 return (__d); \ 194 } while (0) 195 196 cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); 197 198 if ((fd = open(cdb_path, O_RDONLY)) < 0) 199 DICT_CDBQ_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path, 200 O_RDONLY, dict_flags, 201 "open database %s: %m", cdb_path)); 202 203 dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB, 204 cdb_path, sizeof(*dict_cdbq)); 205 #if defined(TINYCDB_VERSION) 206 if (cdb_init(&(dict_cdbq->cdb), fd) != 0) 207 msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path); 208 #else 209 cdb_init(&(dict_cdbq->cdb), fd); 210 #endif 211 dict_cdbq->dict.lookup = dict_cdbq_lookup; 212 dict_cdbq->dict.close = dict_cdbq_close; 213 dict_cdbq->dict.stat_fd = fd; 214 if (fstat(fd, &st) < 0) 215 msg_fatal("dict_dbq_open: fstat: %m"); 216 dict_cdbq->dict.mtime = st.st_mtime; 217 dict_cdbq->dict.owner.uid = st.st_uid; 218 dict_cdbq->dict.owner.status = (st.st_uid != 0); 219 close_on_exec(fd, CLOSE_ON_EXEC); 220 221 /* 222 * Warn if the source file is newer than the indexed file, except when 223 * the source file changed only seconds ago. 224 */ 225 if (stat(path, &st) == 0 226 && st.st_mtime > dict_cdbq->dict.mtime 227 && st.st_mtime < time((time_t *) 0) - 100) 228 msg_warn("database %s is older than source file %s", cdb_path, path); 229 230 /* 231 * If undecided about appending a null byte to key and value, choose to 232 * try both in query mode. 233 */ 234 if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 235 dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL; 236 dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED; 237 if (dict_flags & DICT_FLAG_FOLD_FIX) 238 dict_cdbq->dict.fold_buf = vstring_alloc(10); 239 240 DICT_CDBQ_OPEN_RETURN(DICT_DEBUG (&dict_cdbq->dict)); 241 } 242 243 /* dict_cdbm_update - add database entry, create mode */ 244 245 static int dict_cdbm_update(DICT *dict, const char *name, const char *value) 246 { 247 DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict; 248 unsigned ksize, vsize; 249 int r; 250 251 dict->error = 0; 252 253 /* 254 * Optionally fold the key. 255 */ 256 if (dict->flags & DICT_FLAG_FOLD_FIX) { 257 if (dict->fold_buf == 0) 258 dict->fold_buf = vstring_alloc(10); 259 vstring_strcpy(dict->fold_buf, name); 260 name = lowercase(vstring_str(dict->fold_buf)); 261 } 262 ksize = strlen(name); 263 vsize = strlen(value); 264 265 /* 266 * Optionally append a null byte to key and value. 267 */ 268 if (dict->flags & DICT_FLAG_TRY1NULL) { 269 ksize++; 270 vsize++; 271 } 272 273 /* 274 * Do the add operation. No locking is done. 275 */ 276 #ifdef TINYCDB_VERSION 277 #ifndef CDB_PUT_ADD 278 #error please upgrate tinycdb to at least 0.5 version 279 #endif 280 if (dict->flags & DICT_FLAG_DUP_IGNORE) 281 r = CDB_PUT_ADD; 282 else if (dict->flags & DICT_FLAG_DUP_REPLACE) 283 r = CDB_PUT_REPLACE; 284 else 285 r = CDB_PUT_INSERT; 286 r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r); 287 if (r < 0) 288 msg_fatal("error writing %s: %m", dict_cdbm->tmp_path); 289 else if (r > 0) { 290 if (dict->flags & (DICT_FLAG_DUP_IGNORE | DICT_FLAG_DUP_REPLACE)) 291 /* void */ ; 292 else if (dict->flags & DICT_FLAG_DUP_WARN) 293 msg_warn("%s: duplicate entry: \"%s\"", 294 dict_cdbm->dict.name, name); 295 else 296 msg_fatal("%s: duplicate entry: \"%s\"", 297 dict_cdbm->dict.name, name); 298 } 299 return (r); 300 #else 301 if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0) 302 msg_fatal("error writing %s: %m", dict_cdbm->tmp_path); 303 return (0); 304 #endif 305 } 306 307 /* dict_cdbm_close - close data base and rename file.tmp to file.cdb */ 308 309 static void dict_cdbm_close(DICT *dict) 310 { 311 DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict; 312 int fd = cdb_fileno(&dict_cdbm->cdbm); 313 314 /* 315 * Note: if FCNTL locking is used, closing any file descriptor on a 316 * locked file cancels all locks that the process may have on that file. 317 * CDB is FCNTL locking safe, because it uses the same file descriptor 318 * for database I/O and locking. 319 */ 320 if (cdb_make_finish(&dict_cdbm->cdbm) < 0) 321 msg_fatal("finish database %s: %m", dict_cdbm->tmp_path); 322 if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0) 323 msg_fatal("rename database from %s to %s: %m", 324 dict_cdbm->tmp_path, dict_cdbm->cdb_path); 325 if (close(fd) < 0) /* releases a lock */ 326 msg_fatal("close database %s: %m", dict_cdbm->cdb_path); 327 myfree(dict_cdbm->cdb_path); 328 myfree(dict_cdbm->tmp_path); 329 if (dict->fold_buf) 330 vstring_free(dict->fold_buf); 331 dict_free(dict); 332 } 333 334 /* dict_cdbm_open - create database as file.tmp */ 335 336 static DICT *dict_cdbm_open(const char *path, int dict_flags) 337 { 338 DICT_CDBM *dict_cdbm; 339 char *cdb_path; 340 char *tmp_path; 341 int fd; 342 struct stat st0, st1; 343 344 /* 345 * Let the optimizer worry about eliminating redundant code. 346 */ 347 #define DICT_CDBM_OPEN_RETURN(d) do { \ 348 DICT *__d = (d); \ 349 if (cdb_path) \ 350 myfree(cdb_path); \ 351 if (tmp_path) \ 352 myfree(tmp_path); \ 353 return (__d); \ 354 } while (0) 355 356 cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); 357 tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0); 358 359 /* 360 * Repeat until we have opened *and* locked *existing* file. Since the 361 * new (tmp) file will be renamed to be .cdb file, locking here is 362 * somewhat funny to work around possible race conditions. Note that we 363 * can't open a file with O_TRUNC as we can't know if another process 364 * isn't creating it at the same time. 365 */ 366 for (;;) { 367 if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0) 368 DICT_CDBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path, 369 O_RDWR, dict_flags, 370 "open database %s: %m", 371 tmp_path)); 372 if (fstat(fd, &st0) < 0) 373 msg_fatal("fstat(%s): %m", tmp_path); 374 375 /* 376 * Get an exclusive lock - we're going to change the database so we 377 * can't have any spectators. 378 */ 379 if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 380 msg_fatal("lock %s: %m", tmp_path); 381 382 if (stat(tmp_path, &st1) < 0) 383 msg_fatal("stat(%s): %m", tmp_path); 384 385 /* 386 * Compare file's state before and after lock: should be the same, 387 * and nlinks should be >0, or else we opened non-existing file... 388 */ 389 if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev 390 && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink 391 && st0.st_nlink > 0) 392 break; /* successefully opened */ 393 394 close(fd); 395 396 } 397 398 #ifndef NO_FTRUNCATE 399 if (st0.st_size) 400 ftruncate(fd, 0); 401 #endif 402 403 dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path, 404 sizeof(*dict_cdbm)); 405 if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0) 406 msg_fatal("initialize database %s: %m", tmp_path); 407 dict_cdbm->dict.close = dict_cdbm_close; 408 dict_cdbm->dict.update = dict_cdbm_update; 409 dict_cdbm->cdb_path = cdb_path; 410 dict_cdbm->tmp_path = tmp_path; 411 cdb_path = tmp_path = 0; /* DICT_CDBM_OPEN_RETURN() */ 412 dict_cdbm->dict.owner.uid = st1.st_uid; 413 dict_cdbm->dict.owner.status = (st1.st_uid != 0); 414 close_on_exec(fd, CLOSE_ON_EXEC); 415 416 /* 417 * If undecided about appending a null byte to key and value, choose a 418 * default to not append a null byte when creating a cdb. 419 */ 420 if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 421 dict_flags |= DICT_FLAG_TRY0NULL; 422 else if ((dict_flags & DICT_FLAG_TRY1NULL) 423 && (dict_flags & DICT_FLAG_TRY0NULL)) 424 dict_flags &= ~DICT_FLAG_TRY0NULL; 425 dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; 426 if (dict_flags & DICT_FLAG_FOLD_FIX) 427 dict_cdbm->dict.fold_buf = vstring_alloc(10); 428 429 DICT_CDBM_OPEN_RETURN(DICT_DEBUG (&dict_cdbm->dict)); 430 } 431 432 /* dict_cdb_open - open data base for query mode or create mode */ 433 434 DICT *dict_cdb_open(const char *path, int open_flags, int dict_flags) 435 { 436 switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) { 437 case O_RDONLY: /* query mode */ 438 return dict_cdbq_open(path, dict_flags); 439 case O_WRONLY | O_CREAT | O_TRUNC: /* create mode */ 440 case O_RDWR | O_CREAT | O_TRUNC: /* sloppiness */ 441 return dict_cdbm_open(path, dict_flags); 442 default: 443 msg_fatal("dict_cdb_open: inappropriate open flags for cdb database" 444 " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC"); 445 } 446 } 447 448 #endif /* HAS_CDB */ 449