1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	mkmap_db 3
6/* SUMMARY
7/*	create or open database, DB style
8/* SYNOPSIS
9/*	#include <mkmap.h>
10/*
11/*	MKMAP	*mkmap_hash_open(path)
12/*	const char *path;
13/*
14/*	MKMAP	*mkmap_btree_open(path)
15/*	const char *path;
16/* DESCRIPTION
17/*	This module implements support for creating DB databases.
18/*
19/*	mkmap_hash_open() and mkmap_btree_open() take a file name,
20/*	append the ".db" suffix, and do whatever initialization is
21/*	required before the Berkeley DB open routine is called.
22/*
23/*	All errors are fatal.
24/* SEE ALSO
25/*	dict_db(3), DB dictionary interface.
26/* LICENSE
27/* .ad
28/* .fi
29/*	The Secure Mailer license must be distributed with this software.
30/* AUTHOR(S)
31/*	Wietse Venema
32/*	IBM T.J. Watson Research
33/*	P.O. Box 704
34/*	Yorktown Heights, NY 10598, USA
35/*--*/
36
37/* System library. */
38
39#include <sys_defs.h>
40#include <sys/stat.h>
41#include <unistd.h>
42#include <errno.h>
43
44/* Utility library. */
45
46#include <msg.h>
47#include <mymalloc.h>
48#include <stringops.h>
49#include <dict.h>
50#include <dict_db.h>
51#include <myflock.h>
52
53/* Global library. */
54
55#include <mail_params.h>
56
57/* Application-specific. */
58
59#include "mkmap.h"
60
61#ifdef HAS_DB
62#ifdef PATH_DB_H
63#include PATH_DB_H
64#else
65#include <db.h>
66#endif
67
68typedef struct MKMAP_DB {
69    MKMAP   mkmap;			/* parent class */
70    char   *lock_file;			/* path name */
71    int     lock_fd;			/* -1 or open locked file */
72} MKMAP_DB;
73
74/* mkmap_db_after_close - clean up after closing database */
75
76static void mkmap_db_after_close(MKMAP *mp)
77{
78    MKMAP_DB *mkmap = (MKMAP_DB *) mp;
79
80    if (mkmap->lock_fd >= 0 && close(mkmap->lock_fd) < 0)
81	msg_warn("close %s: %m", mkmap->lock_file);
82    myfree(mkmap->lock_file);
83}
84
85/* mkmap_db_after_open - lock newly created database */
86
87static void mkmap_db_after_open(MKMAP *mp)
88{
89    MKMAP_DB *mkmap = (MKMAP_DB *) mp;
90
91    if (mkmap->lock_fd < 0) {
92	if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0)
93	    msg_fatal("open lockfile %s: %m", mkmap->lock_file);
94	if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
95	    msg_fatal("lock %s: %m", mkmap->lock_file);
96    }
97}
98
99/* mkmap_db_before_open - lock existing database */
100
101static MKMAP *mkmap_db_before_open(const char *path,
102			          DICT *(*db_open) (const char *, int, int))
103{
104    MKMAP_DB *mkmap = (MKMAP_DB *) mymalloc(sizeof(*mkmap));
105    struct stat st;
106
107    /*
108     * Override the default per-table cache size for map (re)builds.
109     *
110     * db_cache_size" is defined in util/dict_db.c and defaults to 128kB, which
111     * works well for the lookup code.
112     *
113     * We use a larger per-table cache when building ".db" files. For "hash"
114     * files performance degrades rapidly unless the memory pool is O(file
115     * size).
116     *
117     * For "btree" files peformance is good with sorted input even for small
118     * memory pools, but with random input degrades rapidly unless the memory
119     * pool is O(file size).
120     *
121     * XXX This should be specified via the DICT interface so that the buffer
122     * size becomes an object property, instead of being specified by poking
123     * a global variable so that it becomes a class property.
124     */
125    dict_db_cache_size = var_db_create_buf;
126
127    /*
128     * Fill in the generic members.
129     */
130    mkmap->lock_file = concatenate(path, ".db", (char *) 0);
131    mkmap->mkmap.open = db_open;
132    mkmap->mkmap.after_open = mkmap_db_after_open;
133    mkmap->mkmap.after_close = mkmap_db_after_close;
134
135    /*
136     * Unfortunately, not all systems that might support db databases do
137     * support locking on open(), so we open the file before updating it.
138     *
139     * XXX Berkeley DB 4.1 refuses to open a zero-length file. This means we can
140     * open and lock only an existing file, and that we must not truncate it.
141     */
142    if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0) {
143	if (errno != ENOENT)
144	    msg_fatal("open %s: %m", mkmap->lock_file);
145    }
146
147    /*
148     * Get an exclusive lock - we're going to change the database so we can't
149     * have any spectators.
150     *
151     * XXX Horror. Berkeley DB 4.1 refuses to open a zero-length file. This
152     * means that we must examine the size while the file is locked, and that
153     * we must unlink a zero-length file while it is locked. Avoid a race
154     * condition where two processes try to open the same zero-length file
155     * and where the second process ends up deleting the wrong file.
156     */
157    else {
158	if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
159	    msg_fatal("lock %s: %m", mkmap->lock_file);
160	if (fstat(mkmap->lock_fd, &st) < 0)
161	    msg_fatal("fstat %s: %m", mkmap->lock_file);
162	if (st.st_size == 0) {
163	    if (st.st_nlink > 0) {
164		if (unlink(mkmap->lock_file) < 0)
165		    msg_fatal("cannot remove zero-length database file %s: %m",
166			      mkmap->lock_file);
167		msg_warn("removing zero-length database file: %s",
168			 mkmap->lock_file);
169	    }
170	    close(mkmap->lock_fd);
171	    mkmap->lock_fd = -1;
172	}
173    }
174
175    return (&mkmap->mkmap);
176}
177
178/* mkmap_hash_open - create or open hashed DB file */
179
180MKMAP  *mkmap_hash_open(const char *path)
181{
182    return (mkmap_db_before_open(path, dict_hash_open));
183}
184
185/* mkmap_btree_open - create or open btree DB file */
186
187MKMAP  *mkmap_btree_open(const char *path)
188{
189    return (mkmap_db_before_open(path, dict_btree_open));
190}
191
192#endif
193