Postfix3.3.1
dict_cdb.c
[詳解]
1 /*++
2 /* NAME
3 /* dict_cdb 3
4 /* SUMMARY
5 /* dictionary manager interface to CDB files
6 /* SYNOPSIS
7 /* #include <dict_cdb.h>
8 /*
9 /* DICT *dict_cdb_open(path, open_flags, dict_flags)
10 /* const char *path;
11 /* int open_flags;
12 /* int dict_flags;
13 /*
14 /* DESCRIPTION
15 /* dict_cdb_open() opens the specified CDB database. The result is
16 /* a pointer to a structure that can be used to access the dictionary
17 /* using the generic methods documented in dict_open(3).
18 /*
19 /* Arguments:
20 /* .IP path
21 /* The database pathname, not including the ".cdb" suffix.
22 /* .IP open_flags
23 /* Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC.
24 /* .IP dict_flags
25 /* Flags used by the dictionary interface.
26 /* SEE ALSO
27 /* dict(3) generic dictionary manager
28 /* DIAGNOSTICS
29 /* Fatal errors: cannot open file, write error, out of memory.
30 /* LICENSE
31 /* .ad
32 /* .fi
33 /* The Secure Mailer license must be distributed with this software.
34 /* AUTHOR(S)
35 /* Michael Tokarev <mjt@tls.msk.ru> based on dict_db.c by
36 /* Wietse Venema
37 /* IBM T.J. Watson Research
38 /* P.O. Box 704
39 /* Yorktown Heights, NY 10598, USA
40 /*--*/
41 
42 #include "sys_defs.h"
43 
44 /* System library. */
45 
46 #include <sys/stat.h>
47 #include <limits.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <stdio.h>
51 
52 /* Utility library. */
53 
54 #include "msg.h"
55 #include "mymalloc.h"
56 #include "vstring.h"
57 #include "stringops.h"
58 #include "iostuff.h"
59 #include "myflock.h"
60 #include "stringops.h"
61 #include "dict.h"
62 #include "dict_cdb.h"
63 #include "warn_stat.h"
64 
65 #ifdef HAS_CDB
66 
67 #include <cdb.h>
68 #ifndef TINYCDB_VERSION
69 #include <cdb_make.h>
70 #endif
71 #ifndef cdb_fileno
72 #define cdb_fileno(c) ((c)->fd)
73 #endif
74 
75 #ifndef CDB_SUFFIX
76 #define CDB_SUFFIX ".cdb"
77 #endif
78 #ifndef CDB_TMP_SUFFIX
79 #define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp"
80 #endif
81 
82 /* Application-specific. */
83 
84 typedef struct {
85  DICT dict; /* generic members */
86  struct cdb cdb; /* cdb structure */
87 } DICT_CDBQ; /* query interface */
88 
89 typedef struct {
90  DICT dict; /* generic members */
91  struct cdb_make cdbm; /* cdb_make structure */
92  char *cdb_path; /* cdb pathname (.cdb) */
93  char *tmp_path; /* temporary pathname (.tmp) */
94 } DICT_CDBM; /* rebuild interface */
95 
96 /* dict_cdbq_lookup - find database entry, query mode */
97 
98 static const char *dict_cdbq_lookup(DICT *dict, const char *name)
99 {
100  DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
101  unsigned vlen;
102  int status = 0;
103  static char *buf;
104  static unsigned len;
105  const char *result = 0;
106 
107  dict->error = 0;
108 
109  /* CDB is constant, so do not try to acquire a lock. */
110 
111  /*
112  * Optionally fold the key.
113  */
114  if (dict->flags & DICT_FLAG_FOLD_FIX) {
115  if (dict->fold_buf == 0)
116  dict->fold_buf = vstring_alloc(10);
117  vstring_strcpy(dict->fold_buf, name);
118  name = lowercase(vstring_str(dict->fold_buf));
119  }
120 
121  /*
122  * See if this CDB file was written with one null byte appended to key
123  * and value.
124  */
125  if (dict->flags & DICT_FLAG_TRY1NULL) {
126  status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1);
127  if (status > 0)
128  dict->flags &= ~DICT_FLAG_TRY0NULL;
129  }
130 
131  /*
132  * See if this CDB file was written with no null byte appended to key and
133  * value.
134  */
135  if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
136  status = cdb_find(&dict_cdbq->cdb, name, strlen(name));
137  if (status > 0)
138  dict->flags &= ~DICT_FLAG_TRY1NULL;
139  }
140  if (status < 0)
141  msg_fatal("error reading %s: %m", dict->name);
142 
143  if (status) {
144  vlen = cdb_datalen(&dict_cdbq->cdb);
145  if (len < vlen) {
146  if (buf == 0)
147  buf = mymalloc(vlen + 1);
148  else
149  buf = myrealloc(buf, vlen + 1);
150  len = vlen;
151  }
152  if (cdb_read(&dict_cdbq->cdb, buf, vlen,
153  cdb_datapos(&dict_cdbq->cdb)) < 0)
154  msg_fatal("error reading %s: %m", dict->name);
155  buf[vlen] = '\0';
156  result = buf;
157  }
158  /* No locking so not release the lock. */
159 
160  return (result);
161 }
162 
163 /* dict_cdbq_close - close data base, query mode */
164 
165 static void dict_cdbq_close(DICT *dict)
166 {
167  DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
168 
169  cdb_free(&dict_cdbq->cdb);
170  close(dict->stat_fd);
171  if (dict->fold_buf)
172  vstring_free(dict->fold_buf);
173  dict_free(dict);
174 }
175 
176 /* dict_cdbq_open - open data base, query mode */
177 
178 static DICT *dict_cdbq_open(const char *path, int dict_flags)
179 {
180  DICT_CDBQ *dict_cdbq;
181  struct stat st;
182  char *cdb_path;
183  int fd;
184 
185  /*
186  * Let the optimizer worry about eliminating redundant code.
187  */
188 #define DICT_CDBQ_OPEN_RETURN(d) do { \
189  DICT *__d = (d); \
190  myfree(cdb_path); \
191  return (__d); \
192  } while (0)
193 
194  cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
195 
196  if ((fd = open(cdb_path, O_RDONLY)) < 0)
197  DICT_CDBQ_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path,
198  O_RDONLY, dict_flags,
199  "open database %s: %m", cdb_path));
200 
201  dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB,
202  cdb_path, sizeof(*dict_cdbq));
203 #if defined(TINYCDB_VERSION)
204  if (cdb_init(&(dict_cdbq->cdb), fd) != 0)
205  msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path);
206 #else
207  cdb_init(&(dict_cdbq->cdb), fd);
208 #endif
209  dict_cdbq->dict.lookup = dict_cdbq_lookup;
210  dict_cdbq->dict.close = dict_cdbq_close;
211  dict_cdbq->dict.stat_fd = fd;
212  if (fstat(fd, &st) < 0)
213  msg_fatal("dict_dbq_open: fstat: %m");
214  dict_cdbq->dict.mtime = st.st_mtime;
215  dict_cdbq->dict.owner.uid = st.st_uid;
216  dict_cdbq->dict.owner.status = (st.st_uid != 0);
218 
219  /*
220  * Warn if the source file is newer than the indexed file, except when
221  * the source file changed only seconds ago.
222  */
223  if (stat(path, &st) == 0
224  && st.st_mtime > dict_cdbq->dict.mtime
225  && st.st_mtime < time((time_t *) 0) - 100)
226  msg_warn("database %s is older than source file %s", cdb_path, path);
227 
228  /*
229  * If undecided about appending a null byte to key and value, choose to
230  * try both in query mode.
231  */
232  if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
233  dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL;
234  dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED;
235  if (dict_flags & DICT_FLAG_FOLD_FIX)
236  dict_cdbq->dict.fold_buf = vstring_alloc(10);
237 
238  DICT_CDBQ_OPEN_RETURN(DICT_DEBUG (&dict_cdbq->dict));
239 }
240 
241 /* dict_cdbm_update - add database entry, create mode */
242 
243 static int dict_cdbm_update(DICT *dict, const char *name, const char *value)
244 {
245  DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
246  unsigned ksize, vsize;
247  int r;
248 
249  dict->error = 0;
250 
251  /*
252  * Optionally fold the key.
253  */
254  if (dict->flags & DICT_FLAG_FOLD_FIX) {
255  if (dict->fold_buf == 0)
256  dict->fold_buf = vstring_alloc(10);
257  vstring_strcpy(dict->fold_buf, name);
258  name = lowercase(vstring_str(dict->fold_buf));
259  }
260  ksize = strlen(name);
261  vsize = strlen(value);
262 
263  /*
264  * Optionally append a null byte to key and value.
265  */
266  if (dict->flags & DICT_FLAG_TRY1NULL) {
267  ksize++;
268  vsize++;
269  }
270 
271  /*
272  * Do the add operation. No locking is done.
273  */
274 #ifdef TINYCDB_VERSION
275 #ifndef CDB_PUT_ADD
276 #error please upgrate tinycdb to at least 0.5 version
277 #endif
278  if (dict->flags & DICT_FLAG_DUP_IGNORE)
279  r = CDB_PUT_ADD;
280  else if (dict->flags & DICT_FLAG_DUP_REPLACE)
281  r = CDB_PUT_REPLACE;
282  else
283  r = CDB_PUT_INSERT;
284  r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r);
285  if (r < 0)
286  msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
287  else if (r > 0) {
289  /* void */ ;
290  else if (dict->flags & DICT_FLAG_DUP_WARN)
291  msg_warn("%s: duplicate entry: \"%s\"",
292  dict_cdbm->dict.name, name);
293  else
294  msg_fatal("%s: duplicate entry: \"%s\"",
295  dict_cdbm->dict.name, name);
296  }
297  return (r);
298 #else
299  if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0)
300  msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
301  return (0);
302 #endif
303 }
304 
305 /* dict_cdbm_close - close data base and rename file.tmp to file.cdb */
306 
307 static void dict_cdbm_close(DICT *dict)
308 {
309  DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
310  int fd = cdb_fileno(&dict_cdbm->cdbm);
311 
312  /*
313  * Note: if FCNTL locking is used, closing any file descriptor on a
314  * locked file cancels all locks that the process may have on that file.
315  * CDB is FCNTL locking safe, because it uses the same file descriptor
316  * for database I/O and locking.
317  */
318  if (cdb_make_finish(&dict_cdbm->cdbm) < 0)
319  msg_fatal("finish database %s: %m", dict_cdbm->tmp_path);
320  if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0)
321  msg_fatal("rename database from %s to %s: %m",
322  dict_cdbm->tmp_path, dict_cdbm->cdb_path);
323  if (close(fd) < 0) /* releases a lock */
324  msg_fatal("close database %s: %m", dict_cdbm->cdb_path);
325  myfree(dict_cdbm->cdb_path);
326  myfree(dict_cdbm->tmp_path);
327  if (dict->fold_buf)
328  vstring_free(dict->fold_buf);
329  dict_free(dict);
330 }
331 
332 /* dict_cdbm_open - create database as file.tmp */
333 
334 static DICT *dict_cdbm_open(const char *path, int dict_flags)
335 {
336  DICT_CDBM *dict_cdbm;
337  char *cdb_path;
338  char *tmp_path;
339  int fd;
340  struct stat st0, st1;
341 
342  /*
343  * Let the optimizer worry about eliminating redundant code.
344  */
345 #define DICT_CDBM_OPEN_RETURN(d) do { \
346  DICT *__d = (d); \
347  if (cdb_path) \
348  myfree(cdb_path); \
349  if (tmp_path) \
350  myfree(tmp_path); \
351  return (__d); \
352  } while (0)
353 
354  cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
355  tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0);
356 
357  /*
358  * Repeat until we have opened *and* locked *existing* file. Since the
359  * new (tmp) file will be renamed to be .cdb file, locking here is
360  * somewhat funny to work around possible race conditions. Note that we
361  * can't open a file with O_TRUNC as we can't know if another process
362  * isn't creating it at the same time.
363  */
364  for (;;) {
365  if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0)
366  DICT_CDBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path,
367  O_RDWR, dict_flags,
368  "open database %s: %m",
369  tmp_path));
370  if (fstat(fd, &st0) < 0)
371  msg_fatal("fstat(%s): %m", tmp_path);
372 
373  /*
374  * Get an exclusive lock - we're going to change the database so we
375  * can't have any spectators.
376  */
377  if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
378  msg_fatal("lock %s: %m", tmp_path);
379 
380  if (stat(tmp_path, &st1) < 0)
381  msg_fatal("stat(%s): %m", tmp_path);
382 
383  /*
384  * Compare file's state before and after lock: should be the same,
385  * and nlinks should be >0, or else we opened non-existing file...
386  */
387  if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev
388  && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink
389  && st0.st_nlink > 0)
390  break; /* successefully opened */
391 
392  close(fd);
393 
394  }
395 
396 #ifndef NO_FTRUNCATE
397  if (st0.st_size)
398  ftruncate(fd, 0);
399 #endif
400 
401  dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path,
402  sizeof(*dict_cdbm));
403  if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0)
404  msg_fatal("initialize database %s: %m", tmp_path);
405  dict_cdbm->dict.close = dict_cdbm_close;
406  dict_cdbm->dict.update = dict_cdbm_update;
407  dict_cdbm->cdb_path = cdb_path;
408  dict_cdbm->tmp_path = tmp_path;
409  cdb_path = tmp_path = 0; /* DICT_CDBM_OPEN_RETURN() */
410  dict_cdbm->dict.owner.uid = st1.st_uid;
411  dict_cdbm->dict.owner.status = (st1.st_uid != 0);
413 
414  /*
415  * If undecided about appending a null byte to key and value, choose a
416  * default to not append a null byte when creating a cdb.
417  */
418  if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
419  dict_flags |= DICT_FLAG_TRY0NULL;
420  else if ((dict_flags & DICT_FLAG_TRY1NULL)
421  && (dict_flags & DICT_FLAG_TRY0NULL))
422  dict_flags &= ~DICT_FLAG_TRY0NULL;
423  dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
424  if (dict_flags & DICT_FLAG_FOLD_FIX)
425  dict_cdbm->dict.fold_buf = vstring_alloc(10);
426 
427  DICT_CDBM_OPEN_RETURN(DICT_DEBUG (&dict_cdbm->dict));
428 }
429 
430 /* dict_cdb_open - open data base for query mode or create mode */
431 
432 DICT *dict_cdb_open(const char *path, int open_flags, int dict_flags)
433 {
434  switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) {
435  case O_RDONLY: /* query mode */
436  return dict_cdbq_open(path, dict_flags);
437  case O_WRONLY | O_CREAT | O_TRUNC: /* create mode */
438  case O_RDWR | O_CREAT | O_TRUNC: /* sloppiness */
439  return dict_cdbm_open(path, dict_flags);
440  default:
441  msg_fatal("dict_cdb_open: inappropriate open flags for cdb database"
442  " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC");
443  }
444 }
445 
446 #endif /* HAS_CDB */
#define DICT_FLAG_DUP_IGNORE
Definition: dict.h:111
void myfree(void *ptr)
Definition: mymalloc.c:207
#define vstring_str(vp)
Definition: vstring.h:71
char * name
Definition: dict.h:80
#define stat(p, s)
Definition: warn_stat.h:18
#define DICT_FLAG_FIXED
Definition: dict.h:114
void * myrealloc(void *ptr, ssize_t len)
Definition: mymalloc.c:175
int flags
Definition: dict.h:81
#define MYFLOCK_OP_EXCLUSIVE
Definition: myflock.h:30
#define DICT_FLAG_FOLD_FIX
Definition: dict.h:124
VSTRING * vstring_strcpy(VSTRING *vp, const char *src)
Definition: vstring.c:431
Definition: dict.h:78
int stat_fd
Definition: dict.h:90
#define DICT_FLAG_DUP_REPLACE
Definition: dict.h:117
#define DICT_FLAG_TRY1NULL
Definition: dict.h:113
void msg_warn(const char *fmt,...)
Definition: msg.c:215
VSTRING * vstring_alloc(ssize_t len)
Definition: vstring.c:353
int myflock(int fd, int lock_style, int operation)
Definition: myflock.c:87
int error
Definition: dict.h:94
char * lowercase(char *string)
Definition: lowercase.c:34
#define DICT_FLAG_DUP_WARN
Definition: dict.h:110
NORETURN msg_fatal(const char *fmt,...)
Definition: msg.c:249
void dict_free(DICT *)
Definition: dict_alloc.c:163
char * concatenate(const char *arg0,...)
Definition: concatenate.c:42
VSTRING * vstring_free(VSTRING *vp)
Definition: vstring.c:380
#define CLOSE_ON_EXEC
Definition: iostuff.h:51
DICT * dict_alloc(const char *, const char *, ssize_t)
Definition: dict_alloc.c:135
VSTRING * fold_buf
Definition: dict.h:92
#define DICT_TYPE_CDB
Definition: dict_cdb.h:22
#define DICT_FLAG_TRY0NULL
Definition: dict.h:112
int close_on_exec(int fd, int on)
Definition: close_on_exec.c:49
DICT * dict_cdb_open(const char *, int, int)
DICT * dict_surrogate(const char *dict_type, const char *dict_name, int open_flags, int dict_flags, const char *fmt,...)
#define fstat(f, s)
Definition: warn_stat.h:20
void * mymalloc(ssize_t len)
Definition: mymalloc.c:150