/* * ovdb_init * Performs recovery on OV database, if needed * Performs upgrade of OV database, if needed and if '-u' used * Starts ovdb_monitor, if needed */ #include "config.h" #include "clibrary.h" #include "libinn.h" #include #include #include "inn/innconf.h" #include "inn/messages.h" #include "ov.h" #include "../storage/ovdb/ovdb.h" #include "../storage/ovdb/ovdb-private.h" #ifndef USE_BERKELEY_DB int main(int argc UNUSED, char **argv UNUSED) { die("BerkeleyDB support not compiled"); } #else /* USE_BERKELEY_DB */ static int open_db(DB **db, const char *name, int type) { int ret; #if DB_VERSION_MAJOR == 2 DB_INFO dbinfo; memset(&dbinfo, 0, sizeof dbinfo); ret = db_open(name, type, DB_CREATE, 0666, OVDBenv, &dbinfo, db); if (ret != 0) { warn("db_open failed: %s", db_strerror(ret)); return ret; } #else ret = db_create(db, OVDBenv, 0); if (ret != 0) { warn("db_create failed: %s\n", db_strerror(ret)); return ret; } #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1) ret = (*db)->open(*db, NULL, name, NULL, type, DB_CREATE, 0666); #else ret = (*db)->open(*db, name, NULL, type, DB_CREATE, 0666); #endif if (ret != 0) { (*db)->close(*db, 0); warn("%s->open failed: %s", name, db_strerror(ret)); return ret; } #endif return 0; } /* Upgrade BerkeleyDB version */ static int upgrade_database(const char *name UNUSED) { #if DB_VERSION_MAJOR == 2 return 0; #else int ret; DB *db; ret = db_create(&db, OVDBenv, 0); if (ret != 0) return ret; notice("upgrading %s...", name); ret = db->upgrade(db, name, 0); if (ret != 0) warn("db->upgrade(%s) failed: %s", name, db_strerror(ret)); db->close(db, 0); return ret; #endif } struct groupstats { ARTNUM low; ARTNUM high; int count; int flag; time_t expired; }; static int v1_which_db(char *group) { HASH grouphash; unsigned int i; grouphash = Hash(group, strlen(group)); memcpy(&i, &grouphash, sizeof(i)); return i % ovdb_conf.numdbfiles; } /* Upgrade ovdb data format version 1 to 2 */ /* groupstats and groupsbyname are replaced by groupinfo */ static int upgrade_v1_to_v2(void) { DB *groupstats, *groupsbyname, *groupinfo, *vdb; DBT key, val, ikey, ival; DBC *cursor; group_id_t gid, higid = 0, higidbang = 0; struct groupinfo gi; struct groupstats gs; char group[MAXHEADERSIZE]; u_int32_t v2 = 2; int ret; char *p; notice("upgrading data to version 2"); ret = open_db(&groupstats, "groupstats", DB_BTREE); if (ret != 0) return ret; ret = open_db(&groupsbyname, "groupsbyname", DB_HASH); if (ret != 0) return ret; ret = open_db(&groupinfo, "groupinfo", DB_BTREE); if (ret != 0) return ret; memset(&key, 0, sizeof key); memset(&val, 0, sizeof val); memset(&ikey, 0, sizeof ikey); memset(&ival, 0, sizeof ival); ret = groupsbyname->cursor(groupsbyname, NULL, &cursor, 0); if (ret != 0) return ret; while((ret = cursor->c_get(cursor, &key, &val, DB_NEXT)) == 0) { if(key.size == 1 && *((char *)(key.data)) == '!') { if(val.size == sizeof(group_id_t)) memcpy(&higidbang, val.data, sizeof(group_id_t)); continue; } if(key.size >= MAXHEADERSIZE) continue; memcpy(group, key.data, key.size); group[key.size] = 0; if(val.size != sizeof(group_id_t)) continue; memcpy(&gid, val.data, sizeof(group_id_t)); if(gid > higid) higid = gid; ikey.data = &gid; ikey.size = sizeof(group_id_t); ret = groupstats->get(groupstats, NULL, &ikey, &ival, 0); if (ret != 0) continue; if(ival.size != sizeof(struct groupstats)) continue; memcpy(&gs, ival.data, sizeof(struct groupstats)); gi.low = gs.low; gi.high = gs.high; gi.count = gs.count; gi.flag = gs.flag; gi.expired = gs.expired; gi.current_gid = gi.new_gid = gid; gi.current_db = gi.new_db = v1_which_db(group); gi.expiregrouppid = gi.status = 0; val.data = &gi; val.size = sizeof(gi); ret = groupinfo->put(groupinfo, NULL, &key, &val, 0); if (ret != 0) { warn("groupinfo->put failed: %s", db_strerror(ret)); cursor->c_close(cursor); return ret; } } cursor->c_close(cursor); if(ret != DB_NOTFOUND) { warn("cursor->get failed: %s", db_strerror(ret)); return ret; } higid++; if(higidbang > higid) higid = higidbang; key.data = (char *) "!groupid_freelist"; key.size = sizeof("!groupid_freelist"); val.data = &higid; val.size = sizeof(group_id_t); ret = groupinfo->put(groupinfo, NULL, &key, &val, 0); if (ret != 0) { warn("groupinfo->put failed: %s", db_strerror(ret)); return ret; } ret = open_db(&vdb, "version", DB_BTREE); if (ret != 0) return ret; key.data = (char *) "dataversion"; key.size = sizeof("dataversion"); val.data = &v2; val.size = sizeof v2; ret = vdb->put(vdb, NULL, &key, &val, 0); if (ret != 0) { warn("version->put failed: %s", db_strerror(ret)); return ret; } groupstats->close(groupstats, 0); groupsbyname->close(groupsbyname, 0); groupinfo->close(groupinfo, 0); vdb->close(vdb, 0); #if DB_VERSION_MAJOR >= 3 ret = db_create(&groupstats, OVDBenv, 0); if (ret != 0) return ret; groupstats->remove(groupstats, "groupstats", NULL, 0); ret = db_create(&groupsbyname, OVDBenv, 0); if (ret != 0) return ret; groupsbyname->remove(groupsbyname, "groupsbyname", NULL, 0); #else /* This won't work if someone changed DB_DATA_DIR in DB_CONFIG */ p = concatpath(ovdb_conf.home, "groupstats"); unlink(p); free(p); p = concatpath(ovdb_conf.home, "groupsbyname"); unlink(p); free(p); #endif return 0; } static int check_upgrade(int do_upgrade) { int ret, i; DB *db; DBT key, val; u_int32_t dv; char name[50]; if(do_upgrade && (ret = upgrade_database("version"))) return ret; ret = open_db(&db, "version", DB_BTREE); if (ret != 0) return ret; memset(&key, 0, sizeof key); memset(&val, 0, sizeof val); key.data = (char *) "dataversion"; key.size = sizeof("dataversion"); ret = db->get(db, NULL, &key, &val, 0); if (ret != 0) { if(ret != DB_NOTFOUND) { warn("cannot retrieve version: %s", db_strerror(ret)); db->close(db, 0); return ret; } } if(ret == DB_NOTFOUND || val.size != sizeof dv) { dv = DATA_VERSION; val.data = &dv; val.size = sizeof dv; ret = db->put(db, NULL, &key, &val, 0); if (ret != 0) { warn("cannot store version: %s", db_strerror(ret)); db->close(db, 0); return ret; } } else memcpy(&dv, val.data, sizeof dv); key.data = (char *) "numdbfiles"; key.size = sizeof("numdbfiles"); if ((ret = db->get(db, NULL, &key, &val, 0)) == 0) if(val.size == sizeof(ovdb_conf.numdbfiles)) memcpy(&(ovdb_conf.numdbfiles), val.data, sizeof(ovdb_conf.numdbfiles)); db->close(db, 0); if(do_upgrade) { if(dv == 1) { ret = upgrade_database("groupstats"); if (ret != 0) return ret; ret = upgrade_database("groupsbyname"); if (ret != 0) return ret; } else { ret = upgrade_database("groupinfo"); if (ret != 0) return ret; } ret = upgrade_database("groupaliases"); if (ret != 0) return ret; for(i = 0; i < ovdb_conf.numdbfiles; i++) { snprintf(name, sizeof(name), "ov%05d", i); ret = upgrade_database(name); if (ret != 0) return ret; } } if(dv > DATA_VERSION) { warn("cannot open database: unknown version %d", dv); return EINVAL; } if(dv < DATA_VERSION) { if(do_upgrade) return upgrade_v1_to_v2(); warn("database needs to be upgraded"); return EINVAL; } return 0; } int upgrade_environment(void) { int ret; ovdb_close_berkeleydb(); ret = ovdb_open_berkeleydb(OV_WRITE, OVDB_UPGRADE); if (ret != 0) return ret; #if DB_VERSION_MAJOR >= 3 #if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0 ret = OVDBenv->remove(OVDBenv, ovdb_conf.home, NULL, 0); #else ret = OVDBenv->remove(OVDBenv, ovdb_conf.home, 0); #endif if (ret != 0) return ret; OVDBenv = NULL; ret = ovdb_open_berkeleydb(OV_WRITE, 0); #endif return ret; } int main(int argc, char **argv) { int ret, c, do_upgrade = 0, recover_only = 0, err = 0; bool locked; int flags; openlog("ovdb_init", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG); message_program_name = "ovdb_init"; if (!innconf_read(NULL)) exit(1); if(strcmp(innconf->ovmethod, "ovdb")) die("ovmethod not set to ovdb in inn.conf"); if(!ovdb_check_user()) die("command must be run as user " NEWSUSER); chdir(innconf->pathtmp); ovdb_errmode = OVDB_ERR_STDERR; while((c = getopt(argc, argv, "ru")) != -1) { switch(c) { case 'r': recover_only = 1; break; case 'u': do_upgrade = 1; break; case '?': warn("unrecognized option -%c", optopt); err++; break; } } if(recover_only && do_upgrade) { warn("cannot use both -r and -u at the same time"); err++; } if(err) { fprintf(stderr, "Usage: ovdb_init [-r|-u]\n"); exit(1); } locked = ovdb_getlock(OVDB_LOCK_EXCLUSIVE); if(locked) { if(do_upgrade) { notice("database is quiescent, upgrading"); flags = OVDB_RECOVER | OVDB_UPGRADE; } else { notice("database is quiescent, running normal recovery"); flags = OVDB_RECOVER; } } else { warn("database is active"); if(do_upgrade) { warn("upgrade will not be attempted"); do_upgrade = 0; } if(recover_only) die("recovery will not be attempted"); ovdb_getlock(OVDB_LOCK_ADMIN); flags = 0; } ret = ovdb_open_berkeleydb(OV_WRITE, flags); if(ret == DB_RUNRECOVERY) { if(locked) die("database could not be recovered"); else { warn("database needs recovery but cannot be locked"); die("other processes accessing the database must exit to start" " recovery"); } } if(ret != 0) die("cannot open BerkeleyDB: %s", db_strerror(ret)); if(recover_only) exit(0); if(do_upgrade) { ret = upgrade_environment(); if(ret != 0) die("cannot upgrade BerkeleyDB environment: %s", db_strerror(ret)); } if(check_upgrade(do_upgrade)) { ovdb_close_berkeleydb(); exit(1); } ovdb_close_berkeleydb(); ovdb_releaselock(); if(ovdb_check_pidfile(OVDB_MONITOR_PIDFILE) == false) { notice("starting ovdb monitor"); switch(fork()) { case -1: sysdie("cannot fork"); case 0: setsid(); execl(concatpath(innconf->pathbin, "ovdb_monitor"), "ovdb_monitor", SPACES, NULL); syswarn("cannot exec ovdb_monitor"); _exit(1); } sleep(2); /* give the monitor a chance to start */ } else warn("ovdb_monitor already running"); if(ovdb_conf.readserver) { if(ovdb_check_pidfile(OVDB_SERVER_PIDFILE) == false) { notice("starting ovdb server"); switch(fork()) { case -1: sysdie("cannot fork"); case 0: setsid(); execl(concatpath(innconf->pathbin, "ovdb_server"), "ovdb_server", SPACES, NULL); syswarn("cannot exec ovdb_server"); _exit(1); } } else warn("ovdb_server already running"); } exit(0); } #endif /* USE_BERKELEY_DB */