chiark / gitweb /
Merge branch 'master' of login.chiark.greenend.org.uk:public-git/inn-innduct
[inn-innduct.git] / frontends / ovdb_init.c
1 /*
2  * ovdb_init
3  *  Performs recovery on OV database, if needed
4  *  Performs upgrade of OV database, if needed and if '-u' used
5  *  Starts ovdb_monitor, if needed
6  */
7
8 #include "config.h"
9 #include "clibrary.h"
10 #include "libinn.h"
11 #include <errno.h>
12 #include <syslog.h>
13
14 #include "inn/innconf.h"
15 #include "inn/messages.h"
16 #include "ov.h"
17 #include "../storage/ovdb/ovdb.h"
18 #include "../storage/ovdb/ovdb-private.h"
19
20 #ifndef USE_BERKELEY_DB
21
22 int main(int argc UNUSED, char **argv UNUSED)
23 {
24     die("BerkeleyDB support not compiled");
25 }
26
27 #else /* USE_BERKELEY_DB */
28
29 static int open_db(DB **db, const char *name, int type)
30 {
31     int ret;
32 #if DB_VERSION_MAJOR == 2
33     DB_INFO dbinfo;
34     memset(&dbinfo, 0, sizeof dbinfo);
35
36     ret = db_open(name, type, DB_CREATE, 0666, OVDBenv, &dbinfo, db);
37     if (ret != 0) {
38         warn("db_open failed: %s", db_strerror(ret));
39         return ret;
40     }
41 #else
42     ret = db_create(db, OVDBenv, 0);
43     if (ret != 0) {
44         warn("db_create failed: %s\n", db_strerror(ret));
45         return ret;
46     }
47 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
48     ret = (*db)->open(*db, NULL, name, NULL, type, DB_CREATE, 0666);
49 #else
50     ret = (*db)->open(*db, name, NULL, type, DB_CREATE, 0666);
51 #endif
52     if (ret != 0) {
53         (*db)->close(*db, 0);
54         warn("%s->open failed: %s", name, db_strerror(ret));
55         return ret;
56     }
57 #endif
58     return 0;
59 }
60
61 /* Upgrade BerkeleyDB version */
62 static int upgrade_database(const char *name UNUSED)
63 {
64 #if DB_VERSION_MAJOR == 2
65     return 0;
66 #else
67     int ret;
68     DB *db;
69
70     ret = db_create(&db, OVDBenv, 0);
71     if (ret != 0)
72         return ret;
73
74     notice("upgrading %s...", name);
75     ret = db->upgrade(db, name, 0);
76     if (ret != 0)
77         warn("db->upgrade(%s) failed: %s", name, db_strerror(ret));
78
79     db->close(db, 0);
80     return ret;
81 #endif
82 }
83
84
85 struct groupstats {
86     ARTNUM low;
87     ARTNUM high;
88     int count;
89     int flag;
90     time_t expired;
91 };
92
93 static int v1_which_db(char *group)
94 {
95     HASH grouphash;
96     unsigned int i;
97
98     grouphash = Hash(group, strlen(group));
99     memcpy(&i, &grouphash, sizeof(i));
100     return i % ovdb_conf.numdbfiles;
101 }
102
103 /* Upgrade ovdb data format version 1 to 2 */
104 /* groupstats and groupsbyname are replaced by groupinfo */
105 static int upgrade_v1_to_v2(void)
106 {
107     DB *groupstats, *groupsbyname, *groupinfo, *vdb;
108     DBT key, val, ikey, ival;
109     DBC *cursor;
110     group_id_t gid, higid = 0, higidbang = 0;
111     struct groupinfo gi;
112     struct groupstats gs;
113     char group[MAXHEADERSIZE];
114     u_int32_t v2 = 2;
115     int ret;
116     char *p;
117
118     notice("upgrading data to version 2");
119     ret = open_db(&groupstats, "groupstats", DB_BTREE);
120     if (ret != 0)
121         return ret;
122     ret = open_db(&groupsbyname, "groupsbyname", DB_HASH);
123     if (ret != 0)
124         return ret;
125     ret = open_db(&groupinfo, "groupinfo", DB_BTREE);
126     if (ret != 0)
127         return ret;
128
129     memset(&key, 0, sizeof key);
130     memset(&val, 0, sizeof val);
131     memset(&ikey, 0, sizeof ikey);
132     memset(&ival, 0, sizeof ival);
133
134     ret = groupsbyname->cursor(groupsbyname, NULL, &cursor, 0);
135     if (ret != 0)
136         return ret;
137
138     while((ret = cursor->c_get(cursor, &key, &val, DB_NEXT)) == 0) {
139         if(key.size == 1 && *((char *)(key.data)) == '!') {
140             if(val.size == sizeof(group_id_t))
141                 memcpy(&higidbang, val.data, sizeof(group_id_t));
142             continue;
143         }
144         if(key.size >= MAXHEADERSIZE)
145             continue;
146         memcpy(group, key.data, key.size);
147         group[key.size] = 0;
148
149         if(val.size != sizeof(group_id_t))
150             continue;
151         memcpy(&gid, val.data, sizeof(group_id_t));
152         if(gid > higid)
153             higid = gid;
154         ikey.data = &gid;
155         ikey.size = sizeof(group_id_t);
156
157         ret = groupstats->get(groupstats, NULL, &ikey, &ival, 0);
158         if (ret != 0)
159             continue;
160         if(ival.size != sizeof(struct groupstats))
161             continue;
162         memcpy(&gs, ival.data, sizeof(struct groupstats));
163
164         gi.low = gs.low;
165         gi.high = gs.high;
166         gi.count = gs.count;
167         gi.flag = gs.flag;
168         gi.expired = gs.expired;
169         gi.current_gid = gi.new_gid = gid;
170         gi.current_db = gi.new_db = v1_which_db(group);
171         gi.expiregrouppid = gi.status = 0;
172
173         val.data = &gi;
174         val.size = sizeof(gi);
175         ret = groupinfo->put(groupinfo, NULL, &key, &val, 0);
176         if (ret != 0) {
177             warn("groupinfo->put failed: %s", db_strerror(ret));
178             cursor->c_close(cursor);
179             return ret;
180         }
181     }
182     cursor->c_close(cursor);
183     if(ret != DB_NOTFOUND) {
184         warn("cursor->get failed: %s", db_strerror(ret));
185         return ret;
186     }
187
188     higid++;
189     if(higidbang > higid)
190         higid = higidbang;
191
192     key.data = (char *) "!groupid_freelist";
193     key.size = sizeof("!groupid_freelist");
194     val.data = &higid;
195     val.size = sizeof(group_id_t);
196
197     ret = groupinfo->put(groupinfo, NULL, &key, &val, 0);
198     if (ret != 0) {
199         warn("groupinfo->put failed: %s", db_strerror(ret));
200         return ret;
201     }
202
203     ret = open_db(&vdb, "version", DB_BTREE);
204     if (ret != 0)
205         return ret;
206
207     key.data = (char *) "dataversion";
208     key.size = sizeof("dataversion");
209     val.data = &v2;
210     val.size = sizeof v2;
211
212     ret = vdb->put(vdb, NULL, &key, &val, 0);
213     if (ret != 0) {
214         warn("version->put failed: %s", db_strerror(ret));
215         return ret;
216     }
217
218     groupstats->close(groupstats, 0);
219     groupsbyname->close(groupsbyname, 0);
220     groupinfo->close(groupinfo, 0);
221     vdb->close(vdb, 0);
222     
223 #if DB_VERSION_MAJOR >= 3
224     ret = db_create(&groupstats, OVDBenv, 0);
225     if (ret != 0)
226         return ret;
227     groupstats->remove(groupstats, "groupstats", NULL, 0);
228     ret = db_create(&groupsbyname, OVDBenv, 0);
229     if (ret != 0)
230         return ret;
231     groupsbyname->remove(groupsbyname, "groupsbyname", NULL, 0);
232 #else
233     /* This won't work if someone changed DB_DATA_DIR in DB_CONFIG */
234     p = concatpath(ovdb_conf.home, "groupstats");
235     unlink(p);
236     free(p);
237     p = concatpath(ovdb_conf.home, "groupsbyname");
238     unlink(p);
239     free(p);
240 #endif
241
242     return 0;
243 }
244
245 static int check_upgrade(int do_upgrade)
246 {
247     int ret, i;
248     DB *db;
249     DBT key, val;
250     u_int32_t dv;
251     char name[50];
252
253     if(do_upgrade && (ret = upgrade_database("version")))
254         return ret;
255
256     ret = open_db(&db, "version", DB_BTREE);
257     if (ret != 0)
258         return ret;
259
260     memset(&key, 0, sizeof key);
261     memset(&val, 0, sizeof val);
262     key.data = (char *) "dataversion";
263     key.size = sizeof("dataversion");
264     ret = db->get(db, NULL, &key, &val, 0);
265     if (ret != 0) {
266         if(ret != DB_NOTFOUND) {
267             warn("cannot retrieve version: %s", db_strerror(ret));
268             db->close(db, 0);
269             return ret;
270         }
271     }
272     if(ret == DB_NOTFOUND || val.size != sizeof dv) {
273         dv = DATA_VERSION;
274
275         val.data = &dv;
276         val.size = sizeof dv;
277         ret = db->put(db, NULL, &key, &val, 0);
278         if (ret != 0) {
279             warn("cannot store version: %s", db_strerror(ret));
280             db->close(db, 0);
281             return ret;
282         }
283     } else
284         memcpy(&dv, val.data, sizeof dv);
285
286     key.data = (char *) "numdbfiles";
287     key.size = sizeof("numdbfiles");
288     if ((ret = db->get(db, NULL, &key, &val, 0)) == 0)
289         if(val.size == sizeof(ovdb_conf.numdbfiles))
290             memcpy(&(ovdb_conf.numdbfiles), val.data, sizeof(ovdb_conf.numdbfiles));
291     db->close(db, 0);
292
293     if(do_upgrade) {
294         if(dv == 1) {
295             ret = upgrade_database("groupstats");
296             if (ret != 0)
297                 return ret;
298             ret = upgrade_database("groupsbyname");
299             if (ret != 0)
300                 return ret;
301         } else {
302             ret = upgrade_database("groupinfo");
303             if (ret != 0)
304                 return ret;
305         }
306         ret = upgrade_database("groupaliases");
307         if (ret != 0)
308             return ret;
309         for(i = 0; i < ovdb_conf.numdbfiles; i++) {
310             snprintf(name, sizeof(name), "ov%05d", i);
311             ret = upgrade_database(name);
312             if (ret != 0)
313                 return ret;
314         }
315     }
316
317     if(dv > DATA_VERSION) {
318         warn("cannot open database: unknown version %d", dv);
319         return EINVAL;
320     }
321     if(dv < DATA_VERSION) {
322         if(do_upgrade)
323             return upgrade_v1_to_v2();
324
325         warn("database needs to be upgraded");
326         return EINVAL;
327     }
328     return 0;
329 }
330
331 int
332 upgrade_environment(void)
333 {
334     int ret;
335
336     ovdb_close_berkeleydb();
337     ret = ovdb_open_berkeleydb(OV_WRITE, OVDB_UPGRADE);
338     if (ret != 0)
339         return ret;
340 #if DB_VERSION_MAJOR >= 3
341 #if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
342     ret = OVDBenv->remove(OVDBenv, ovdb_conf.home, NULL, 0);
343 #else
344     ret = OVDBenv->remove(OVDBenv, ovdb_conf.home, 0);
345 #endif
346     if (ret != 0)
347         return ret;
348     OVDBenv = NULL;
349     ret = ovdb_open_berkeleydb(OV_WRITE, 0);
350 #endif
351     return ret;
352 }
353
354 int main(int argc, char **argv)
355 {
356     int ret, c, do_upgrade = 0, recover_only = 0, err = 0;
357     bool locked;
358     int flags;
359
360     openlog("ovdb_init", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
361     message_program_name = "ovdb_init";
362
363     if (!innconf_read(NULL))
364         exit(1);
365
366     if(strcmp(innconf->ovmethod, "ovdb"))
367         die("ovmethod not set to ovdb in inn.conf");
368
369     if(!ovdb_check_user())
370         die("command must be run as user " NEWSUSER);
371
372     chdir(innconf->pathtmp);
373     ovdb_errmode = OVDB_ERR_STDERR;
374
375     while((c = getopt(argc, argv, "ru")) != -1) {
376         switch(c) {
377         case 'r':
378             recover_only = 1;
379             break;
380         case 'u':
381             do_upgrade = 1;
382             break;
383         case '?':
384             warn("unrecognized option -%c", optopt);
385             err++;
386             break;
387         }
388     }
389     if(recover_only && do_upgrade) {
390         warn("cannot use both -r and -u at the same time");
391         err++;
392     }
393     if(err) {
394         fprintf(stderr, "Usage: ovdb_init [-r|-u]\n");
395         exit(1);
396     }
397
398     locked = ovdb_getlock(OVDB_LOCK_EXCLUSIVE);
399     if(locked) {
400         if(do_upgrade) {
401             notice("database is quiescent, upgrading");
402             flags = OVDB_RECOVER | OVDB_UPGRADE;
403         }
404         else {
405             notice("database is quiescent, running normal recovery");
406             flags = OVDB_RECOVER;
407         }
408     } else {
409         warn("database is active");
410         if(do_upgrade) {
411             warn("upgrade will not be attempted");
412             do_upgrade = 0;
413         }
414         if(recover_only)
415             die("recovery will not be attempted");
416         ovdb_getlock(OVDB_LOCK_ADMIN);
417         flags = 0;
418     }
419
420     ret = ovdb_open_berkeleydb(OV_WRITE, flags);
421     if(ret == DB_RUNRECOVERY) {
422         if(locked)
423             die("database could not be recovered");
424         else {
425             warn("database needs recovery but cannot be locked");
426             die("other processes accessing the database must exit to start"
427                 " recovery");
428         }
429     }
430     if(ret != 0)
431         die("cannot open BerkeleyDB: %s", db_strerror(ret));
432
433     if(recover_only)
434         exit(0);
435
436     if(do_upgrade) {
437         ret = upgrade_environment();
438         if(ret != 0)
439             die("cannot upgrade BerkeleyDB environment: %s", db_strerror(ret));
440     }
441
442     if(check_upgrade(do_upgrade)) {
443         ovdb_close_berkeleydb();
444         exit(1);
445     }
446
447     ovdb_close_berkeleydb();
448     ovdb_releaselock();
449
450     if(ovdb_check_pidfile(OVDB_MONITOR_PIDFILE) == false) {
451         notice("starting ovdb monitor");
452         switch(fork()) {
453         case -1:
454             sysdie("cannot fork");
455         case 0:
456             setsid();
457             execl(concatpath(innconf->pathbin, "ovdb_monitor"),
458                 "ovdb_monitor", SPACES, NULL);
459             syswarn("cannot exec ovdb_monitor");
460             _exit(1);
461         }
462         sleep(2);       /* give the monitor a chance to start */
463     } else
464         warn("ovdb_monitor already running");
465
466     if(ovdb_conf.readserver) {
467         if(ovdb_check_pidfile(OVDB_SERVER_PIDFILE) == false) {
468             notice("starting ovdb server");
469             daemonize(innconf->pathtmp);
470             execl(concatpath(innconf->pathbin, "ovdb_server"), "ovdb_server",
471                 SPACES, NULL);
472             syswarn("cannot exec ovdb_server");
473             _exit(1);
474         } else
475             warn("ovdb_server already running");
476     }
477
478     exit(0);
479 }
480 #endif /* USE_BERKELEY_DB */
481