7 /* dictionary manager interface to SDBM files
9 /* #include <dict_sdbm.h>
11 /* DICT *dict_sdbm_open(path, open_flags, dict_flags)
17 /* dict_sdbm_open() opens the named SDBM 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 /* sdbm(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
46 /* Utility library. */
54 #include <stringops.h>
56 #include <dict_sdbm.h>
60 /* Application-specific. */
63 DICT dict
; /* generic members */
64 SDBM
*dbm
; /* open database */
65 VSTRING
*key_buf
; /* key buffer */
66 VSTRING
*val_buf
; /* result buffer */
69 #define SCOPY(buf, data, size) \
70 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
72 /* dict_sdbm_lookup - find database entry */
74 static const char *dict_sdbm_lookup(DICT
*dict
, const char *name
)
76 DICT_SDBM
*dict_sdbm
= (DICT_SDBM
*) dict
;
79 const char *result
= 0;
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");
90 * Optionally fold the key.
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
));
100 * Acquire an exclusive lock.
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
);
107 * See if this DBM file was written with one null byte appended to key
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
);
121 * See if this DBM file was written with no null byte appended to key and
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
);
135 * Release the exclusive lock.
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
);
144 /* dict_sdbm_update - add or update database entry */
146 static void dict_sdbm_update(DICT
*dict
, const char *name
, const char *value
)
148 DICT_SDBM
*dict_sdbm
= (DICT_SDBM
*) dict
;
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");
160 * Optionally fold the key.
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
));
168 dbm_key
.dptr
= (void *) name
;
169 dbm_value
.dptr
= (void *) value
;
170 dbm_key
.dsize
= strlen(name
);
171 dbm_value
.dsize
= strlen(value
);
174 * If undecided about appending a null byte to key and value, choose a
175 * default depending on the platform.
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
;
182 dict
->flags
&= ~DICT_FLAG_TRY0NULL
;
187 * Optionally append a null byte to key and value.
189 if (dict
->flags
& DICT_FLAG_TRY1NULL
) {
195 * Acquire an exclusive lock.
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
);
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
);
208 if (dict
->flags
& DICT_FLAG_DUP_IGNORE
)
210 else if (dict
->flags
& DICT_FLAG_DUP_WARN
)
211 msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm
->dict
.name
, name
);
213 msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm
->dict
.name
, name
);
217 * Release the exclusive lock.
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
);
224 /* dict_sdbm_delete - delete one entry from the dictionary */
226 static int dict_sdbm_delete(DICT
*dict
, const char *name
)
228 DICT_SDBM
*dict_sdbm
= (DICT_SDBM
*) dict
;
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");
239 * Optionally fold the key.
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
));
249 * Acquire an exclusive lock.
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
);
256 * See if this DBM file was written with one null byte appended to key
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 */
268 dict
->flags
&= ~DICT_FLAG_TRY0NULL
; /* found */
273 * See if this DBM file was written with no null byte appended to key and
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 */
285 dict
->flags
&= ~DICT_FLAG_TRY1NULL
; /* found */
290 * Release the exclusive lock.
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
);
299 /* traverse the dictionary */
301 static int dict_sdbm_sequence(DICT
*dict
, const int function
,
302 const char **key
, const char **value
)
304 const char *myname
= "dict_sdbm_sequence";
305 DICT_SDBM
*dict_sdbm
= (DICT_SDBM
*) dict
;
310 * Acquire a shared lock.
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
);
317 * Determine and execute the seek function. It returns the key.
319 sdbm_clearerr(dict_sdbm
->dbm
);
321 case DICT_SEQ_FUN_FIRST
:
322 dbm_key
= sdbm_firstkey(dict_sdbm
->dbm
);
324 case DICT_SEQ_FUN_NEXT
:
325 dbm_key
= sdbm_nextkey(dict_sdbm
->dbm
);
328 msg_panic("%s: invalid function: %d", myname
, function
);
331 if (dbm_key
.dptr
!= 0 && dbm_key
.dsize
> 0) {
334 * Copy the key so that it is guaranteed null terminated.
336 *key
= SCOPY(dict_sdbm
->key_buf
, dbm_key
.dptr
, dbm_key
.dsize
);
339 * Fetch the corresponding value.
341 dbm_value
= sdbm_fetch(dict_sdbm
->dbm
, dbm_key
);
343 if (dbm_value
.dptr
!= 0 && dbm_value
.dsize
> 0) {
346 * Copy the value so that it is guaranteed null terminated.
348 *value
= SCOPY(dict_sdbm
->val_buf
, dbm_value
.dptr
, dbm_value
.dsize
);
352 * Determine if we have hit the last record or an error
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!) */
363 * Determine if we have hit the last record or an error condition.
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 */
371 * Release the shared lock.
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
);
380 /* dict_sdbm_close - disassociate from data base */
382 static void dict_sdbm_close(DICT
*dict
)
384 DICT_SDBM
*dict_sdbm
= (DICT_SDBM
*) dict
;
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
);
392 vstring_free(dict
->fold_buf
);
396 /* dict_sdbm_open - open SDBM data base */
398 DICT
*dict_sdbm_open(const char *path
, int open_flags
, int dict_flags
)
400 DICT_SDBM
*dict_sdbm
;
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.
410 * Programs such as postmap/postalias use their own large-grained (in the
411 * time domain) locks while rewriting the entire file.
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
);
422 * XXX sdbm_open() has no const in prototype.
424 if ((dbm
= sdbm_open((char *) path
, open_flags
, 0644)) == 0)
425 msg_fatal("open database %s.{dir,pag}: %m", path
);
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
);
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
;
446 * Warn if the source file is newer than the indexed file, except when
447 * the source file changed only seconds ago.
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
);
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;
466 if ((dict_flags
& DICT_FLAG_LOCK
))
469 return (DICT_DEBUG (&dict_sdbm
->dict
));