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