xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_sdbm.c (revision b62fc9e20372b08e1785ff6d769312d209fa2005)
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