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