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