1/*	$NetBSD: mkmap_db.c,v 1.2 2023/12/23 20:30:46 christos Exp $	*/
2
3/*++
4/* NAME
5/*	mkmap_db 3
6/* SUMMARY
7/*	create or open database, DB style
8/* SYNOPSIS
9/*	#include <dict_db.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/*	Wietse Venema
37/*	Google, Inc.
38/*	111 8th Avenue
39/*	New York, NY 10011, USA
40/*--*/
41
42/* System library. */
43
44#include <sys_defs.h>
45#include <sys/stat.h>
46#include <unistd.h>
47#include <errno.h>
48
49/* Utility library. */
50
51#include <msg.h>
52#include <mymalloc.h>
53#include <stringops.h>
54#include <dict_db.h>
55#include <myflock.h>
56#include <warn_stat.h>
57
58#ifdef HAS_DB
59#ifdef PATH_DB_H
60#include PATH_DB_H
61#else
62#include <db.h>
63#endif
64
65typedef struct MKMAP_DB {
66    MKMAP   mkmap;			/* parent class */
67    char   *lock_file;			/* path name */
68    int     lock_fd;			/* -1 or open locked file */
69} MKMAP_DB;
70
71/* mkmap_db_after_close - clean up after closing database */
72
73static void mkmap_db_after_close(MKMAP *mp)
74{
75    MKMAP_DB *mkmap = (MKMAP_DB *) mp;
76
77    if (mkmap->lock_fd >= 0 && close(mkmap->lock_fd) < 0)
78	msg_warn("close %s: %m", mkmap->lock_file);
79    myfree(mkmap->lock_file);
80}
81
82/* mkmap_db_after_open - lock newly created database */
83
84static void mkmap_db_after_open(MKMAP *mp)
85{
86    MKMAP_DB *mkmap = (MKMAP_DB *) mp;
87
88    if (mkmap->lock_fd < 0) {
89	if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0)
90	    msg_fatal("open lockfile %s: %m", mkmap->lock_file);
91	if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
92	    msg_fatal("lock %s: %m", mkmap->lock_file);
93    }
94}
95
96/* mkmap_db_before_open - lock existing database */
97
98static MKMAP *mkmap_db_before_open(const char *path,
99			          DICT *(*db_open) (const char *, int, int))
100{
101    MKMAP_DB *mkmap = (MKMAP_DB *) mymalloc(sizeof(*mkmap));
102    struct stat st;
103
104    /*
105     * Assumes that  dict_db_cache_size = var_db_create_buf was done in the
106     * caller, because this code has no access to Postfix variables.
107     */
108
109    /*
110     * Fill in the generic members.
111     */
112    mkmap->lock_file = concatenate(path, ".db", (char *) 0);
113    mkmap->mkmap.open = db_open;
114    mkmap->mkmap.after_open = mkmap_db_after_open;
115    mkmap->mkmap.after_close = mkmap_db_after_close;
116
117    /*
118     * Unfortunately, not all systems that might support db databases do
119     * support locking on open(), so we open the file before updating it.
120     *
121     * XXX Berkeley DB 4.1 refuses to open a zero-length file. This means we can
122     * open and lock only an existing file, and that we must not truncate it.
123     */
124    if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0) {
125	if (errno != ENOENT)
126	    msg_fatal("open %s: %m", mkmap->lock_file);
127    }
128
129    /*
130     * Get an exclusive lock - we're going to change the database so we can't
131     * have any spectators.
132     *
133     * XXX Horror. Berkeley DB 4.1 refuses to open a zero-length file. This
134     * means that we must examine the size while the file is locked, and that
135     * we must unlink a zero-length file while it is locked. Avoid a race
136     * condition where two processes try to open the same zero-length file
137     * and where the second process ends up deleting the wrong file.
138     */
139    else {
140	if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
141	    msg_fatal("lock %s: %m", mkmap->lock_file);
142	if (fstat(mkmap->lock_fd, &st) < 0)
143	    msg_fatal("fstat %s: %m", mkmap->lock_file);
144	if (st.st_size == 0) {
145	    if (st.st_nlink > 0) {
146		if (unlink(mkmap->lock_file) < 0)
147		    msg_fatal("cannot remove zero-length database file %s: %m",
148			      mkmap->lock_file);
149		msg_warn("removing zero-length database file: %s",
150			 mkmap->lock_file);
151	    }
152	    close(mkmap->lock_fd);
153	    mkmap->lock_fd = -1;
154	}
155    }
156
157    return (&mkmap->mkmap);
158}
159
160/* mkmap_hash_open - create or open hashed DB file */
161
162MKMAP  *mkmap_hash_open(const char *path)
163{
164    return (mkmap_db_before_open(path, dict_hash_open));
165}
166
167/* mkmap_btree_open - create or open btree DB file */
168
169MKMAP  *mkmap_btree_open(const char *path)
170{
171    return (mkmap_db_before_open(path, dict_btree_open));
172}
173
174#endif
175