These are the changes to eliminate most of the arbitrary limits in the
[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
20
21 /*
22  * This array holds one gdbm handle for each Citadel database.
23  */
24 GDBM_FILE gdbms[MAXCDB];
25
26 /*
27  * We also keep these around, for sequential searches... (one per 
28  * session.  Maybe there's a better way?)
29  */
30 #define MAXKEYS 256
31 datum dtkey[MAXKEYS];
32
33
34 /*
35  * Reclaim unused space in the databases.  We need to do each one of
36  * these discretely, rather than in a loop.
37  */
38 void defrag_databases() {
39
40         /* defrag the message base */
41         begin_critical_section(S_MSGMAIN);
42         gdbm_reorganize(gdbms[CDB_MSGMAIN]);
43         end_critical_section(S_MSGMAIN);
44
45         /* defrag the user file and mailboxes */
46         begin_critical_section(S_USERSUPP);
47         gdbm_reorganize(gdbms[CDB_USERSUPP]);
48         gdbm_reorganize(gdbms[CDB_MAILBOXES]);
49         end_critical_section(S_USERSUPP);
50
51         /* defrag the room files and message lists */
52         begin_critical_section(S_QUICKROOM);
53         gdbm_reorganize(gdbms[CDB_QUICKROOM]);
54         gdbm_reorganize(gdbms[CDB_MSGLISTS]);
55         end_critical_section(S_QUICKROOM);
56
57         /* defrag the floor table */
58         begin_critical_section(S_FLOORTAB);
59         gdbm_reorganize(gdbms[CDB_FLOORTAB]);
60         end_critical_section(S_FLOORTAB);
61         }
62
63
64 /*
65  * Open the various gdbm databases we'll be using.  Any database which
66  * does not exist should be created.
67  */
68 void open_databases() {
69         int a;
70
71         gdbms[CDB_MSGMAIN] = gdbm_open("msgmain.gdbm", 8192,
72                 GDBM_WRCREAT, 0600, NULL);
73         if (gdbms[CDB_MSGMAIN] == NULL) {
74                 lprintf(2, "Cannot open msgmain: %s\n",
75                         gdbm_strerror(gdbm_errno));
76                 }
77
78         gdbms[CDB_USERSUPP] = gdbm_open("usersupp.gdbm", 0,
79                 GDBM_WRCREAT, 0600, NULL);
80         if (gdbms[CDB_USERSUPP] == NULL) {
81                 lprintf(2, "Cannot open usersupp: %s\n",
82                         gdbm_strerror(gdbm_errno));
83                 }
84
85         gdbms[CDB_QUICKROOM] = gdbm_open("quickroom.gdbm", 0,
86                 GDBM_WRCREAT, 0600, NULL);
87         if (gdbms[CDB_QUICKROOM] == NULL) {
88                 lprintf(2, "Cannot open quickroom: %s\n",
89                         gdbm_strerror(gdbm_errno));
90                 }
91
92         gdbms[CDB_FLOORTAB] = gdbm_open("floortab.gdbm", 0,
93                 GDBM_WRCREAT, 0600, NULL);
94         if (gdbms[CDB_FLOORTAB] == NULL) {
95                 lprintf(2, "Cannot open floortab: %s\n",
96                         gdbm_strerror(gdbm_errno));
97                 }
98
99         gdbms[CDB_MSGLISTS] = gdbm_open("msglists.gdbm", 0,
100                 GDBM_WRCREAT, 0600, NULL);
101         if (gdbms[CDB_MSGLISTS] == NULL) {
102                 lprintf(2, "Cannot open msglists: %s\n",
103                         gdbm_strerror(gdbm_errno));
104                 }
105
106         gdbms[CDB_MAILBOXES] = gdbm_open("mailboxes.gdbm", 0,
107                 GDBM_WRCREAT, 0600, NULL);
108         if (gdbms[CDB_MAILBOXES] == NULL) {
109                 lprintf(2, "Cannot open mailboxes: %s\n",
110                         gdbm_strerror(gdbm_errno));
111                 }
112
113         for (a=0; a<MAXKEYS; ++a) {
114                 dtkey[a].dsize = 0;
115                 dtkey[a].dptr = NULL;
116                 }
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() {
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                 char *key, int keylen,
150                 char *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, char *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, char *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         }