/*
 * ovdb_monitor
 *   Performs database maintenance tasks
 *   + Transaction checkpoints
 *   + Deadlock detection
 *   + Transaction log removal
 */

#include "config.h"
#include "clibrary.h"
#include "portable/setproctitle.h"
#include "portable/wait.h"
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>

#include "inn/innconf.h"
#include "inn/messages.h"
#include "libinn.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)
{
    exit(0);
}

#else /* USE_BERKELEY_DB */

static int signalled = 0;
static void sigfunc(int sig UNUSED)
{
    signalled = 1;
}


static pid_t deadlockpid = 0;
static pid_t checkpointpid = 0;
static pid_t logremoverpid = 0;

static int putpid(const char *path)
{
    char buf[30];
    int fd = open(path, O_WRONLY|O_TRUNC|O_CREAT, 0664);
    if(!fd) {
        syswarn("cannot open %s", path);
	return -1;
    }
    snprintf(buf, sizeof(buf), "%d\n", getpid());
    if(write(fd, buf, strlen(buf)) < 0) {
        syswarn("cannot write to %s", path);
	close(fd);
	return -1;
    }
    close(fd);
    return 0;
}

static void deadlock(void)
{
    int ret, status = 0;
    u_int32_t atype = DB_LOCK_YOUNGEST;

    if(ovdb_open_berkeleydb(OV_WRITE, 0))
	_exit(1);

    setproctitle("deadlock");

    while(!signalled) {
#if DB_VERSION_MAJOR == 2
	ret = lock_detect(OVDBenv->lk_info, 0, atype);
#elif DB_VERSION_MAJOR == 3
	ret = lock_detect(OVDBenv, 0, atype, NULL);
#else
	ret = OVDBenv->lock_detect(OVDBenv, 0, atype, NULL);
#endif
	if(ret != 0) {
            warn("OVDB: lock_detect: %s", db_strerror(ret));
	    status = 1;
	    break;
	}
	sleep(30);
    }

    ovdb_close_berkeleydb();
    _exit(status);
}

static void checkpoint(void)
{
    int ret, status = 0;
    DB *db;
#if DB_VERSION_MAJOR == 2
    DB_INFO dbinfo;
#endif

    if(ovdb_open_berkeleydb(OV_WRITE, 0))
	_exit(1);

    setproctitle("checkpoint");

    /* Open a database and close it.  This is so a necessary initialization
       gets performed (by the db->open function).  */

#if DB_VERSION_MAJOR == 2
    memset(&dbinfo, 0, sizeof dbinfo);
    ret = db_open("version", DB_BTREE, DB_CREATE, 0666, OVDBenv, &dbinfo, &db);
    if (ret != 0) {
        warn("OVDB: checkpoint: db_open failed: %s", db_strerror(ret));
        _exit(1);
    }
#else
    ret = db_create(&db, OVDBenv, 0);
    if (ret != 0) {
        warn("OVDB: checkpoint: db_create: %s", db_strerror(ret));
        _exit(1);
    }
#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
    ret = db->open(db, NULL, "version", NULL, DB_BTREE, DB_CREATE, 0666);
#else
    ret = db->open(db, "version", NULL, DB_BTREE, DB_CREATE, 0666);
#endif
    if (ret != 0) {
        db->close(db, 0);
        warn("OVDB: checkpoint: version open: %s", db_strerror(ret));
        _exit(1);
    }
#endif
    db->close(db, 0);


    while(!signalled) {
#if DB_VERSION_MAJOR == 2
	ret = txn_checkpoint(OVDBenv->tx_info, 2048, 1);
#elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
	ret = txn_checkpoint(OVDBenv, 2048, 1);
#elif DB_VERSION_MAJOR == 3
	ret = txn_checkpoint(OVDBenv, 2048, 1, 0);
#elif DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 1
	ret = OVDBenv->txn_checkpoint(OVDBenv, 2048, 1, 0);
#else
	OVDBenv->txn_checkpoint(OVDBenv, 2048, 1, 0);
#endif
#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
	sleep(30);
#else
	if(ret != 0 && ret != DB_INCOMPLETE) {
            warn("OVDB: txn_checkpoint: %s", db_strerror(ret));
	    status = 1;
	    break;
	}
	if(ret == DB_INCOMPLETE)
	    sleep(2);
	else
	    sleep(30);
#endif
    }

    ovdb_close_berkeleydb();
    _exit(status);
}

