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