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