static void logremover(void)
{
    int ret, status = 0;
    char **listp, **p;

    if(ovdb_open_berkeleydb(OV_WRITE, 0))
	_exit(1);

    setproctitle("logremover");

    while(!signalled) {
#if DB_VERSION_MAJOR == 2
	ret = log_archive(OVDBenv->lg_info, &listp, DB_ARCH_ABS, malloc);
#elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
	ret = log_archive(OVDBenv, &listp, DB_ARCH_ABS, malloc);
#elif DB_VERSION_MAJOR == 3
	ret = log_archive(OVDBenv, &listp, DB_ARCH_ABS);
#else
	ret = OVDBenv->log_archive(OVDBenv, &listp, DB_ARCH_ABS);
#endif
	if(ret != 0) {
            warn("OVDB: log_archive: %s", db_strerror(ret));
	    status = 1;
	    break;
	}
	if(listp != NULL) {
	    for(p = listp; *p; p++)
		unlink(*p);
	    free(listp);
	}
	sleep(45);
    }

    ovdb_close_berkeleydb();
    _exit(status);
}

static int start_process(pid_t *pid, void (*func)(void))
{
    pid_t child;

    switch(child = fork()) {
    case 0:
	(*func)();
	_exit(0);
    case -1:
        syswarn("cannot fork");
	return -1;
    default:
	*pid = child;
	return 0;
    }
    /*NOTREACHED*/
}

static void cleanup(int status)
{
    int cs;

    if(deadlockpid)
	kill(deadlockpid, SIGTERM);
    if(checkpointpid)
	kill(checkpointpid, SIGTERM);
    if(logremoverpid)
	kill(logremoverpid, SIGTERM);

    xsignal(SIGINT, SIG_DFL);
    xsignal(SIGTERM, SIG_DFL);
    xsignal(SIGHUP, SIG_DFL);

    if(deadlockpid)
	waitpid(deadlockpid, &cs, 0);
    if(checkpointpid)
	waitpid(checkpointpid, &cs, 0);
    if(logremoverpid)
	waitpid(logremoverpid, &cs, 0);

    unlink(concatpath(innconf->pathrun, OVDB_MONITOR_PIDFILE));
    exit(status);
}

static void monitorloop(void)
{
    int cs, restartit;
    pid_t child;

    while(!signalled) {
	child = waitpid(-1, &cs, WNOHANG);
	if(child > 0) {
	    if(WIFSIGNALED(cs)) {
		restartit = 0;
	    } else {
		if(WEXITSTATUS(cs) == 0)
		    restartit = 1;
		else
		    restartit = 0;
	    }
	    if(child == deadlockpid) {
		deadlockpid = 0;
		if(restartit && start_process(&deadlockpid, deadlock))
		    cleanup(1);
	    } else if(child == checkpointpid) {
		checkpointpid = 0;
		if(restartit && start_process(&checkpointpid, checkpoint))
		    cleanup(1);
	    } else if(child == logremoverpid) {
		logremoverpid = 0;
		if(restartit && start_process(&logremoverpid, logremover))
		    cleanup(1);
	    }
	    if(!restartit)
		cleanup(1);
	}
	sleep(20);
    }
    cleanup(0);
}


int main(int argc, char **argv)
{
    char *pidfile;

    setproctitle_init(argc, argv);

    openlog("ovdb_monitor", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
    message_program_name = "ovdb_monitor";

    if(argc != 2 || strcmp(argv[1], SPACES))
        die("should be started by ovdb_init");
    message_handlers_warn(1, message_log_syslog_err);
    message_handlers_die(1, message_log_syslog_err);

    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);
    if(!ovdb_getlock(OVDB_LOCK_ADMIN))
        die("cannot lock database");

    xsignal(SIGINT, sigfunc);
    xsignal(SIGTERM, sigfunc);
    xsignal(SIGHUP, sigfunc);

    pidfile = concatpath(innconf->pathrun, OVDB_MONITOR_PIDFILE);
    if(putpid(pidfile))
	exit(1);
    if(start_process(&deadlockpid, deadlock))
	cleanup(1);
    if(start_process(&checkpointpid, checkpoint))
	cleanup(1);
    if(start_process(&logremoverpid, logremover))
	cleanup(1);

    monitorloop();

    /* Never reached. */
    return 1;
}

#endif /* USE_BERKELEY_DB */

