* sysdep.c: call DLoader_Init() with an absolute path so that gdb can
[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                 exit(1);
115                 }
116
117         gdbms[CDB_USERSUPP] = gdbm_open("data/usersupp.gdbm", 0,
118                 GDBM_WRCREAT, 0600, NULL);
119         if (gdbms[CDB_USERSUPP] == NULL) {
120                 lprintf(2, "Cannot open usersupp: %s\n",
121                         gdbm_strerror(gdbm_errno));
122                 exit(1);
123                 }
124
125         gdbms[CDB_VISIT] = gdbm_open("data/visit.gdbm", 0,
126                 GDBM_WRCREAT, 0600, NULL);
127         if (gdbms[CDB_VISIT] == NULL) {
128                 lprintf(2, "Cannot open visit file: %s\n",
129                         gdbm_strerror(gdbm_errno));
130                 exit(1);
131                 }
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
141         gdbms[CDB_FLOORTAB] = gdbm_open("data/floortab.gdbm", 0,
142                 GDBM_WRCREAT, 0600, NULL);
143         if (gdbms[CDB_FLOORTAB] == NULL) {
144                 lprintf(2, "Cannot open floortab: %s\n",
145                         gdbm_strerror(gdbm_errno));
146                 exit(1);
147                 }
148
149         gdbms[CDB_MSGLISTS] = gdbm_open("data/msglists.gdbm", 0,
150                 GDBM_WRCREAT, 0600, NULL);
151         if (gdbms[CDB_MSGLISTS] == NULL) {
152                 lprintf(2, "Cannot open msglists: %s\n",
153                         gdbm_strerror(gdbm_errno));
154                 exit(1);
155                 }
156
157         for (a=0; a<MAXKEYS; ++a) {
158                 dtkey[a].dsize = 0;
159                 dtkey[a].dptr = NULL;
160                 }
161
162         /*
163         end_critical_section(S_DATABASE);
164          */
165
166         }
167
168
169 /*
170  * Close all of the gdbm database files we've opened.  This can be done
171  * in a loop, since it's just a bunch of closes.
172  */
173 void close_databases(void) {
174         int a;
175
176         /* Hmm... we should decide when would be a good time to defrag.
177          * Server shutdowns might be an opportune time.
178          */
179         defrag_databases();
180
181         begin_critical_section(S_DATABASE);
182         for (a=0; a<MAXCDB; ++a) {
183                 lprintf(7, "Closing database %d\n", a);
184                 gdbm_close(gdbms[a]);
185                 }
186         end_critical_section(S_DATABASE);
187
188         for (a=0; a<MAXKEYS; ++a) {
189                 if (dtkey[a].dptr != NULL) {
190                         phree(dtkey[a].dptr);
191                         }
192                 }
193
194         }
195
196
197 /*
198  * Store a piece of data.  Returns 0 if the operation was successful.  If a
199  * datum already exists it should be overwritten.
200  */
201 int cdb_store(int cdb,
202                 void *key, int keylen,
203                 void *data, int datalen) {
204
205         datum dkey, ddata;
206         int retval;
207
208         dkey.dsize = keylen;
209         dkey.dptr = key;
210         ddata.dsize = datalen;
211         ddata.dptr = data;
212
213         begin_critical_section(S_DATABASE);
214         retval = gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE);
215         end_critical_section(S_DATABASE);
216         if ( retval < 0 ) {
217                 lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
218                 return(-1);
219                 }
220
221         return(0);
222         }
223
224
225 /*
226  * Delete a piece of data.  Returns 0 if the operation was successful.
227  */
228 int cdb_delete(int cdb, void *key, int keylen) {
229
230         datum dkey;
231         int retval;
232
233         dkey.dsize = keylen;
234         dkey.dptr = key;
235
236         begin_critical_section(S_DATABASE);
237         retval = gdbm_delete(gdbms[cdb], dkey);
238         end_critical_section(S_DATABASE);
239         return(retval);
240
241         }
242
243
244
245
246 /*
247  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
248  * a struct cdbdata which it is the caller's responsibility to free later on
249  * using the cdb_free() routine.
250  */
251 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen) {
252         
253         struct cdbdata *tempcdb;
254         datum dkey, dret;
255         
256         dkey.dsize = keylen;
257         dkey.dptr = key;
258
259         begin_critical_section(S_DATABASE);
260         dret = gdbm_fetch(gdbms[cdb], dkey);
261         end_critical_section(S_DATABASE);
262         if (dret.dptr == NULL) {
263                 return NULL;
264                 }
265
266         tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
267         if (tempcdb == NULL) {
268                 lprintf(2, "Cannot allocate memory!\n");
269                 }
270
271         tempcdb->len = dret.dsize;
272         tempcdb->ptr = dret.dptr;
273         return(tempcdb);
274         }
275
276
277 /*
278  * Free a cdbdata item (ok, this is really no big deal, but we might need to do
279  * more complex stuff with other database managers in the future).
280  */
281 void cdb_free(struct cdbdata *cdb) {
282         phree(cdb->ptr);
283         phree(cdb);
284         }
285
286
287 /* 
288  * Prepare for a sequential search of an entire database.  (In the gdbm model,
289  * we do this by keeping an array dtkey[] of "the next" key for each session
290  * that is open.  There is guaranteed to be no more than one traversal in
291  * progress per session at any given time.)
292  */
293 void cdb_rewind(int cdb) {
294
295         if (dtkey[CC->cs_pid].dptr != NULL) {
296                 phree(dtkey[CC->cs_pid].dptr);
297                 }
298
299         begin_critical_section(S_DATABASE);
300         dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
301         end_critical_section(S_DATABASE);
302         }
303
304
305 /*
306  * Fetch the next item in a sequential search.  Returns a pointer to a 
307  * cdbdata structure, or NULL if we've hit the end.
308  */
309 struct cdbdata *cdb_next_item(int cdb) {
310         datum dret;
311         struct cdbdata *cdbret;
312
313         
314         if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
315                 return NULL;
316                 }
317
318         begin_critical_section(S_DATABASE);
319         dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
320         end_critical_section(S_DATABASE);
321         if (dret.dptr == NULL) {        /* bad read */
322                 phree(dtkey[CC->cs_pid].dptr);
323                 return NULL;
324                 }
325
326         cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
327         cdbret->len = dret.dsize;
328         cdbret->ptr = dret.dptr;
329
330         begin_critical_section(S_DATABASE);
331         dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
332         end_critical_section(S_DATABASE);
333         return(cdbret);
334         }