- added a new function to the database interface, cdb_close_cursor(). always
[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
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
25 # include <time.h>
26 #else
27 # if HAVE_SYS_TIME_H
28 #  include <sys/time.h>
29 # else
30 #  include <time.h>
31 # endif
32 #endif
33
34 #include <ctype.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <gdbm.h>
38 #include "citadel.h"
39 #include "server.h"
40 #include "database.h"
41 #include "sysdep_decls.h"
42
43
44 /*
45  * This array holds one gdbm handle for each Citadel database.
46  */
47 GDBM_FILE gdbms[MAXCDB];
48
49 /*
50  * We also keep these around, for sequential searches (one per session slot)
51  */
52 int max_keys = 0;
53 datum *dtkey;
54
55
56 /*
57  * Reclaim unused space in the databases.  We need to do each one of
58  * these discretely, rather than in a loop.
59  */
60 void defrag_databases(void)
61 {
62
63         /* defrag the message base */
64         lprintf(7, "Defragmenting message base\n");
65         begin_critical_section(S_DATABASE);
66         gdbm_reorganize(gdbms[CDB_MSGMAIN]);
67         end_critical_section(S_DATABASE);
68
69         /* defrag the user file, mailboxes, and user/room relationships */
70         lprintf(7, "Defragmenting user file\n");
71         begin_critical_section(S_USERSUPP);
72         begin_critical_section(S_DATABASE);
73         gdbm_reorganize(gdbms[CDB_USERSUPP]);
74         gdbm_reorganize(gdbms[CDB_VISIT]);
75         end_critical_section(S_DATABASE);
76         end_critical_section(S_USERSUPP);
77
78         /* defrag the room files and message lists */
79         lprintf(7, "Defragmenting room files and message lists\n");
80         begin_critical_section(S_QUICKROOM);
81         begin_critical_section(S_DATABASE);
82         gdbm_reorganize(gdbms[CDB_QUICKROOM]);
83         gdbm_reorganize(gdbms[CDB_MSGLISTS]);
84         end_critical_section(S_DATABASE);
85         end_critical_section(S_QUICKROOM);
86
87         /* defrag the floor table */
88         lprintf(7, "Defragmenting floor table\n");
89         begin_critical_section(S_FLOORTAB);
90         begin_critical_section(S_DATABASE);
91         gdbm_reorganize(gdbms[CDB_FLOORTAB]);
92         end_critical_section(S_DATABASE);
93         end_critical_section(S_FLOORTAB);
94 }
95
96
97 /*
98  * Open the various gdbm databases we'll be using.  Any database which
99  * does not exist should be created.
100  */
101 void open_databases(void)
102 {
103         lprintf(7, "%s\n", gdbm_version);
104
105         /*
106          * Silently try to create the database subdirectory.  If it's
107          * already there, no problem.
108          */
109         system("exec mkdir data 2>/dev/null");
110
111         /* a critical section is unnecessary, as this function is called before
112            any other threads are created. and it causes problems on BSDI.
113
114            begin_critical_section(S_DATABASE);
115
116          */
117
118         gdbms[CDB_MSGMAIN] = gdbm_open("data/msgmain.gdbm", 8192,
119                                        GDBM_WRCREAT, 0600, NULL);
120         if (gdbms[CDB_MSGMAIN] == NULL) {
121                 lprintf(2, "Cannot open msgmain: %s\n",
122                         gdbm_strerror(gdbm_errno));
123                 exit(1);
124         }
125         gdbms[CDB_USERSUPP] = gdbm_open("data/usersupp.gdbm", 0,
126                                         GDBM_WRCREAT, 0600, NULL);
127         if (gdbms[CDB_USERSUPP] == NULL) {
128                 lprintf(2, "Cannot open usersupp: %s\n",
129                         gdbm_strerror(gdbm_errno));
130                 exit(1);
131         }
132         gdbms[CDB_VISIT] = gdbm_open("data/visit.gdbm", 0,
133                                      GDBM_WRCREAT, 0600, NULL);
134         if (gdbms[CDB_VISIT] == NULL) {
135                 lprintf(2, "Cannot open visit file: %s\n",
136                         gdbm_strerror(gdbm_errno));
137                 exit(1);
138         }
139         gdbms[CDB_QUICKROOM] = gdbm_open("data/quickroom.gdbm", 0,
140                                          GDBM_WRCREAT, 0600, NULL);
141         if (gdbms[CDB_QUICKROOM] == NULL) {
142                 lprintf(2, "Cannot open quickroom: %s\n",
143                         gdbm_strerror(gdbm_errno));
144                 exit(1);
145         }
146         gdbms[CDB_FLOORTAB] = gdbm_open("data/floortab.gdbm", 0,
147                                         GDBM_WRCREAT, 0600, NULL);
148         if (gdbms[CDB_FLOORTAB] == NULL) {
149                 lprintf(2, "Cannot open floortab: %s\n",
150                         gdbm_strerror(gdbm_errno));
151                 exit(1);
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            end_critical_section(S_DATABASE);
162          */
163
164 }
165
166
167 /*
168  * Close all of the gdbm database files we've opened.  This can be done
169  * in a loop, since it's just a bunch of closes.
170  */
171 void close_databases(void)
172 {
173         int a;
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 < max_keys; ++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
200         datum dkey, ddata;
201         int retval;
202
203         dkey.dsize = keylen;
204         dkey.dptr = key;
205         ddata.dsize = datalen;
206         ddata.dptr = data;
207
208         begin_critical_section(S_DATABASE);
209         retval = gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE);
210         end_critical_section(S_DATABASE);
211         if (retval < 0) {
212                 lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
213                 return (-1);
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
225         datum dkey;
226         int retval;
227
228         dkey.dsize = keylen;
229         dkey.dptr = key;
230
231         begin_critical_section(S_DATABASE);
232         retval = gdbm_delete(gdbms[cdb], dkey);
233         end_critical_section(S_DATABASE);
234         return (retval);
235
236 }
237
238
239
240
241 /*
242  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
243  * a struct cdbdata which it is the caller's responsibility to free later on
244  * using the cdb_free() routine.
245  */
246 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen)
247 {
248
249         struct cdbdata *tempcdb;
250         datum dkey, dret;
251
252         dkey.dsize = keylen;
253         dkey.dptr = key;
254
255         begin_critical_section(S_DATABASE);
256         dret = gdbm_fetch(gdbms[cdb], dkey);
257         end_critical_section(S_DATABASE);
258         if (dret.dptr == NULL) {
259                 return NULL;
260         }
261         tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
262         if (tempcdb == NULL) {
263                 lprintf(2, "Cannot allocate memory!\n");
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 {
277         phree(cdb->ptr);
278         phree(cdb);
279 }
280
281 void cdb_close_cursor(cdb)
282 {
283         while (max_keys <= CC->cs_pid) {
284                 ++max_keys;
285                 if (dtkey == NULL) {
286                         dtkey = (datum *)
287                             mallok((sizeof(datum) * max_keys));
288                 } else {
289                         dtkey = (datum *)
290                             reallok(dtkey, (sizeof(datum) * max_keys));
291                 }
292                 dtkey[max_keys - 1].dsize = 0;
293                 dtkey[max_keys - 1].dptr = NULL;
294         }
295
296         if (dtkey[CC->cs_pid].dptr != NULL) {
297                 phree(dtkey[CC->cs_pid].dptr);
298         }
299         dtkey[CC->cs_pid].dptr = NULL;
300         dtkey[CC->cs_pid].dsize = 0;
301 }
302
303
304 /* 
305  * Prepare for a sequential search of an entire database.  (In the gdbm model,
306  * we do this by keeping an array dtkey[] of "the next" key for each session
307  * that is open.  There is guaranteed to be no more than one traversal in
308  * progress per session at any given time.)
309  */
310 void cdb_rewind(int cdb)
311 {
312
313         while (max_keys <= CC->cs_pid) {
314                 ++max_keys;
315                 if (dtkey == NULL) {
316                         dtkey = (datum *)
317                             mallok((sizeof(datum) * max_keys));
318                 } else {
319                         dtkey = (datum *)
320                             reallok(dtkey, (sizeof(datum) * max_keys));
321                 }
322                 dtkey[max_keys - 1].dsize = 0;
323                 dtkey[max_keys - 1].dptr = NULL;
324         }
325
326         if (dtkey[CC->cs_pid].dptr != NULL) {
327                 phree(dtkey[CC->cs_pid].dptr);
328         }
329         begin_critical_section(S_DATABASE);
330         dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
331         end_critical_section(S_DATABASE);
332 }
333
334
335 /*
336  * Fetch the next item in a sequential search.  Returns a pointer to a 
337  * cdbdata structure, or NULL if we've hit the end.
338  */
339 struct cdbdata *cdb_next_item(int cdb)
340 {
341         datum dret;
342         struct cdbdata *cdbret;
343         void *ptr = NULL;
344
345
346         if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
347                 return NULL;
348         }
349         begin_critical_section(S_DATABASE);
350         dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
351         end_critical_section(S_DATABASE);
352         if (dret.dptr == NULL) {        /* bad read */
353                 phree(dtkey[CC->cs_pid].dptr);
354                 return NULL;
355         }
356         cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
357         cdbret->len = dret.dsize;
358         cdbret->ptr = dret.dptr;
359
360         ptr = dtkey[CC->cs_pid].dptr;
361         begin_critical_section(S_DATABASE);
362         dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
363         end_critical_section(S_DATABASE);
364
365         if (ptr != NULL) {      /* Free the previous key. */
366                 free(ptr);
367         }
368
369         return (cdbret);
370 }
371
372
373 /*
374  * empty functions because GDBM doesn't have transaction support
375  */
376
377 void cdb_begin_transaction(void) {
378 }
379
380 void cdb_end_transaction(void) {
381 }
382
383 void cdb_allocate_tsd(void) {
384 }
385
386 void cdb_free_tsd(void) {
387 }
388
389 void cdb_check_handles(void) {
390 }