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