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