* database.c: Removed arbitrary limit on maximum number of sessions
[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 session slot)
42  */
43 int max_keys = 0;
44 datum *dtkey;
45
46
47 /*
48  * Reclaim unused space in the databases.  We need to do each one of
49  * these discretely, rather than in a loop.
50  */
51 void defrag_databases(void) {
52
53         /* defrag the message base */
54         lprintf(7, "Defragmenting message base\n");
55         begin_critical_section(S_MSGMAIN);
56         begin_critical_section(S_DATABASE);
57         gdbm_reorganize(gdbms[CDB_MSGMAIN]);
58         end_critical_section(S_DATABASE);
59         end_critical_section(S_MSGMAIN);
60
61         /* defrag the user file, mailboxes, and user/room relationships */
62         lprintf(7, "Defragmenting user file\n");
63         begin_critical_section(S_USERSUPP);
64         begin_critical_section(S_DATABASE);
65         gdbm_reorganize(gdbms[CDB_USERSUPP]);
66         gdbm_reorganize(gdbms[CDB_VISIT]);
67         end_critical_section(S_DATABASE);
68         end_critical_section(S_USERSUPP);
69
70         /* defrag the room files and message lists */
71         lprintf(7, "Defragmenting room files and message lists\n");
72         begin_critical_section(S_QUICKROOM);
73         begin_critical_section(S_DATABASE);
74         gdbm_reorganize(gdbms[CDB_QUICKROOM]);
75         gdbm_reorganize(gdbms[CDB_MSGLISTS]);
76         end_critical_section(S_DATABASE);
77         end_critical_section(S_QUICKROOM);
78
79         /* defrag the floor table */
80         lprintf(7, "Defragmenting floor table\n");
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         lprintf(7, "%s\n", gdbm_version);
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         /*
158         end_critical_section(S_DATABASE);
159          */
160
161         }
162
163
164 /*
165  * Close all of the gdbm database files we've opened.  This can be done
166  * in a loop, since it's just a bunch of closes.
167  */
168 void close_databases(void) {
169         int a;
170
171         begin_critical_section(S_DATABASE);
172         for (a=0; a<MAXCDB; ++a) {
173                 lprintf(7, "Closing database %d\n", a);
174                 gdbm_close(gdbms[a]);
175                 }
176         end_critical_section(S_DATABASE);
177
178         for (a=0; a<max_keys; ++a) {
179                 if (dtkey[a].dptr != NULL) {
180                         phree(dtkey[a].dptr);
181                         }
182                 }
183
184         }
185
186
187 /*
188  * Store a piece of data.  Returns 0 if the operation was successful.  If a
189  * datum already exists it should be overwritten.
190  */
191 int cdb_store(int cdb,
192                 void *key, int keylen,
193                 void *data, int datalen) {
194
195         datum dkey, ddata;
196         int retval;
197
198         dkey.dsize = keylen;
199         dkey.dptr = key;
200         ddata.dsize = datalen;
201         ddata.dptr = data;
202
203         begin_critical_section(S_DATABASE);
204         retval = gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE);
205         end_critical_section(S_DATABASE);
206         if ( retval < 0 ) {
207                 lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
208                 return(-1);
209                 }
210
211         return(0);
212         }
213
214
215 /*
216  * Delete a piece of data.  Returns 0 if the operation was successful.
217  */
218 int cdb_delete(int cdb, void *key, int keylen) {
219
220         datum dkey;
221         int retval;
222
223         dkey.dsize = keylen;
224         dkey.dptr = key;
225
226         begin_critical_section(S_DATABASE);
227         retval = gdbm_delete(gdbms[cdb], dkey);
228         end_critical_section(S_DATABASE);
229         return(retval);
230
231         }
232
233
234
235
236 /*
237  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
238  * a struct cdbdata which it is the caller's responsibility to free later on
239  * using the cdb_free() routine.
240  */
241 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen) {
242         
243         struct cdbdata *tempcdb;
244         datum dkey, dret;
245         
246         dkey.dsize = keylen;
247         dkey.dptr = key;
248
249         begin_critical_section(S_DATABASE);
250         dret = gdbm_fetch(gdbms[cdb], dkey);
251         end_critical_section(S_DATABASE);
252         if (dret.dptr == NULL) {
253                 return NULL;
254                 }
255
256         tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
257         if (tempcdb == NULL) {
258                 lprintf(2, "Cannot allocate memory!\n");
259                 }
260
261         tempcdb->len = dret.dsize;
262         tempcdb->ptr = dret.dptr;
263         return(tempcdb);
264         }
265
266
267 /*
268  * Free a cdbdata item (ok, this is really no big deal, but we might need to do
269  * more complex stuff with other database managers in the future).
270  */
271 void cdb_free(struct cdbdata *cdb) {
272         phree(cdb->ptr);
273         phree(cdb);
274         }
275
276
277 /* 
278  * Prepare for a sequential search of an entire database.  (In the gdbm model,
279  * we do this by keeping an array dtkey[] of "the next" key for each session
280  * that is open.  There is guaranteed to be no more than one traversal in
281  * progress per session at any given time.)
282  */
283 void cdb_rewind(int cdb) {
284
285         while (max_keys <= CC->cs_pid) {
286                 ++max_keys;
287                 if (dtkey == NULL) {
288                         dtkey = (datum *)
289                                 mallok( (sizeof(datum) * max_keys) );
290                 }
291                 else {
292                         dtkey = (datum *)
293                                 reallok(dtkey, (sizeof(datum) * max_keys) );
294                 }
295                 dtkey[max_keys - 1].dsize = 0;
296                 dtkey[max_keys - 1].dptr = NULL;
297         }
298
299         if (dtkey[CC->cs_pid].dptr != NULL) {
300                 phree(dtkey[CC->cs_pid].dptr);
301                 }
302
303         begin_critical_section(S_DATABASE);
304         dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
305         end_critical_section(S_DATABASE);
306         }
307
308
309 /*
310  * Fetch the next item in a sequential search.  Returns a pointer to a 
311  * cdbdata structure, or NULL if we've hit the end.
312  */
313 struct cdbdata *cdb_next_item(int cdb) {
314         datum dret;
315         struct cdbdata *cdbret;
316
317         
318         if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
319                 return NULL;
320                 }
321
322         begin_critical_section(S_DATABASE);
323         dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
324         end_critical_section(S_DATABASE);
325         if (dret.dptr == NULL) {        /* bad read */
326                 phree(dtkey[CC->cs_pid].dptr);
327                 return NULL;
328                 }
329
330         cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
331         cdbret->len = dret.dsize;
332         cdbret->ptr = dret.dptr;
333
334         begin_critical_section(S_DATABASE);
335         dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
336         end_critical_section(S_DATABASE);
337         return(cdbret);
338         }