7 /* dictionary manager interface to DBM files
9 /* #include <dict_dbm.h>
11 /* DICT *dict_dbm_open(path, open_flags, dict_flags)
17 /* dict_dbm_open() opens the named DBM database and makes it available
18 /* via the generic interface described in dict_open(3).
20 /* Fatal errors: cannot open file, file write error, out of memory.
22 /* dict(3) generic dictionary manager
23 /* ndbm(3) data base subroutines
27 /* The Secure Mailer license must be distributed with this software.
30 /* IBM T.J. Watson Research
32 /* Yorktown Heights, NY 10598, USA
48 #error "Error: you are including the Berkeley DB version of ndbm.h"
49 #error "To build with Postfix NDBM support, delete the Berkeley DB ndbm.h file"
54 /* Utility library. */
62 #include "stringops.h"
66 /* Application-specific. */
69 DICT dict
; /* generic members */
70 DBM
*dbm
; /* open database */
71 VSTRING
*key_buf
; /* key buffer */
72 VSTRING
*val_buf
; /* result buffer */
75 #define SCOPY(buf, data, size) \
76 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
78 /* dict_dbm_lookup - find database entry */
80 static const char *dict_dbm_lookup(DICT
*dict
, const char *name
)
82 DICT_DBM
*dict_dbm
= (DICT_DBM
*) dict
;
85 const char *result
= 0;
90 if ((dict
->flags
& (DICT_FLAG_TRY1NULL
| DICT_FLAG_TRY0NULL
)) == 0)
91 msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
96 * Optionally fold the key.
98 if (dict
->flags
& DICT_FLAG_FOLD_FIX
) {
99 if (dict
->fold_buf
== 0)
100 dict
->fold_buf
= vstring_alloc(10);
101 vstring_strcpy(dict
->fold_buf
, name
);
102 name
= lowercase(vstring_str(dict
->fold_buf
));
106 * Acquire an exclusive lock.
108 if ((dict
->flags
& DICT_FLAG_LOCK
)
109 && myflock(dict
->lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_SHARED
) < 0)
110 msg_fatal("%s: lock dictionary: %m", dict_dbm
->dict
.name
);
113 * See if this DBM file was written with one null byte appended to key
116 if (dict
->flags
& DICT_FLAG_TRY1NULL
) {
117 dbm_key
.dptr
= (void *) name
;
118 dbm_key
.dsize
= strlen(name
) + 1;
119 dbm_value
= dbm_fetch(dict_dbm
->dbm
, dbm_key
);
120 if (dbm_value
.dptr
!= 0) {
121 dict
->flags
&= ~DICT_FLAG_TRY0NULL
;
122 result
= SCOPY(dict_dbm
->val_buf
, dbm_value
.dptr
, dbm_value
.dsize
);
127 * See if this DBM file was written with no null byte appended to key and
130 if (result
== 0 && (dict
->flags
& DICT_FLAG_TRY0NULL
)) {
131 dbm_key
.dptr
= (void *) name
;
132 dbm_key
.dsize
= strlen(name
);
133 dbm_value
= dbm_fetch(dict_dbm
->dbm
, dbm_key
);
134 if (dbm_value
.dptr
!= 0) {
135 dict
->flags
&= ~DICT_FLAG_TRY1NULL
;
136 result
= SCOPY(dict_dbm
->val_buf
, dbm_value
.dptr
, dbm_value
.dsize
);
141 * Release the exclusive lock.
143 if ((dict
->flags
& DICT_FLAG_LOCK
)
144 && myflock(dict
->lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_NONE
) < 0)
145 msg_fatal("%s: unlock dictionary: %m", dict_dbm
->dict
.name
);
150 /* dict_dbm_update - add or update database entry */
152 static void dict_dbm_update(DICT
*dict
, const char *name
, const char *value
)
154 DICT_DBM
*dict_dbm
= (DICT_DBM
*) dict
;
162 if ((dict
->flags
& (DICT_FLAG_TRY1NULL
| DICT_FLAG_TRY0NULL
)) == 0)
163 msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
166 * Optionally fold the key.
168 if (dict
->flags
& DICT_FLAG_FOLD_FIX
) {
169 if (dict
->fold_buf
== 0)
170 dict
->fold_buf
= vstring_alloc(10);
171 vstring_strcpy(dict
->fold_buf
, name
);
172 name
= lowercase(vstring_str(dict
->fold_buf
));
175 dbm_key
.dptr
= (void *) name
;
176 dbm_value
.dptr
= (void *) value
;
177 dbm_key
.dsize
= strlen(name
);
178 dbm_value
.dsize
= strlen(value
);
181 * If undecided about appending a null byte to key and value, choose a
182 * default depending on the platform.
184 if ((dict
->flags
& DICT_FLAG_TRY1NULL
)
185 && (dict
->flags
& DICT_FLAG_TRY0NULL
)) {
186 #ifdef DBM_NO_TRAILING_NULL
187 dict
->flags
&= ~DICT_FLAG_TRY1NULL
;
189 dict
->flags
&= ~DICT_FLAG_TRY0NULL
;
194 * Optionally append a null byte to key and value.
196 if (dict
->flags
& DICT_FLAG_TRY1NULL
) {
202 * Acquire an exclusive lock.
204 if ((dict
->flags
& DICT_FLAG_LOCK
)
205 && myflock(dict
->lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_EXCLUSIVE
) < 0)
206 msg_fatal("%s: lock dictionary: %m", dict_dbm
->dict
.name
);
211 if ((status
= dbm_store(dict_dbm
->dbm
, dbm_key
, dbm_value
,
212 (dict
->flags
& DICT_FLAG_DUP_REPLACE
) ? DBM_REPLACE
: DBM_INSERT
)) < 0)
213 msg_fatal("error writing DBM database %s: %m", dict_dbm
->dict
.name
);
215 if (dict
->flags
& DICT_FLAG_DUP_IGNORE
)
217 else if (dict
->flags
& DICT_FLAG_DUP_WARN
)
218 msg_warn("%s: duplicate entry: \"%s\"", dict_dbm
->dict
.name
, name
);
220 msg_fatal("%s: duplicate entry: \"%s\"", dict_dbm
->dict
.name
, name
);
224 * Release the exclusive lock.
226 if ((dict
->flags
& DICT_FLAG_LOCK
)
227 && myflock(dict
->lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_NONE
) < 0)
228 msg_fatal("%s: unlock dictionary: %m", dict_dbm
->dict
.name
);
231 /* dict_dbm_delete - delete one entry from the dictionary */
233 static int dict_dbm_delete(DICT
*dict
, const char *name
)
235 DICT_DBM
*dict_dbm
= (DICT_DBM
*) dict
;
242 if ((dict
->flags
& (DICT_FLAG_TRY1NULL
| DICT_FLAG_TRY0NULL
)) == 0)
243 msg_panic("dict_dbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
246 * Optionally fold the key.
248 if (dict
->flags
& DICT_FLAG_FOLD_FIX
) {
249 if (dict
->fold_buf
== 0)
250 dict
->fold_buf
= vstring_alloc(10);
251 vstring_strcpy(dict
->fold_buf
, name
);
252 name
= lowercase(vstring_str(dict
->fold_buf
));
256 * Acquire an exclusive lock.
258 if ((dict
->flags
& DICT_FLAG_LOCK
)
259 && myflock(dict
->lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_EXCLUSIVE
) < 0)
260 msg_fatal("%s: lock dictionary: %m", dict_dbm
->dict
.name
);
263 * See if this DBM file was written with one null byte appended to key
266 if (dict
->flags
& DICT_FLAG_TRY1NULL
) {
267 dbm_key
.dptr
= (void *) name
;
268 dbm_key
.dsize
= strlen(name
) + 1;
269 dbm_clearerr(dict_dbm
->dbm
);
270 if ((status
= dbm_delete(dict_dbm
->dbm
, dbm_key
)) < 0) {
271 if (dbm_error(dict_dbm
->dbm
) != 0) /* fatal error */
272 msg_fatal("error deleting from %s: %m", dict_dbm
->dict
.name
);
273 status
= 1; /* not found */
275 dict
->flags
&= ~DICT_FLAG_TRY0NULL
; /* found */
280 * See if this DBM file was written with no null byte appended to key and
283 if (status
> 0 && (dict
->flags
& DICT_FLAG_TRY0NULL
)) {
284 dbm_key
.dptr
= (void *) name
;
285 dbm_key
.dsize
= strlen(name
);
286 dbm_clearerr(dict_dbm
->dbm
);
287 if ((status
= dbm_delete(dict_dbm
->dbm
, dbm_key
)) < 0) {
288 if (dbm_error(dict_dbm
->dbm
) != 0) /* fatal error */
289 msg_fatal("error deleting from %s: %m", dict_dbm
->dict
.name
);
290 status
= 1; /* not found */
292 dict
->flags
&= ~DICT_FLAG_TRY1NULL
; /* found */
297 * Release the exclusive lock.
299 if ((dict
->flags
& DICT_FLAG_LOCK
)
300 && myflock(dict
->lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_NONE
) < 0)
301 msg_fatal("%s: unlock dictionary: %m", dict_dbm
->dict
.name
);
306 /* traverse the dictionary */
308 static int dict_dbm_sequence(DICT
*dict
, int function
,
309 const char **key
, const char **value
)
311 const char *myname
= "dict_dbm_sequence";
312 DICT_DBM
*dict_dbm
= (DICT_DBM
*) dict
;
317 * Acquire a shared lock.
319 if ((dict
->flags
& DICT_FLAG_LOCK
)
320 && myflock(dict
->lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_SHARED
) < 0)
321 msg_fatal("%s: lock dictionary: %m", dict_dbm
->dict
.name
);
324 * Determine and execute the seek function. It returns the key.
327 case DICT_SEQ_FUN_FIRST
:
328 dbm_key
= dbm_firstkey(dict_dbm
->dbm
);
330 case DICT_SEQ_FUN_NEXT
:
331 dbm_key
= dbm_nextkey(dict_dbm
->dbm
);
334 msg_panic("%s: invalid function: %d", myname
, function
);
337 if (dbm_key
.dptr
!= 0 && dbm_key
.dsize
> 0) {
340 * Copy the key so that it is guaranteed null terminated.
342 *key
= SCOPY(dict_dbm
->key_buf
, dbm_key
.dptr
, dbm_key
.dsize
);
345 * Fetch the corresponding value.
347 dbm_value
= dbm_fetch(dict_dbm
->dbm
, dbm_key
);
349 if (dbm_value
.dptr
!= 0 && dbm_value
.dsize
> 0) {
352 * Copy the value so that it is guaranteed null terminated.
354 *value
= SCOPY(dict_dbm
->val_buf
, dbm_value
.dptr
, dbm_value
.dsize
);
358 * Determine if we have hit the last record or an error
361 if (dbm_error(dict_dbm
->dbm
))
362 msg_fatal("error seeking %s: %m", dict_dbm
->dict
.name
);
363 return (1); /* no error: eof/not found
364 * (should not happen!) */
369 * Determine if we have hit the last record or an error condition.
371 if (dbm_error(dict_dbm
->dbm
))
372 msg_fatal("error seeking %s: %m", dict_dbm
->dict
.name
);
373 return (1); /* no error: eof/not found */
377 * Release the shared lock.
379 if ((dict
->flags
& DICT_FLAG_LOCK
)
380 && myflock(dict
->lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_NONE
) < 0)
381 msg_fatal("%s: unlock dictionary: %m", dict_dbm
->dict
.name
);
386 /* dict_dbm_close - disassociate from data base */
388 static void dict_dbm_close(DICT
*dict
)
390 DICT_DBM
*dict_dbm
= (DICT_DBM
*) dict
;
392 dbm_close(dict_dbm
->dbm
);
393 if (dict_dbm
->key_buf
)
394 vstring_free(dict_dbm
->key_buf
);
395 if (dict_dbm
->val_buf
)
396 vstring_free(dict_dbm
->val_buf
);
398 vstring_free(dict
->fold_buf
);
402 /* dict_dbm_open - open DBM data base */
404 DICT
*dict_dbm_open(const char *path
, int open_flags
, int dict_flags
)
413 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
414 * the time domain) locking while accessing individual database records.
416 * Programs such as postmap/postalias use their own large-grained (in the
417 * time domain) locks while rewriting the entire file.
419 if (dict_flags
& DICT_FLAG_LOCK
) {
420 dbm_path
= concatenate(path
, ".dir", (char *) 0);
421 if ((lock_fd
= open(dbm_path
, open_flags
, 0644)) < 0)
422 msg_fatal("open database %s: %m", dbm_path
);
423 if (myflock(lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_SHARED
) < 0)
424 msg_fatal("shared-lock database %s for open: %m", dbm_path
);
428 * XXX SunOS 5.x has no const in dbm_open() prototype.
430 if ((dbm
= dbm_open((char *) path
, open_flags
, 0644)) == 0)
431 msg_fatal("open database %s.{dir,pag}: %m", path
);
433 if (dict_flags
& DICT_FLAG_LOCK
) {
434 if (myflock(lock_fd
, INTERNAL_LOCK
, MYFLOCK_OP_NONE
) < 0)
435 msg_fatal("unlock database %s for open: %m", dbm_path
);
436 if (close(lock_fd
) < 0)
437 msg_fatal("close database %s: %m", dbm_path
);
439 dict_dbm
= (DICT_DBM
*) dict_alloc(DICT_TYPE_DBM
, path
, sizeof(*dict_dbm
));
440 dict_dbm
->dict
.lookup
= dict_dbm_lookup
;
441 dict_dbm
->dict
.update
= dict_dbm_update
;
442 dict_dbm
->dict
.delete = dict_dbm_delete
;
443 dict_dbm
->dict
.sequence
= dict_dbm_sequence
;
444 dict_dbm
->dict
.close
= dict_dbm_close
;
445 dict_dbm
->dict
.lock_fd
= dbm_dirfno(dbm
);
446 dict_dbm
->dict
.stat_fd
= dbm_pagfno(dbm
);
447 if (dict_dbm
->dict
.lock_fd
== dict_dbm
->dict
.stat_fd
)
448 msg_fatal("open database %s: cannot support GDBM", path
);
449 if (fstat(dict_dbm
->dict
.stat_fd
, &st
) < 0)
450 msg_fatal("dict_dbm_open: fstat: %m");
451 dict_dbm
->dict
.mtime
= st
.st_mtime
;
454 * Warn if the source file is newer than the indexed file, except when
455 * the source file changed only seconds ago.
457 if ((dict_flags
& DICT_FLAG_LOCK
) != 0
458 && stat(path
, &st
) == 0
459 && st
.st_mtime
> dict_dbm
->dict
.mtime
460 && st
.st_mtime
< time((time_t *) 0) - 100)
461 msg_warn("database %s is older than source file %s", dbm_path
, path
);
463 close_on_exec(dbm_pagfno(dbm
), CLOSE_ON_EXEC
);
464 close_on_exec(dbm_dirfno(dbm
), CLOSE_ON_EXEC
);
465 dict_dbm
->dict
.flags
= dict_flags
| DICT_FLAG_FIXED
;
466 if ((dict_flags
& (DICT_FLAG_TRY0NULL
| DICT_FLAG_TRY1NULL
)) == 0)
467 dict_dbm
->dict
.flags
|= (DICT_FLAG_TRY0NULL
| DICT_FLAG_TRY1NULL
);
468 if (dict_flags
& DICT_FLAG_FOLD_FIX
)
469 dict_dbm
->dict
.fold_buf
= vstring_alloc(10);
471 dict_dbm
->key_buf
= 0;
472 dict_dbm
->val_buf
= 0;
474 if ((dict_flags
& DICT_FLAG_LOCK
))
477 return (DICT_DEBUG (&dict_dbm
->dict
));