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