7 /* dictionary manager interface to CDB files
9 /* #include <dict_cdb.h>
11 /* DICT *dict_cdb_open(path, open_flags, dict_flags)
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).
23 /* The database pathname, not including the ".cdb" suffix.
25 /* Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC.
27 /* Flags used by the dictionary interface.
29 /* dict(3) generic dictionary manager
31 /* Fatal errors: cannot open file, write error, out of memory.
35 /* The Secure Mailer license must be distributed with this software.
37 /* Michael Tokarev <mjt@tls.msk.ru> based on dict_db.c by
39 /* IBM T.J. Watson Research
41 /* Yorktown Heights, NY 10598, USA
54 /* Utility library. */
59 #include "stringops.h"
62 #include "stringops.h"
69 #ifndef TINYCDB_VERSION
73 #define cdb_fileno(c) ((c)->fd)
77 #define CDB_SUFFIX ".cdb"
79 #ifndef CDB_TMP_SUFFIX
80 #define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp"
83 /* Application-specific. */
86 DICT dict
; /* generic members */
87 struct cdb cdb
; /* cdb structure */
88 } DICT_CDBQ
; /* query interface */
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 */
97 /* dict_cdbq_lookup - find database entry, query mode */
99 static const char *dict_cdbq_lookup(DICT
*dict
, const char *name
)
101 DICT_CDBQ
*dict_cdbq
= (DICT_CDBQ
*) dict
;
106 const char *result
= 0;
110 /* CDB is constant, so do not try to acquire a lock. */
113 * Optionally fold the key.
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
));
123 * See if this CDB file was written with one null byte appended to key
126 if (dict
->flags
& DICT_FLAG_TRY1NULL
) {
127 status
= cdb_find(&dict_cdbq
->cdb
, name
, strlen(name
) + 1);
129 dict
->flags
&= ~DICT_FLAG_TRY0NULL
;
133 * See if this CDB file was written with no null byte appended to key and
136 if (status
== 0 && (dict
->flags
& DICT_FLAG_TRY0NULL
)) {
137 status
= cdb_find(&dict_cdbq
->cdb
, name
, strlen(name
));
139 dict
->flags
&= ~DICT_FLAG_TRY1NULL
;
142 msg_fatal("error reading %s: %m", dict
->name
);
145 vlen
= cdb_datalen(&dict_cdbq
->cdb
);
148 buf
= mymalloc(vlen
+ 1);
150 buf
= myrealloc(buf
, vlen
+ 1);
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
);
159 /* No locking so not release the lock. */
164 /* dict_cdbq_close - close data base, query mode */
166 static void dict_cdbq_close(DICT
*dict
)
168 DICT_CDBQ
*dict_cdbq
= (DICT_CDBQ
*) dict
;
170 cdb_free(&dict_cdbq
->cdb
);
171 close(dict
->stat_fd
);
173 vstring_free(dict
->fold_buf
);
177 /* dict_cdbq_open - open data base, query mode */
179 static DICT
*dict_cdbq_open(const char *path
, int dict_flags
)
181 DICT_CDBQ
*dict_cdbq
;
186 cdb_path
= concatenate(path
, CDB_SUFFIX
, (char *) 0);
188 if ((fd
= open(cdb_path
, O_RDONLY
)) < 0)
189 msg_fatal("open database %s: %m", cdb_path
);
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
);
197 cdb_init(&(dict_cdbq
->cdb
), fd
);
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
);
208 * Warn if the source file is newer than the indexed file, except when
209 * the source file changed only seconds ago.
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
);
217 * If undecided about appending a null byte to key and value, choose to
218 * try both in query mode.
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);
227 return (&dict_cdbq
->dict
);
230 /* dict_cdbm_update - add database entry, create mode */
232 static void dict_cdbm_update(DICT
*dict
, const char *name
, const char *value
)
234 DICT_CDBM
*dict_cdbm
= (DICT_CDBM
*) dict
;
235 unsigned ksize
, vsize
;
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
));
247 ksize
= strlen(name
);
248 vsize
= strlen(value
);
251 * Optionally append a null byte to key and value.
253 if (dict
->flags
& DICT_FLAG_TRY1NULL
) {
259 * Do the add operation. No locking is done.
261 #ifdef TINYCDB_VERSION
263 #error please upgrate tinycdb to at least 0.5 version
265 if (dict
->flags
& DICT_FLAG_DUP_IGNORE
)
267 else if (dict
->flags
& DICT_FLAG_DUP_REPLACE
)
271 r
= cdb_make_put(&dict_cdbm
->cdbm
, name
, ksize
, value
, vsize
, r
);
273 msg_fatal("error writing %s: %m", dict_cdbm
->tmp_path
);
275 if (dict
->flags
& (DICT_FLAG_DUP_IGNORE
| DICT_FLAG_DUP_REPLACE
))
277 else if (dict
->flags
& DICT_FLAG_DUP_WARN
)
278 msg_warn("%s: duplicate entry: \"%s\"",
279 dict_cdbm
->dict
.name
, name
);
281 msg_fatal("%s: duplicate entry: \"%s\"",
282 dict_cdbm
->dict
.name
, name
);
285 if (cdb_make_add(&dict_cdbm
->cdbm
, name
, ksize
, value
, vsize
) < 0)
286 msg_fatal("error writing %s: %m", dict_cdbm
->tmp_path
);
290 /* dict_cdbm_close - close data base and rename file.tmp to file.cdb */
292 static void dict_cdbm_close(DICT
*dict
)
294 DICT_CDBM
*dict_cdbm
= (DICT_CDBM
*) dict
;
295 int fd
= cdb_fileno(&dict_cdbm
->cdbm
);
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.
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
);
313 vstring_free(dict
->fold_buf
);
317 /* dict_cdbm_open - create database as file.tmp */
319 static DICT
*dict_cdbm_open(const char *path
, int dict_flags
)
321 DICT_CDBM
*dict_cdbm
;
325 struct stat st0
, st1
;
327 cdb_path
= concatenate(path
, CDB_SUFFIX
, (char *) 0);
328 tmp_path
= concatenate(path
, CDB_TMP_SUFFIX
, (char *) 0);
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.
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
);
343 * Get an exclusive lock - we're going to change the database so we
344 * can't have any spectators.
346 if (myflock(fd
, INTERNAL_LOCK
, MYFLOCK_OP_EXCLUSIVE
) < 0)
347 msg_fatal("lock %s: %m", tmp_path
);
349 if (stat(tmp_path
, &st1
) < 0)
350 msg_fatal("stat(%s): %m", tmp_path
);
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...
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
359 break; /* successefully opened */
370 dict_cdbm
= (DICT_CDBM
*) dict_alloc(DICT_TYPE_CDB
, path
,
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
);
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.
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);
393 return (&dict_cdbm
->dict
);
396 /* dict_cdb_open - open data base for query mode or create mode */
398 DICT
*dict_cdb_open(const char *path
, int open_flags
, int dict_flags
)
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
);
407 msg_fatal("dict_cdb_open: inappropriate open flags for cdb database"
408 " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC");