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