Fixed stuff I broke.
[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 "database.h"
19 #include "sysdep_decls.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, mailboxes, and user/room relationships */
47         begin_critical_section(S_USERSUPP);
48         gdbm_reorganize(gdbms[CDB_USERSUPP]);
49         gdbm_reorganize(gdbms[CDB_VISIT]);
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         /*
73          * Silently try to create the database subdirectory.  If it's
74          * already there, no problem.
75          */
76         system("exec mkdir data 2>/dev/null");
77
78         gdbms[CDB_MSGMAIN] = gdbm_open("data/msgmain.gdbm", 8192,
79                 GDBM_WRCREAT, 0600, NULL);
80         if (gdbms[CDB_MSGMAIN] == NULL) {
81                 lprintf(2, "Cannot open msgmain: %s\n",
82                         gdbm_strerror(gdbm_errno));
83                 }
84
85         gdbms[CDB_USERSUPP] = gdbm_open("data/usersupp.gdbm", 0,
86                 GDBM_WRCREAT, 0600, NULL);
87         if (gdbms[CDB_USERSUPP] == NULL) {
88                 lprintf(2, "Cannot open usersupp: %s\n",
89                         gdbm_strerror(gdbm_errno));
90                 }
91
92         gdbms[CDB_VISIT] = gdbm_open("data/visit.gdbm", 0,
93                 GDBM_WRCREAT, 0600, NULL);
94         if (gdbms[CDB_VISIT] == NULL) {
95                 lprintf(2, "Cannot open visit file: %s\n",
96                         gdbm_strerror(gdbm_errno));
97                 }
98
99         gdbms[CDB_QUICKROOM] = gdbm_open("data/quickroom.gdbm", 0,
100                 GDBM_WRCREAT, 0600, NULL);
101         if (gdbms[CDB_QUICKROOM] == NULL) {
102                 lprintf(2, "Cannot open quickroom: %s\n",
103                         gdbm_strerror(gdbm_errno));
104                 }
105
106         gdbms[CDB_FLOORTAB] = gdbm_open("data/floortab.gdbm", 0,
107                 GDBM_WRCREAT, 0600, NULL);
108         if (gdbms[CDB_FLOORTAB] == NULL) {
109                 lprintf(2, "Cannot open floortab: %s\n",
110                         gdbm_strerror(gdbm_errno));
111                 }
112
113         gdbms[CDB_MSGLISTS] = gdbm_open("data/msglists.gdbm", 0,
114                 GDBM_WRCREAT, 0600, NULL);
115         if (gdbms[CDB_MSGLISTS] == NULL) {
116                 lprintf(2, "Cannot open msglists: %s\n",
117                         gdbm_strerror(gdbm_errno));
118                 }
119
120         for (a=0; a<MAXKEYS; ++a) {
121                 dtkey[a].dsize = 0;
122                 dtkey[a].dptr = NULL;
123                 }
124
125         }
126
127
128 /*
129  * Close all of the gdbm database files we've opened.  This can be done
130  * in a loop, since it's just a bunch of closes.
131  */
132 void close_databases(void) {
133         int a;
134
135         /* Hmm... we should decide when would be a good time to defrag.
136          * Server shutdowns might be an opportune time.
137         defrag_databases(); */
138
139         for (a=0; a<MAXCDB; ++a) {
140                 lprintf(7, "Closing database %d\n", a);
141                 gdbm_close(gdbms[a]);
142                 }
143
144         for (a=0; a<MAXKEYS; ++a) {
145                 if (dtkey[a].dptr != NULL) {
146                         free(dtkey[a].dptr);
147                         }
148                 }
149
150         }
151
152
153 /*
154  * Store a piece of data.  Returns 0 if the operation was successful.  If a
155  * datum already exists it should be overwritten.
156  */
157 int cdb_store(int cdb,
158                 void *key, int keylen,
159                 void *data, int datalen) {
160
161         datum dkey, ddata;
162
163         dkey.dsize = keylen;
164         dkey.dptr = key;
165         ddata.dsize = datalen;
166         ddata.dptr = data;
167
168         if ( gdbm_store(gdbms[cdb], dkey, ddata, GDBM_REPLACE) < 0 ) {
169                 lprintf(2, "gdbm error: %s\n", gdbm_strerror(gdbm_errno));
170                 return(-1);
171                 }
172
173         return(0);
174         }
175
176
177 /*
178  * Delete a piece of data.  Returns 0 if the operation was successful.
179  */
180 int cdb_delete(int cdb, void *key, int keylen) {
181
182         datum dkey;
183
184         dkey.dsize = keylen;
185         dkey.dptr = key;
186
187         return(gdbm_delete(gdbms[cdb], dkey));
188
189         }
190
191
192
193
194 /*
195  * Fetch a piece of data.  If not found, returns NULL.  Otherwise, it returns
196  * a struct cdbdata which it is the caller's responsibility to free later on
197  * using the cdb_free() routine.
198  */
199 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen) {
200         
201         struct cdbdata *tempcdb;
202         datum dkey, dret;
203         
204         dkey.dsize = keylen;
205         dkey.dptr = key;
206
207         dret = gdbm_fetch(gdbms[cdb], dkey);
208         if (dret.dptr == NULL) {
209                 return NULL;
210                 }
211
212         tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
213         if (tempcdb == NULL) {
214                 lprintf(2, "Cannot allocate memory!\n");
215                 }
216
217         tempcdb->len = dret.dsize;
218         tempcdb->ptr = dret.dptr;
219         return(tempcdb);
220         }
221
222
223 /*
224  * Free a cdbdata item (ok, this is really no big deal, but we might need to do
225  * more complex stuff with other database managers in the future).
226  */
227 void cdb_free(struct cdbdata *cdb) {
228         free(cdb->ptr);
229         free(cdb);
230         }
231
232
233 /* 
234  * Prepare for a sequential search of an entire database.  (In the gdbm model,
235  * we do this by keeping an array dtkey[] of "the next" key for each session
236  * that is open.  There is guaranteed to be no more than one traversal in
237  * progress per session at any given time.)
238  */
239 void cdb_rewind(int cdb) {
240
241         if (dtkey[CC->cs_pid].dptr != NULL) {
242                 free(dtkey[CC->cs_pid].dptr);
243                 }
244
245         dtkey[CC->cs_pid] = gdbm_firstkey(gdbms[cdb]);
246         }
247
248
249 /*
250  * Fetch the next item in a sequential search.  Returns a pointer to a 
251  * cdbdata structure, or NULL if we've hit the end.
252  */
253 struct cdbdata *cdb_next_item(int cdb) {
254         datum dret;
255         struct cdbdata *cdbret;
256
257         
258         if (dtkey[CC->cs_pid].dptr == NULL) {   /* end of file */
259                 return NULL;
260                 }
261
262         dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
263         if (dret.dptr == NULL) {        /* bad read */
264                 free(dtkey[CC->cs_pid].dptr);
265                 return NULL;
266                 }
267
268         cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
269         cdbret->len = dret.dsize;
270         cdbret->ptr = dret.dptr;
271
272         dtkey[CC->cs_pid] = gdbm_nextkey(gdbms[cdb], dtkey[CC->cs_pid]);
273         return(cdbret);
274         }