f8bb3dc0580357a5679dcfe876dbdfe2e3b2098e
[citadel.git] / citadel / database_sleepycat.c
1 /*
2  * $Id$
3  *
4  * Sleepycat (Berkeley) DB driver for Citadel/UX
5  *
6  */
7
8 #include "sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <time.h>
13 #include <ctype.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <db.h>
17 #include "citadel.h"
18 #include "server.h"
19 #include "citserver.h"
20 #include "database.h"
21 #include "sysdep_decls.h"
22
23
24 /*
25  * This array holds one DB handle for each Citadel database.
26  */
27 DB *dbp[MAXCDB];
28
29 DB_ENV *dbenv;
30
31 DBC **cursorz = NULL;
32 int num_cursorz = 0;
33 #define MYCURSOR cursorz[CC->cs_pid]
34
35 /*
36  * Reclaim unused space in the databases.  We need to do each one of
37  * these discretely, rather than in a loop.
38  *
39  * This is a stub function in the Sleepycat DB backend, because there is no
40  * such API call available.
41  */
42 void defrag_databases(void)
43 {
44         /* do nothing */
45 }
46
47
48 /*
49  * Open the various databases we'll be using.  Any database which
50  * does not exist should be created.  Note that we don't need an S_DATABASE
51  * critical section here, because there aren't any active threads manipulating
52  * the database yet -- and besides, it causes problems on BSDI.
53  */
54 void open_databases(void)
55 {
56         int ret;
57         int i;
58         char dbfilename[256];
59
60         /*
61          * Silently try to create the database subdirectory.  If it's
62          * already there, no problem.
63          */
64         system("exec mkdir data 2>/dev/null");
65
66         lprintf(9, "Setting up DB environment\n");
67         ret = db_env_create(&dbenv, 0);
68         if (ret) {
69                 lprintf(1, "db_env_create: %s\n", db_strerror(ret));
70                 exit(ret);
71         }
72         dbenv->set_errpfx(dbenv, "citserver");
73
74         /*
75          * We want to specify the shared memory buffer pool cachesize,
76          * but everything else is the default.
77          */
78         ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
79         if (ret) {
80                 lprintf(1, "set_cachesize: %s\n", db_strerror(ret));
81                 dbenv->close(dbenv, 0);
82                 exit(ret);
83         }
84
85         /*
86          * We specify DB_PRIVATE but not DB_INIT_LOCK or DB_THREAD, even
87          * though this is a multithreaded application.  Since Citadel does all
88          * database access in S_DATABASE critical sections, access to the db
89          * is serialized already, so don't bother the database manager with
90          * it.  Besides, it locks up when we do it that way.
91          */
92         /* (void)dbenv->set_data_dir(dbenv, "/database/files"); */
93         ret = dbenv->open(dbenv, "./data",
94                 ( DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE ),
95                 0);
96         if (ret) {
97                 lprintf(1, "dbenv->open: %s\n", db_strerror(ret));
98                 dbenv->close(dbenv, 0);
99                 exit(ret);
100         }
101
102         lprintf(7, "Starting up DB\n");
103
104         for (i = 0; i < MAXCDB; ++i) {
105
106                 /* Create a database handle */
107                 ret = db_create(&dbp[i], dbenv, 0);
108                 if (ret) {
109                         lprintf(1, "db_create: %s\n", db_strerror(ret));
110                         exit(ret);
111                 }
112
113
114                 /* Arbitrary names for our tables -- we reference them by
115                  * number, so we don't have string names for them.
116                  */
117                 sprintf(dbfilename, "cdb.%02x", i);
118
119                 ret = dbp[i]->open(dbp[i],
120                                 dbfilename,
121                                 NULL,
122                                 DB_BTREE,
123                                 DB_CREATE,
124                                 0600);
125                 if (ret) {
126                         lprintf(1, "db_open[%d]: %s\n", i, db_strerror(ret));
127                         exit(ret);
128                 }
129
130         }
131
132 }
133
134
135 /*
136  * Close all of the gdbm database files we've opened.  This can be done
137  * in a loop, since it's just a bunch of closes.
138  */
139 void close_databases(void)
140 {
141         int a;
142         int ret;
143
144         begin_critical_section(S_DATABASE);
145         for (a = 0; a < MAXCDB; ++a) {
146                 lprintf(7, "Closing database %d\n", a);
147                 ret = dbp[a]->close(dbp[a], 0);
148                 if (ret) {
149                         lprintf(1, "db_close: %s\n", db_strerror(ret));
150                 }
151                 
152         }
153
154
155
156         /* Close the handle. */
157         ret = dbenv->close(dbenv, 0);
158         if (ret) {
159                 lprintf(1, "DBENV->close: %s\n", db_strerror(ret));
160         }
161
162
163         end_critical_section(S_DATABASE);
164
165 }
166
167
168 /*
169  * Store a piece of data.  Returns 0 if the operation was successful.  If a
170  * key already exists it should be overwritten.
171  */
172 int cdb_store(int cdb,
173               void *ckey, int ckeylen,
174               void *cdata, int cdatalen)
175 {
176
177         DBT dkey, ddata;
178         int ret;
179
180         memset(&dkey, 0, sizeof(DBT));
181         memset(&ddata, 0, sizeof(DBT));
182         dkey.size = ckeylen;
183         dkey.data = ckey;
184         ddata.size = cdatalen;
185         ddata.data = cdata;
186
187         begin_critical_section(S_DATABASE);
188         ret = dbp[cdb]->put(dbp[cdb],           /* db */
189                                 NULL,           /* transaction ID (hmm...) */
190                                 &dkey,          /* key */
191                                 &ddata,         /* data */
192                                 0);             /* flags */
193         end_critical_section(S_DATABASE);
194         if (ret) {
195                 lprintf(1, "cdb_store: %s\n", db_strerror(ret));
196                 return (-1);
197         }
198         return (0);
199 }
200
201
202 /*
203  * Delete a piece of data.  Returns 0 if the operation was successful.
204  */
205 int cdb_delete(int cdb, void *key, int keylen)
206 {
207
208         DBT dkey;
209         int ret;
210
211         dkey.size = keylen;
212         dkey.data = key;
213
214         begin_critical_section(S_DATABASE);
215         ret = dbp[cdb]->del(dbp[cdb], NULL, &dkey, 0);
216         end_critical_section(S_DATABASE);
217         return (ret);
218
219 }
220
221
222
223
224 /*
225  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
226  * a struct cdbdata which it is the caller's responsibility to free later on
227  * using the cdb_free() routine.
228  */
229 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen)
230 {
231
232         struct cdbdata *tempcdb;
233         DBT dkey, dret;
234         int ret;
235
236         memset(&dkey, 0, sizeof(DBT));
237         memset(&dret, 0, sizeof(DBT));
238         dkey.size = keylen;
239         dkey.data = key;
240         dret.flags = DB_DBT_MALLOC;
241
242         begin_critical_section(S_DATABASE);
243         ret = dbp[cdb]->get(dbp[cdb], NULL, &dkey, &dret, 0);
244         end_critical_section(S_DATABASE);
245         if ((ret != 0) && (ret != DB_NOTFOUND)) {
246                 lprintf(1, "cdb_fetch: %s\n", db_strerror(ret));
247         }
248         if (ret != 0) return NULL;
249         tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
250         if (tempcdb == NULL) {
251                 lprintf(2, "Cannot allocate memory!\n");
252         }
253         tempcdb->len = dret.size;
254         tempcdb->ptr = dret.data;
255         return (tempcdb);
256 }
257
258
259 /*
260  * Free a cdbdata item (ok, this is really no big deal, but we might need to do
261  * more complex stuff with other database managers in the future).
262  */
263 void cdb_free(struct cdbdata *cdb)
264 {
265         phree(cdb->ptr);
266         phree(cdb);
267 }
268
269
270 /* 
271  * Prepare for a sequential search of an entire database.
272  * (There is guaranteed to be no more than one traversal in
273  * progress per session at any given time.)
274  */
275 void cdb_rewind(int cdb)
276 {
277         int ret = 0;
278
279         /*
280          * Make sure we have a cursor allocated for this session
281          */
282
283         if (num_cursorz <= CC->cs_pid) {
284                 num_cursorz = CC->cs_pid + 1;
285                 if (cursorz == NULL) {
286                         cursorz = (DBC **)
287                             mallok((sizeof(DBC *) * num_cursorz));
288                 } else {
289                         cursorz = (DBC **)
290                             reallok(cursorz, (sizeof(DBC *) * num_cursorz));
291                 }
292         }
293
294
295         /*
296          * Now initialize the cursor
297          */
298         begin_critical_section(S_DATABASE);
299         ret = dbp[cdb]->cursor(dbp[cdb], NULL, &MYCURSOR, 0);
300         if (ret) {
301                 lprintf(1, "db_cursor: %s\n", db_strerror(ret));
302         }
303         end_critical_section(S_DATABASE);
304 }
305
306
307 /*
308  * Fetch the next item in a sequential search.  Returns a pointer to a 
309  * cdbdata structure, or NULL if we've hit the end.
310  */
311 struct cdbdata *cdb_next_item(int cdb)
312 {
313         DBT key, data;
314         struct cdbdata *cdbret;
315         int ret = 0;
316
317         /* Initialize the key/data pair so the flags aren't set. */
318         memset(&key, 0, sizeof(key));
319         memset(&data, 0, sizeof(data));
320         data.flags = DB_DBT_MALLOC;
321
322         begin_critical_section(S_DATABASE);
323         ret = MYCURSOR->c_get(MYCURSOR,
324                 &key, &data, DB_NEXT);
325         end_critical_section(S_DATABASE);
326         
327         if (ret) return NULL;           /* presumably, end of file */
328
329         cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
330         cdbret->len = data.size;
331         cdbret->ptr = data.data;
332
333         return (cdbret);
334 }