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