77360c6e0cf4703d6e8c8bb838d6e41d7b1c450b
[citadel.git] / citadel / database.c
1 /*
2  * $Id$
3  *
4  * This file contains a set of abstractions that allow Citadel to plug into any
5  * record manager or database system for its data store.
6  *
7  */
8
9 /*
10  * Note that each call to a GDBM function is wrapped in an S_DATABASE critical
11  * section.  This is done because GDBM is not threadsafe.  This is the ONLY
12  * place in the entire Citadel server where any code enters two different
13  * classes of critical sections at the same time; this is why the GDBM calls
14  * are *tightly* wrapped in S_DATABASE.  Opening multiple concurrent critical
15  * sections elsewhere in the code can, and probably will, cause deadlock
16  * conditions to occur.  (Deadlock is bad.  Eliminate.)
17  */
18
19 #include "sysdep.h"
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <time.h>
24 #include <ctype.h>
25 #include <string.h>
26 #include <errno.h>
27 #ifdef HAVE_GDBM_H
28 #include <gdbm.h>
29 #endif
30 #include "citadel.h"
31 #include "server.h"
32 #include "database.h"
33 #include "sysdep_decls.h"
34
35
36 /*
37  * This array holds one gdbm handle for each Citadel database.
38  */
39 GDBM_FILE gdbms[MAXCDB];
40
41 /*
42  * We also keep these around, for sequential searches (one per session slot)
43  */
44 int max_keys = 0;
45 datum *dtkey;
46
47
48 /*
49  * Reclaim unused space in the databases.  We need to do each one of
50  * these discretely, rather than in a loop.
51  */
52 void defrag_databases(void)
53 {
54
55         /* defrag the message base */
56         lprintf(7, "Defragmenting message base\n");
57         begin_critical_section(S_MSGMAIN);
58         begin_critical_section(S_DATABASE);
59         gdbm_reorganize(gdbms[CDB_MSGMAIN]);
60         end_critical_section(S_DATABASE);
61         end_critical_section(S_MSGMAIN);
62
63         /* defrag the user file, mailboxes, and user/room relationships */
64         lprintf(7, "Defragmenting user file\n");
65         begin_critical_section(S_USERSUPP);
66         begin_critical_section(S_DATABASE);
67         gdbm_reorganize(gdbms[CDB_USERSUPP]);
68         gdbm_reorganize(gdbms[CDB_VISIT]);
69         end_critical_section(S_DATABASE);
70         end_critical_section(S_USERSUPP);
71
72         /* defrag the room files and message lists */
73         lprintf(7, "Defragmenting room files and message lists\n");
74         begin_critical_section(S_QUICKROOM);
75         begin_critical_section(S_DATABASE);
76         gdbm_reorganize(gdbms[CDB_QUICKROOM]);
77         gdbm_reorganize(gdbms[CDB_MSGLISTS]);
78         end_critical_section(S_DATABASE);
79         end_critical_section(S_QUICKROOM);
80
81         /* defrag the floor table */
82         lprintf(7, "Defragmenting floor table\n");
83         begin_critical_section(S_FLOORTAB);
84         begin_critical_section(S_DATABASE);
85         gdbm_reorganize(gdbms[CDB_FLOORTAB]);
86         end_critical_section(S_DATABASE);
87         end_critical_section(S_FLOORTAB);
88 }
89
90
91 /*
92  * Open the various gdbm databases we'll be using.  Any database which
93  * does not exist should be created.
94  */
95 void open_databases(void)
96 {
97         lprintf(7, "%s\n", gdbm_version);
98
99         /*
100          * Silently try to create the database subdirectory.  If it's
101          * already there, no problem.
102          */
103         system("exec mkdir data 2>/dev/null");
104
105         /* a critical section is unnecessary, as this function is called before
106            any other threads are created. and it causes problems on BSDI.
107
108            begin_critical_section(S_DATABASE);
109
110          */
111
112         gdbms[CDB_MSGMAIN] = gdbm_open("data/msgmain.gdbm", 8192,
113                                        GDBM_WRCREAT, 0600, NULL);
114         if (gdbms[CDB_MSGMAIN] == NULL) {
115                 lprintf(2, "Cannot open msgmain: %s\n",
116                         gdbm_strerror(gdbm_errno));
117                 exit(1);
118         }
119         gdbms[CDB_USERSUPP] = gdbm_open("data/usersupp.gdbm", 0,
120                                         GDBM_WRCREAT, 0600, NULL);
121         if (gdbms[CDB_USERSUPP] == NULL) {
122                 lprintf(2, "Cannot open usersupp: %s\n",
123                         gdbm_strerror(gdbm_errno));
124                 exit(1);
125         }
126         gdbms[CDB_VISIT] = gdbm_open("data/visit.gdbm", 0,
127                                      GDBM_WRCREAT, 0600, NULL);
128         if (gdbms[CDB_VISIT] == NULL) {
129                 lprintf(2, "Cannot open visit file: %s\n",
130                         gdbm_strerror(gdbm_errno));
131                 exit(1);
132         }
133         gdbms[CDB_QUICKROOM] = gdbm_open("data/quickroom.gdbm", 0,
134                                          GDBM_WRCREAT, 0600, NULL);
135         if (gdbms[CDB_QUICKROOM] == NULL) {
136                 lprintf(2, "Cannot open quickroom: %s\n",
137                         gdbm_strerror(gdbm_errno));
138                 exit(1);
139         }
140         gdbms[CDB_FLOORTAB] = gdbm_open("data/floortab.gdbm", 0,
141                                         GDBM_WRCREAT, 0600, NULL);
142         if (gdbms[CDB_FLOORTAB] == NULL) {
143                 lprintf(2, "Cannot open floortab: %s\n",
144                         gdbm_strerror(gdbm_errno));
145                 exit(1);
146         }
147         gdbms[CDB_MSGLISTS] = gdbm_open("data/msglists.gdbm", 0,
148                                         GDBM_WRCREAT, 0600, NULL);
149         if (gdbms[CDB_MSGLISTS] == NULL) {
150                 lprintf(2, "Cannot open msglists: %s\n",
151                         gdbm_strerror(gdbm_errno));
152                 exit(1);
153         }
154         /*
155            end_critical_section(S_DATABASE);
156          */
157
158 }
159
160
161 /*
162  * Close all of the gdbm database files we've opened.  This can be done
163  * in a loop, since it's just a bunch of closes.
164  */
165 void close_databases(void)
166 {
167         int a;
168
169         begin_critical_section(S_DATABASE);
170         for (a = 0; a < MAXCDB; ++a) {
171                 lprintf(7, "Closing database %d\n", a);
172                 gdbm_close(gdbms[a]);
173         }
174         end_critical_section(S_DATABASE);
175
176         for (a = 0; a < max_keys; ++a) {
177                 if (dtkey[a].dptr != NULL) {
178                         phree(dtkey[a].dptr);
179                 }
180         }
181
182 }
183
184
185 /*
186  * Store a piece of data.  Returns 0 if the operation was successful.  If a
187  * datum already exists it should be overwritten.
188  */
189 int cdb_store(int cdb,
190               void *key, int keylen,
191               void *data, int datalen)
192 {
193
194         datum dkey, ddata;
195         int retval;
196
197         dkey.dsize = keylen;
198         dkey.dptr = key;
199         ddata.dsize = datalen;
200         ddata.dptr = data;
201
202         begin_critical_section(S_DATABASE);
203         retval = gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE);
204         end_critical_section(S_DATABASE);
205         if (retval < 0) {
206                 lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
207                 return (-1);
208         }
209         return (0);
210 }
211
212
213 /*
214  * Delete a piece of data.  Returns 0 if the operation was successful.
215  */
216 int cdb_delete(int cdb, void *key, int keylen)
217 {
218
219         datum dkey;
220         int retval;
221
222         dkey.dsize = keylen;
223         dkey.dptr = key;
224
225         begin_critical_section(S_DATABASE);
226         retval = gdbm_delete(gdbms[cdb], dkey);
227         end_critical_section(S_DATABASE);
228         return (retval);
229
230 }
231
232
233
234
235 /*
236  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
237  * a struct cdbdata which it is the caller's responsibility to free later on
238  * using the cdb_free() routine.
239  */
240 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen)
241 {
242
243         struct cdbdata *tempcdb;
244         datum dkey, dret;
245
246         dkey.dsize = keylen;
247         dkey.dptr = key;
248
249         begin_critical_section(S_DATABASE);
250         dret = gdbm_fetch(gdbms[cdb], dkey);
251         end_critical_section(S_DATABASE);
252         if (dret.dptr == NULL) {
253                 return NULL;
254         }
255         tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
256         if (tempcdb == NULL) {
257                 lprintf(2, "Cannot allocate memory!\n");
258         }
259         tempcdb->len = dret.dsize;
260         tempcdb->ptr = dret.dptr;
261         return (tempcdb);
262 }
263
264
265 /*
266  * Free a cdbdata item (ok, this is really no big deal, but we might need to do
267  * more complex stuff with other database managers in the future).
268  */
269 void cdb_free(struct cdbdata *cdb)
270 {
271         phree(cdb->ptr);
272         phree(cdb);
273 }
274
275
276 /* 
277  * Prepare for a sequential search of an entire database.  (In the gdbm model,
278  * we do this by keeping an array dtkey[] of "the next" key for each session
279  * that is open.  There is guaranteed to be no more than one traversal in
280  * progress per session at any given time.)
281  */
282 void cdb_rewind(int cdb)
283 {
284
285         while (max_keys <= CC->cs_pid) {
286                 ++max_keys;
287                 if (dtkey == NULL) {
288                         dtkey = (datum *)
289                             mallok((sizeof(datum) * max_keys));
290                 } else {
291                         dtkey = (datum *)
292                             reallok(dtkey, (sizeof(datum) * max_keys));
293                 }
294                 dtkey[max_keys - 1].dsize = 0;
295                 dtkey[max_keys - 1].dptr = NULL;
296         }
297
298         if (dtkey[CC->cs_pid].dptr != NULL) {
299                 phree(dtkey[CC->cs_pid].dptr);
300         }
301         begin_critical_section(S_DATABASE);
302         dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
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         datum dret;
314         struct cdbdata *cdbret;
315         void *ptr = NULL;
316
317
318         if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
319                 return NULL;
320         }
321         begin_critical_section(S_DATABASE);
322         dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
323         end_critical_section(S_DATABASE);
324         if (dret.dptr == NULL) {        /* bad read */
325                 phree(dtkey[CC->cs_pid].dptr);
326                 return NULL;
327         }
328         cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
329         cdbret->len = dret.dsize;
330         cdbret->ptr = dret.dptr;
331
332         ptr = dtkey[CC->cs_pid].dptr;
333         begin_critical_section(S_DATABASE);
334         dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
335         end_critical_section(S_DATABASE);
336
337         if (ptr != NULL) {      /* Free the previous key. */
338                 free(ptr);
339         }
340
341         return (cdbret);
342 }