* Debugged all possible ways for a session to terminate; do them cleanly.
[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_GDBM_H
27 #include <gdbm.h>
28 #endif
29 #include "citadel.h"
30 #include "server.h"
31 #include "database.h"
32 #include "sysdep_decls.h"
33
34
35 /*
36  * This array holds one gdbm handle for each Citadel database.
37  */
38 GDBM_FILE gdbms[MAXCDB];
39
40 /*
41  * We also keep these around, for sequential searches... (one per 
42  * session.  Maybe there's a better way?)    FIX ... there _is_ a better
43  * way.  We have TSD functions now; use them.
44  */
45 #define MAXKEYS 256
46 datum dtkey[MAXKEYS];
47
48
49 /*
50  * Reclaim unused space in the databases.  We need to do each one of
51  * these discretely, rather than in a loop.
52  */
53 void defrag_databases(void) {
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         int a;
97
98         lprintf(7, "%s\n", gdbm_version);
99
100         /*
101          * Silently try to create the database subdirectory.  If it's
102          * already there, no problem.
103          */
104         system("exec mkdir data 2>/dev/null");
105
106         /* a critical section is unnecessary, as this function is called before
107            any other threads are created. and it causes problems on BSDI.
108
109         begin_critical_section(S_DATABASE);
110
111          */
112
113         gdbms[CDB_MSGMAIN] = gdbm_open("data/msgmain.gdbm", 8192,
114                 GDBM_WRCREAT, 0600, NULL);
115         if (gdbms[CDB_MSGMAIN] == NULL) {
116                 lprintf(2, "Cannot open msgmain: %s\n",
117                         gdbm_strerror(gdbm_errno));
118                 exit(1);
119                 }
120
121         gdbms[CDB_USERSUPP] = gdbm_open("data/usersupp.gdbm", 0,
122                 GDBM_WRCREAT, 0600, NULL);
123         if (gdbms[CDB_USERSUPP] == NULL) {
124                 lprintf(2, "Cannot open usersupp: %s\n",
125                         gdbm_strerror(gdbm_errno));
126                 exit(1);
127                 }
128
129         gdbms[CDB_VISIT] = gdbm_open("data/visit.gdbm", 0,
130                 GDBM_WRCREAT, 0600, NULL);
131         if (gdbms[CDB_VISIT] == NULL) {
132                 lprintf(2, "Cannot open visit file: %s\n",
133                         gdbm_strerror(gdbm_errno));
134                 exit(1);
135                 }
136
137         gdbms[CDB_QUICKROOM] = gdbm_open("data/quickroom.gdbm", 0,
138                 GDBM_WRCREAT, 0600, NULL);
139         if (gdbms[CDB_QUICKROOM] == NULL) {
140                 lprintf(2, "Cannot open quickroom: %s\n",
141                         gdbm_strerror(gdbm_errno));
142                 exit(1);
143                 }
144
145         gdbms[CDB_FLOORTAB] = gdbm_open("data/floortab.gdbm", 0,
146                 GDBM_WRCREAT, 0600, NULL);
147         if (gdbms[CDB_FLOORTAB] == NULL) {
148                 lprintf(2, "Cannot open floortab: %s\n",
149                         gdbm_strerror(gdbm_errno));
150                 exit(1);
151                 }
152
153         gdbms[CDB_MSGLISTS] = gdbm_open("data/msglists.gdbm", 0,
154                 GDBM_WRCREAT, 0600, NULL);
155         if (gdbms[CDB_MSGLISTS] == NULL) {
156                 lprintf(2, "Cannot open msglists: %s\n",
157                         gdbm_strerror(gdbm_errno));
158                 exit(1);
159                 }
160
161         for (a=0; a<MAXKEYS; ++a) {
162                 dtkey[a].dsize = 0;
163                 dtkey[a].dptr = NULL;
164                 }
165
166         /*
167         end_critical_section(S_DATABASE);
168          */
169
170         }
171
172
173 /*
174  * Close all of the gdbm database files we've opened.  This can be done
175  * in a loop, since it's just a bunch of closes.
176  */
177 void close_databases(void) {
178         int a;
179
180         begin_critical_section(S_DATABASE);
181         for (a=0; a<MAXCDB; ++a) {
182                 lprintf(7, "Closing database %d\n", a);
183                 gdbm_close(gdbms[a]);
184                 }
185         end_critical_section(S_DATABASE);
186
187         for (a=0; a<MAXKEYS; ++a) {
188                 if (dtkey[a].dptr != NULL) {
189                         phree(dtkey[a].dptr);
190                         }
191                 }
192
193         }
194
195
196 /*
197  * Store a piece of data.  Returns 0 if the operation was successful.  If a
198  * datum already exists it should be overwritten.
199  */
200 int cdb_store(int cdb,
201                 void *key, int keylen,
202                 void *data, int datalen) {
203
204         datum dkey, ddata;
205         int retval;
206
207         dkey.dsize = keylen;
208         dkey.dptr = key;
209         ddata.dsize = datalen;
210         ddata.dptr = data;
211
212         begin_critical_section(S_DATABASE);
213         retval = gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE);
214         end_critical_section(S_DATABASE);
215         if ( retval < 0 ) {
216                 lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
217                 return(-1);
218                 }
219
220         return(0);
221         }
222
223
224 /*
225  * Delete a piece of data.  Returns 0 if the operation was successful.
226  */
227 int cdb_delete(int cdb, void *key, int keylen) {
228
229         datum dkey;
230         int retval;
231
232         dkey.dsize = keylen;
233         dkey.dptr = key;
234
235         begin_critical_section(S_DATABASE);
236         retval = gdbm_delete(gdbms[cdb], dkey);
237         end_critical_section(S_DATABASE);
238         return(retval);
239
240         }
241
242
243
244
245 /*
246  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
247  * a struct cdbdata which it is the caller's responsibility to free later on
248  * using the cdb_free() routine.
249  */
250 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen) {
251         
252         struct cdbdata *tempcdb;
253         datum dkey, dret;
254         
255         dkey.dsize = keylen;
256         dkey.dptr = key;
257
258         begin_critical_section(S_DATABASE);
259         dret = gdbm_fetch(gdbms[cdb], dkey);
260         end_critical_section(S_DATABASE);
261         if (dret.dptr == NULL) {
262                 return NULL;
263                 }
264
265         tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
266         if (tempcdb == NULL) {
267                 lprintf(2, "Cannot allocate memory!\n");
268                 }
269
270         tempcdb->len = dret.dsize;
271         tempcdb->ptr = dret.dptr;
272         return(tempcdb);
273         }
274
275
276 /*
277  * Free a cdbdata item (ok, this is really no big deal, but we might need to do
278  * more complex stuff with other database managers in the future).
279  */
280 void cdb_free(struct cdbdata *cdb) {
281         phree(cdb->ptr);
282         phree(cdb);
283         }
284
285
286 /* 
287  * Prepare for a sequential search of an entire database.  (In the gdbm model,
288  * we do this by keeping an array dtkey[] of "the next" key for each session
289  * that is open.  There is guaranteed to be no more than one traversal in
290  * progress per session at any given time.)
291  */
292 void cdb_rewind(int cdb) {
293
294         if (dtkey[CC->cs_pid].dptr != NULL) {
295                 phree(dtkey[CC->cs_pid].dptr);
296                 }
297
298         begin_critical_section(S_DATABASE);
299         dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
300         end_critical_section(S_DATABASE);
301         }
302
303
304 /*
305  * Fetch the next item in a sequential search.  Returns a pointer to a 
306  * cdbdata structure, or NULL if we've hit the end.
307  */
308 struct cdbdata *cdb_next_item(int cdb) {
309         datum dret;
310         struct cdbdata *cdbret;
311
312         
313         if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
314                 return NULL;
315                 }
316
317         begin_critical_section(S_DATABASE);
318         dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
319         end_critical_section(S_DATABASE);
320         if (dret.dptr == NULL) {        /* bad read */
321                 phree(dtkey[CC->cs_pid].dptr);
322                 return NULL;
323                 }
324
325         cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
326         cdbret->len = dret.dsize;
327         cdbret->ptr = dret.dptr;
328
329         begin_critical_section(S_DATABASE);
330         dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
331         end_critical_section(S_DATABASE);
332         return(cdbret);
333         }