room expires
[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 <stdlib.h>
19 #include <unistd.h>
20 #include <stdio.h>
21 #include <time.h>
22 #include <ctype.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <pthread.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 
40  * session.  Maybe there's a better way?)
41  */
42 #define MAXKEYS 256
43 datum dtkey[MAXKEYS];
44
45
46 /*
47  * Reclaim unused space in the databases.  We need to do each one of
48  * these discretely, rather than in a loop.
49  */
50 void defrag_databases(void) {
51
52         /* defrag the message base */
53         begin_critical_section(S_MSGMAIN);
54         begin_critical_section(S_DATABASE);
55         gdbm_reorganize(gdbms[CDB_MSGMAIN]);
56         end_critical_section(S_DATABASE);
57         end_critical_section(S_MSGMAIN);
58
59         /* defrag the user file, mailboxes, and user/room relationships */
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         begin_critical_section(S_QUICKROOM);
69         begin_critical_section(S_DATABASE);
70         gdbm_reorganize(gdbms[CDB_QUICKROOM]);
71         gdbm_reorganize(gdbms[CDB_MSGLISTS]);
72         end_critical_section(S_DATABASE);
73         end_critical_section(S_QUICKROOM);
74
75         /* defrag the floor table */
76         begin_critical_section(S_FLOORTAB);
77         begin_critical_section(S_DATABASE);
78         gdbm_reorganize(gdbms[CDB_FLOORTAB]);
79         end_critical_section(S_DATABASE);
80         end_critical_section(S_FLOORTAB);
81         }
82
83
84 /*
85  * Open the various gdbm databases we'll be using.  Any database which
86  * does not exist should be created.
87  */
88 void open_databases(void) {
89         int a;
90
91         /*
92          * Silently try to create the database subdirectory.  If it's
93          * already there, no problem.
94          */
95         system("exec mkdir data 2>/dev/null");
96
97         begin_critical_section(S_DATABASE);
98
99         gdbms[CDB_MSGMAIN] = gdbm_open("data/msgmain.gdbm", 8192,
100                 GDBM_WRCREAT, 0600, NULL);
101         if (gdbms[CDB_MSGMAIN] == NULL) {
102                 lprintf(2, "Cannot open msgmain: %s\n",
103                         gdbm_strerror(gdbm_errno));
104                 }
105
106         gdbms[CDB_USERSUPP] = gdbm_open("data/usersupp.gdbm", 0,
107                 GDBM_WRCREAT, 0600, NULL);
108         if (gdbms[CDB_USERSUPP] == NULL) {
109                 lprintf(2, "Cannot open usersupp: %s\n",
110                         gdbm_strerror(gdbm_errno));
111                 }
112
113         gdbms[CDB_VISIT] = gdbm_open("data/visit.gdbm", 0,
114                 GDBM_WRCREAT, 0600, NULL);
115         if (gdbms[CDB_VISIT] == NULL) {
116                 lprintf(2, "Cannot open visit file: %s\n",
117                         gdbm_strerror(gdbm_errno));
118                 }
119
120         gdbms[CDB_QUICKROOM] = gdbm_open("data/quickroom.gdbm", 0,
121                 GDBM_WRCREAT, 0600, NULL);
122         if (gdbms[CDB_QUICKROOM] == NULL) {
123                 lprintf(2, "Cannot open quickroom: %s\n",
124                         gdbm_strerror(gdbm_errno));
125                 }
126
127         gdbms[CDB_FLOORTAB] = gdbm_open("data/floortab.gdbm", 0,
128                 GDBM_WRCREAT, 0600, NULL);
129         if (gdbms[CDB_FLOORTAB] == NULL) {
130                 lprintf(2, "Cannot open floortab: %s\n",
131                         gdbm_strerror(gdbm_errno));
132                 }
133
134         gdbms[CDB_MSGLISTS] = gdbm_open("data/msglists.gdbm", 0,
135                 GDBM_WRCREAT, 0600, NULL);
136         if (gdbms[CDB_MSGLISTS] == NULL) {
137                 lprintf(2, "Cannot open msglists: %s\n",
138                         gdbm_strerror(gdbm_errno));
139                 }
140
141         for (a=0; a<MAXKEYS; ++a) {
142                 dtkey[a].dsize = 0;
143                 dtkey[a].dptr = NULL;
144                 }
145
146         end_critical_section(S_DATABASE);
147
148         }
149
150
151 /*
152  * Close all of the gdbm database files we've opened.  This can be done
153  * in a loop, since it's just a bunch of closes.
154  */
155 void close_databases(void) {
156         int a;
157
158         /* Hmm... we should decide when would be a good time to defrag.
159          * Server shutdowns might be an opportune time.
160          */
161         defrag_databases();
162
163         begin_critical_section(S_DATABASE);
164         for (a=0; a<MAXCDB; ++a) {
165                 lprintf(7, "Closing database %d\n", a);
166                 gdbm_close(gdbms[a]);
167                 }
168         end_critical_section(S_DATABASE);
169
170         for (a=0; a<MAXKEYS; ++a) {
171                 if (dtkey[a].dptr != NULL) {
172                         free(dtkey[a].dptr);
173                         }
174                 }
175
176         }
177
178
179 /*
180  * Store a piece of data.  Returns 0 if the operation was successful.  If a
181  * datum already exists it should be overwritten.
182  */
183 int cdb_store(int cdb,
184                 void *key, int keylen,
185                 void *data, int datalen) {
186
187         datum dkey, ddata;
188         int retval;
189
190         dkey.dsize = keylen;
191         dkey.dptr = key;
192         ddata.dsize = datalen;
193         ddata.dptr = data;
194
195         begin_critical_section(S_DATABASE);
196         retval = gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE);
197         end_critical_section(S_DATABASE);
198         if ( retval < 0 ) {
199                 lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
200                 return(-1);
201                 }
202
203         return(0);
204         }
205
206
207 /*
208  * Delete a piece of data.  Returns 0 if the operation was successful.
209  */
210 int cdb_delete(int cdb, void *key, int keylen) {
211
212         datum dkey;
213         int retval;
214
215         dkey.dsize = keylen;
216         dkey.dptr = key;
217
218         begin_critical_section(S_DATABASE);
219         retval = gdbm_delete(gdbms[cdb], dkey);
220         end_critical_section(S_DATABASE);
221         return(retval);
222
223         }
224
225
226
227
228 /*
229  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
230  * a struct cdbdata which it is the caller's responsibility to free later on
231  * using the cdb_free() routine.
232  */
233 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen) {
234         
235         struct cdbdata *tempcdb;
236         datum dkey, dret;
237         
238         dkey.dsize = keylen;
239         dkey.dptr = key;
240
241         begin_critical_section(S_DATABASE);
242         dret = gdbm_fetch(gdbms[cdb], dkey);
243         end_critical_section(S_DATABASE);
244         if (dret.dptr == NULL) {
245                 return NULL;
246                 }
247
248         tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
249         if (tempcdb == NULL) {
250                 lprintf(2, "Cannot allocate memory!\n");
251                 }
252
253         tempcdb->len = dret.dsize;
254         tempcdb->ptr = dret.dptr;
255         return(tempcdb);
256         }
257
258
259 /*
260  * Free a cdbdata item (ok, this is really no big deal, but we might need to do
261  * more complex stuff with other database managers in the future).
262  */
263 void cdb_free(struct cdbdata *cdb) {
264         free(cdb->ptr);
265         free(cdb);
266         }
267
268
269 /* 
270  * Prepare for a sequential search of an entire database.  (In the gdbm model,
271  * we do this by keeping an array dtkey[] of "the next" key for each session
272  * that is open.  There is guaranteed to be no more than one traversal in
273  * progress per session at any given time.)
274  */
275 void cdb_rewind(int cdb) {
276
277         if (dtkey[CC->cs_pid].dptr != NULL) {
278                 free(dtkey[CC->cs_pid].dptr);
279                 }
280
281         begin_critical_section(S_DATABASE);
282         dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
283         end_critical_section(S_DATABASE);
284         }
285
286
287 /*
288  * Fetch the next item in a sequential search.  Returns a pointer to a 
289  * cdbdata structure, or NULL if we've hit the end.
290  */
291 struct cdbdata *cdb_next_item(int cdb) {
292         datum dret;
293         struct cdbdata *cdbret;
294
295         
296         if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
297                 return NULL;
298                 }
299
300         begin_critical_section(S_DATABASE);
301         dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
302         end_critical_section(S_DATABASE);
303         if (dret.dptr == NULL) {        /* bad read */
304                 free(dtkey[CC->cs_pid].dptr);
305                 return NULL;
306                 }
307
308         cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
309         cdbret->len = dret.dsize;
310         cdbret->ptr = dret.dptr;
311
312         begin_critical_section(S_DATABASE);
313         dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
314         end_critical_section(S_DATABASE);
315         return(cdbret);
316         }