ea390bf016f73856d38cdb790bc5b691f0902a06
[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  * Close all of the gdbm database files we've opened.  This can be done
123  * in a loop, since it's just a bunch of closes.
124  */
125 void close_databases() {
126         int a;
127
128         defrag_databases();
129         for (a=0; a<MAXCDB; ++a) {
130                 lprintf(7, "Closing database %d\n", a);
131                 gdbm_close(gdbms[a]);
132                 }
133
134         for (a=0; a<MAXKEYS; ++a) {
135                 if (dtkey[a].dptr != NULL) {
136                         free(dtkey[a].dptr);
137                         }
138                 }
139
140         }
141
142
143 /*
144  * Store a piece of data.  Returns 0 if the operation was successful.  If a
145  * datum already exists it should be overwritten.
146  */
147 int cdb_store(int cdb,
148                 char *key, int keylen,
149                 char *data, int datalen) {
150
151         datum dkey, ddata;
152
153         dkey.dsize = keylen;
154         dkey.dptr = key;
155         ddata.dsize = datalen;
156         ddata.dptr = data;
157
158         if ( gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE) < 0 ) {
159                 lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
160                 return(-1);
161                 }
162
163         return(0);
164         }
165
166
167 /*
168  * Delete a piece of data.  Returns 0 if the operation was successful.
169  */
170 int cdb_delete(int cdb, char *key, int keylen) {
171
172         datum dkey;
173
174         dkey.dsize = keylen;
175         dkey.dptr = key;
176
177         return(gdbm_delete(gdbms[cdb], dkey));
178
179         }
180
181
182
183
184 /*
185  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
186  * a struct cdbdata which it is the caller's responsibility to free later on
187  * using the cdb_free() routine.
188  */
189 struct cdbdata *cdb_fetch(int cdb, char *key, int keylen) {
190         
191         struct cdbdata *tempcdb;
192         datum dkey, dret;
193         
194         dkey.dsize = keylen;
195         dkey.dptr = key;
196
197         dret = gdbm_fetch(gdbms[cdb], dkey);
198         if (dret.dptr == NULL) {
199                 return NULL;
200                 }
201
202         tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
203         if (tempcdb == NULL) {
204                 lprintf(2, "Cannot allocate memory!\n");
205                 }
206
207         tempcdb->len = dret.dsize;
208         tempcdb->ptr = dret.dptr;
209         return(tempcdb);
210         }
211
212
213 /*
214  * Free a cdbdata item (ok, this is really no big deal, but we might need to do
215  * more complex stuff with other database managers in the future).
216  */
217 void cdb_free(struct cdbdata *cdb) {
218         free(cdb->ptr);
219         free(cdb);
220         }
221
222
223 /* 
224  * Prepare for a sequential search of an entire database.  (In the gdbm model,
225  * we do this by keeping an array dtkey[] of "the next" key for each session
226  * that is open.  There is guaranteed to be no more than one traversal in
227  * progress per session at any given time.)
228  */
229 void cdb_rewind(int cdb) {
230
231         if (dtkey[CC->cs_pid].dptr != NULL) {
232                 free(dtkey[CC->cs_pid].dptr);
233                 }
234
235         dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
236         }
237
238
239 /*
240  * Fetch the next item in a sequential search.  Returns a pointer to a 
241  * cdbdata structure, or NULL if we've hit the end.
242  */
243 struct cdbdata *cdb_next_item(int cdb) {
244         datum dret;
245         struct cdbdata *cdbret;
246
247         
248         if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
249                 return NULL;
250                 }
251
252         dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
253         if (dret.dptr == NULL) {        /* bad read */
254                 free(dtkey[CC->cs_pid].dptr);
255                 return NULL;
256                 }
257
258         cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
259         cdbret->len = dret.dsize;
260         cdbret->ptr = dret.dptr;
261
262         dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
263         return(cdbret);
264         }