1 /* $NetBSD: dict_sdbm.c,v 1.1.1.2 2013/01/02 18:59:12 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 #include <warn_stat.h>
58
59 #ifdef HAS_SDBM
60
61 /* Application-specific. */
62
63 typedef struct {
64 DICT dict; /* generic members */
65 SDBM *dbm; /* open database */
66 VSTRING *key_buf; /* key buffer */
67 VSTRING *val_buf; /* result buffer */
68 } DICT_SDBM;
69
70 #define SCOPY(buf, data, size) \
71 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
72
73 /* dict_sdbm_lookup - find database entry */
74
dict_sdbm_lookup(DICT * dict,const char * name)75 static const char *dict_sdbm_lookup(DICT *dict, const char *name)
76 {
77 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
78 datum dbm_key;
79 datum dbm_value;
80 const char *result = 0;
81
82 dict->error = 0;
83
84 /*
85 * Sanity check.
86 */
87 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
88 msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
89
90 /*
91 * Optionally fold the key.
92 */
93 if (dict->flags & DICT_FLAG_FOLD_FIX) {
94 if (dict->fold_buf == 0)
95 dict->fold_buf = vstring_alloc(10);
96 vstring_strcpy(dict->fold_buf, name);
97 name = lowercase(vstring_str(dict->fold_buf));
98 }
99
100 /*
101 * Acquire an exclusive lock.
102 */
103 if ((dict->flags & DICT_FLAG_LOCK)
104 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
105 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
106
107 /*
108 * See if this DBM file was written with one null byte appended to key
109 * and value.
110 */
111 if (dict->flags & DICT_FLAG_TRY1NULL) {
112 dbm_key.dptr = (void *) name;
113 dbm_key.dsize = strlen(name) + 1;
114 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
115 if (dbm_value.dptr != 0) {
116 dict->flags &= ~DICT_FLAG_TRY0NULL;
117 result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
118 }
119 }
120
121 /*
122 * See if this DBM file was written with no null byte appended to key and
123 * value.
124 */
125 if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
126 dbm_key.dptr = (void *) name;
127 dbm_key.dsize = strlen(name);
128 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
129 if (dbm_value.dptr != 0) {
130 dict->flags &= ~DICT_FLAG_TRY1NULL;
131 result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
132 }
133 }
134
135 /*
136 * Release the exclusive lock.
137 */
138 if ((dict->flags & DICT_FLAG_LOCK)
139 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
140 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
141
142 return (result);
143 }
144
145 /* dict_sdbm_update - add or update database entry */
146
dict_sdbm_update(DICT * dict,const char * name,const char * value)147 static int dict_sdbm_update(DICT *dict, const char *name, const char *value)
148 {
149 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
150 datum dbm_key;
151 datum dbm_value;
152 int status;
153
154 dict->error = 0;
155
156 /*
157 * Sanity check.
158 */
159 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
160 msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
161
162 /*
163 * Optionally fold the key.
164 */
165 if (dict->flags & DICT_FLAG_FOLD_FIX) {
166 if (dict->fold_buf == 0)
167 dict->fold_buf = vstring_alloc(10);
168 vstring_strcpy(dict->fold_buf, name);
169 name = lowercase(vstring_str(dict->fold_buf));
170 }
171 dbm_key.dptr = (void *) name;
172 dbm_value.dptr = (void *) value;
173 dbm_key.dsize = strlen(name);
174 dbm_value.dsize = strlen(value);
175
176 /*
177 * If undecided about appending a null byte to key and value, choose a
178 * default depending on the platform.
179 */
180 if ((dict->flags & DICT_FLAG_TRY1NULL)
181 && (dict->flags & DICT_FLAG_TRY0NULL)) {
182 #ifdef DBM_NO_TRAILING_NULL
183 dict->flags &= ~DICT_FLAG_TRY1NULL;
184 #else
185 dict->flags &= ~DICT_FLAG_TRY0NULL;
186 #endif
187 }
188
189 /*
190 * Optionally append a null byte to key and value.
191 */
192 if (dict->flags & DICT_FLAG_TRY1NULL) {
193 dbm_key.dsize++;
194 dbm_value.dsize++;
195 }
196
197 /*
198 * Acquire an exclusive lock.
199 */
200 if ((dict->flags & DICT_FLAG_LOCK)
201 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
202 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
203
204 /*
205 * Do the update.
206 */
207 if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value,
208 (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0)
209 msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name);
210 if (status) {
211 if (dict->flags & DICT_FLAG_DUP_IGNORE)
212 /* void */ ;
213 else if (dict->flags & DICT_FLAG_DUP_WARN)
214 msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
215 else
216 msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
217 }
218
219 /*
220 * Release the exclusive lock.
221 */
222 if ((dict->flags & DICT_FLAG_LOCK)
223 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
224 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
225
226 return (status);
227 }
228
229 /* dict_sdbm_delete - delete one entry from the dictionary */
230
dict_sdbm_delete(DICT * dict,const char * name)231 static int dict_sdbm_delete(DICT *dict, const char *name)
232 {
233 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
234 datum dbm_key;
235 int status = 1;
236
237 dict->error = 0;
238
239 /*
240 * Sanity check.
241 */
242 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
243 msg_panic("dict_sdbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
244
245 /*
246 * Optionally fold the key.
247 */
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));
253 }
254
255 /*
256 * Acquire an exclusive lock.
257 */
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_sdbm->dict.name);
261
262 /*
263 * See if this DBM file was written with one null byte appended to key
264 * and value.
265 */
266 if (dict->flags & DICT_FLAG_TRY1NULL) {
267 dbm_key.dptr = (void *) name;
268 dbm_key.dsize = strlen(name) + 1;
269 sdbm_clearerr(dict_sdbm->dbm);
270 if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
271 if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
272 msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name);
273 status = 1; /* not found */
274 } else {
275 dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */
276 }
277 }
278
279 /*
280 * See if this DBM file was written with no null byte appended to key and
281 * value.
282 */
283 if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
284 dbm_key.dptr = (void *) name;
285 dbm_key.dsize = strlen(name);
286 sdbm_clearerr(dict_sdbm->dbm);
287 if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
288 if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
289 msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name);
290 status = 1; /* not found */
291 } else {
292 dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
293 }
294 }
295
296 /*
297 * Release the exclusive lock.
298 */
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_sdbm->dict.name);
302
303 return (status);
304 }
305
306 /* traverse the dictionary */
307
dict_sdbm_sequence(DICT * dict,const int function,const char ** key,const char ** value)308 static int dict_sdbm_sequence(DICT *dict, const int function,
309 const char **key, const char **value)
310 {
311 const char *myname = "dict_sdbm_sequence";
312 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
313 datum dbm_key;
314 datum dbm_value;
315 int status;
316
317 dict->error = 0;
318
319 /*
320 * Acquire a shared lock.
321 */
322 if ((dict->flags & DICT_FLAG_LOCK)
323 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
324 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
325
326 /*
327 * Determine and execute the seek function. It returns the key.
328 */
329 sdbm_clearerr(dict_sdbm->dbm);
330 switch (function) {
331 case DICT_SEQ_FUN_FIRST:
332 dbm_key = sdbm_firstkey(dict_sdbm->dbm);
333 break;
334 case DICT_SEQ_FUN_NEXT:
335 dbm_key = sdbm_nextkey(dict_sdbm->dbm);
336 break;
337 default:
338 msg_panic("%s: invalid function: %d", myname, function);
339 }
340
341 if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
342
343 /*
344 * Copy the key so that it is guaranteed null terminated.
345 */
346 *key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize);
347
348 /*
349 * Fetch the corresponding value.
350 */
351 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
352
353 if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
354
355 /*
356 * Copy the value so that it is guaranteed null terminated.
357 */
358 *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
359 status = 0;
360 } else {
361
362 /*
363 * Determine if we have hit the last record or an error
364 * condition.
365 */
366 if (sdbm_error(dict_sdbm->dbm))
367 msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
368 status = 1; /* no error: eof/not found
369 * (should not happen!) */
370 }
371 } else {
372
373 /*
374 * Determine if we have hit the last record or an error condition.
375 */
376 if (sdbm_error(dict_sdbm->dbm))
377 msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
378 status = 1; /* no error: eof/not found */
379 }
380
381 /*
382 * Release the shared lock.
383 */
384 if ((dict->flags & DICT_FLAG_LOCK)
385 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
386 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
387
388 return (status);
389 }
390
391 /* dict_sdbm_close - disassociate from data base */
392
dict_sdbm_close(DICT * dict)393 static void dict_sdbm_close(DICT *dict)
394 {
395 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
396
397 sdbm_close(dict_sdbm->dbm);
398 if (dict_sdbm->key_buf)
399 vstring_free(dict_sdbm->key_buf);
400 if (dict_sdbm->val_buf)
401 vstring_free(dict_sdbm->val_buf);
402 if (dict->fold_buf)
403 vstring_free(dict->fold_buf);
404 dict_free(dict);
405 }
406
407 /* dict_sdbm_open - open SDBM data base */
408
dict_sdbm_open(const char * path,int open_flags,int dict_flags)409 DICT *dict_sdbm_open(const char *path, int open_flags, int dict_flags)
410 {
411 DICT_SDBM *dict_sdbm;
412 struct stat st;
413 SDBM *dbm;
414 char *dbm_path;
415 int lock_fd;
416
417 /*
418 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
419 * the time domain) locking while accessing individual database records.
420 *
421 * Programs such as postmap/postalias use their own large-grained (in the
422 * time domain) locks while rewriting the entire file.
423 */
424 if (dict_flags & DICT_FLAG_LOCK) {
425 dbm_path = concatenate(path, ".dir", (char *) 0);
426 if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
427 msg_fatal("open database %s: %m", dbm_path);
428 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
429 msg_fatal("shared-lock database %s for open: %m", dbm_path);
430 }
431
432 /*
433 * XXX sdbm_open() has no const in prototype.
434 */
435 if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0)
436 msg_fatal("open database %s.{dir,pag}: %m", path);
437
438 if (dict_flags & DICT_FLAG_LOCK) {
439 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
440 msg_fatal("unlock database %s for open: %m", dbm_path);
441 if (close(lock_fd) < 0)
442 msg_fatal("close database %s: %m", dbm_path);
443 }
444 dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm));
445 dict_sdbm->dict.lookup = dict_sdbm_lookup;
446 dict_sdbm->dict.update = dict_sdbm_update;
447 dict_sdbm->dict.delete = dict_sdbm_delete;
448 dict_sdbm->dict.sequence = dict_sdbm_sequence;
449 dict_sdbm->dict.close = dict_sdbm_close;
450 dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm);
451 dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm);
452 if (fstat(dict_sdbm->dict.stat_fd, &st) < 0)
453 msg_fatal("dict_sdbm_open: fstat: %m");
454 dict_sdbm->dict.mtime = st.st_mtime;
455 dict_sdbm->dict.owner.uid = st.st_uid;
456 dict_sdbm->dict.owner.status = (st.st_uid != 0);
457
458 /*
459 * Warn if the source file is newer than the indexed file, except when
460 * the source file changed only seconds ago.
461 */
462 if ((dict_flags & DICT_FLAG_LOCK) != 0
463 && stat(path, &st) == 0
464 && st.st_mtime > dict_sdbm->dict.mtime
465 && st.st_mtime < time((time_t *) 0) - 100)
466 msg_warn("database %s is older than source file %s", dbm_path, path);
467
468 close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC);
469 close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC);
470 dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
471 if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
472 dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
473 if (dict_flags & DICT_FLAG_FOLD_FIX)
474 dict_sdbm->dict.fold_buf = vstring_alloc(10);
475 dict_sdbm->dbm = dbm;
476 dict_sdbm->key_buf = 0;
477 dict_sdbm->val_buf = 0;
478
479 if ((dict_flags & DICT_FLAG_LOCK))
480 myfree(dbm_path);
481
482 return (DICT_DEBUG (&dict_sdbm->dict));
483 }
484
485 #endif
486