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