/*
 * DHISEXPIRE.C	- Expire the history file with minimal downtime
 *
 * (c)Copyright 2002, Russell Vincent, All Rights Reserved.  Refer to
 *    the COPYRIGHT file in the base directory of this distribution 
 *    for specific rights granted.
 */

#include "defs.h"

#define	USE_DEADMAGIC

int VerboseOpt = 0;
int QuietOpt = 0;
int ShowProgress = 0;
int DonePause = 0;
int UseNewHistory = 0;
int DoRemember = 1;
int UnDead = 0;
int FileCopy = 0;
time_t MaxAge = -1;
int MustExit = 0;
char *FileName = NULL;
char NewFileName[PATH_MAX];
char OldFileName[PATH_MAX];
int HistoryVersion = 0;

void DoUnDead(int fd);
void DoExpire(int fd, int hsize, int rsize);
int ServerCmd(char *cmd);
void Fail(char *fname, char *errmsg);

void
Usage(void)
{
    printf("Expire old entries in the history file.\n\n");
    printf("dhisexpire [-a] [-p] [-r remember] [-T seconds] [-v] [-x]\n");
    printf("           [-C diablo.config] [-d[n]] [-V] dhistory-file [new-history]\n");
    printf("  where:\n");
    printf("\t-a\t- rename the new history to old history when finished\n");
    printf("\t-m\t- rename history files across filesystems (file copy)\n");
#ifndef	USE_DEADMAGIC
    printf("\t-P\t- don't pause diablo server\n");
#endif
    printf("\t-ofile\t- set path/name for backup of old history file\n");
    printf("\t-p\t- show progress on stdout\n");
    printf("\t-rN\t- set rememberdays\n");
    printf("\t-TN\t- don't dump articles older than N seconds\n");
    printf("\t-u\t- remove dead flag for a history file\n");
    printf("\t-v\t- be a little more verbose\n");
    printf("\t-x\t- keep records older than rememberdays old\n");
    printf("\t-Cfile\t- specify diablo.config to use\n");
    printf("\t-d[n]\t- set debug [with optional level]\n");
    printf("\t-V\t- print version and exit\n");
    exit(1);
}

void
sigInt(int sigNo)
{
    printf("Exit signal caught - exiting\n");
    ++MustExit;
    if (MustExit > 3)
	exit(1);
}

int
main(int ac, char **av)
{
    int fd;
    int i;
    int hsize = 1024 * 1024;
    int rsize = sizeof(History);
    struct stat st;

    NewFileName[0] = 0;
    OldFileName[0] = 0;

    LoadDiabloConfig(ac, av);

    for (i = 1; i < ac; ++i) {
	char *ptr = av[i];

	if (*ptr != '-') {
	    if (FileName) {
		if (NewFileName[0] != 0) {
		    fprintf(stderr, "unexpected argument\n");
		    exit(1);
		}
		strncpy(NewFileName, ptr, sizeof(NewFileName) - 1);
		NewFileName[sizeof(NewFileName) - 1] = '\0';
		continue;
	    }
	    FileName = ptr;
	    continue;
	}
	ptr += 2;
	switch(ptr[-1]) {
	case 'a':
	    UseNewHistory = 1; 
	    break;
	case 'm':
	    FileCopy = 1; 
	    break;
#ifndef	USE_DEADMAGIC
	case 'P':
	    DonePause = 1;
	    break;
#endif
	case 'o':
	    strncpy(OldFileName, ptr, sizeof(OldFileName) - 1);
	    OldFileName[sizeof(OldFileName) - 1] = '\0';
	    break;
	case 'p':
	    ShowProgress = 1;
	    break;
	case 'q':
	    QuietOpt = 1;
	    break;
	case 'r':
	    if (!*ptr)
		ptr = av[++i];
	    DOpts.RememberSecs = TimeSpec(ptr, "d");
	    if (DOpts.RememberSecs == -1)
		Usage();
	    break;
	case 'T':
	    MaxAge = btimetol(*ptr ? ptr : av[++i]);
	    break;
	case 'u':
	    UnDead = 1;
	    break;
	case 'v':
	    if (*ptr)
		VerboseOpt = strtol(ptr, NULL, 0);
	    else
		++VerboseOpt;
	    break;
	case 'x':
	    DoRemember = 0;
	    break;
	/* Common options */
	case 'C':		/* parsed by LoadDiabloConfig */
	    if (*ptr == 0)
		++i;
	    break;
	case 'd':
	    DebugOpt = 1;
	    if (*ptr)
		DebugOpt = strtol(ptr, NULL, 0);
	    break;
	case 'V':
	    PrintVersion();
	    break;
	default:
	    fprintf(stderr, "illegal option: %s\n", ptr - 2);
	    Usage();
	}
    }

    if (FileName == NULL) {
	Usage();
    }
    if (NewFileName[0] == 0)
	sprintf(NewFileName, "%s.new", FileName);
    if (OldFileName[0] == 0)
	sprintf(OldFileName, "%s.bak", FileName);
    if (strcmp(OldFileName, "0") == 0)
	OldFileName[0] = 0;

    if ((fd = open(FileName, O_RDWR)) >= 0 && fstat(fd, &st) == 0) {
	/*
	 * new style history file has a header
	 */

	HistHead hh;

	if (UnDead) {
	    DoUnDead(fd);
	    close(fd);
	    return(0);
	}
	if (read(fd, &hh, sizeof(hh)) != sizeof(hh)) {
	    perror("Corrupted history file");
	    exit(1);
	}
	if (hh.hmagic != HMAGIC) {
	    fprintf(stderr, "Corrupted history file - bad magic\n");
	    exit(1);
	}
	if (hh.version != HVERSION) {
	    fprintf(stderr, "WARNING! Version mismatch file V%d, expecting V%d\n", hh.version, HVERSION);
	    fprintf(stderr, "dump may be invalid\n");
	}
	rsize = hh.henSize;
	hsize = hh.hashSize;
	HistoryVersion = hh.version;

	lseek(fd, hh.headSize, 0);

	if (!QuietOpt)
	    printf("Hash table has %d entries, record size %d\n",
	    hsize,
	    rsize
	);

	DoExpire(fd, hsize, rsize);

	close(fd);
    } else {
	perror("History open failed");
	exit(1);
    }
    return(0);
}

void
DoUnDead(int fd)
{
    HistHead hh = { 0 };

    if (lseek(fd, 0, SEEK_SET) == -1) {
	perror("Unable to seek to pos 0 in history");
	return;
    }
    if (read(fd, &hh, sizeof(hh)) != sizeof(hh)) {
	perror("Unable to read history header");
	return;
    }
    if (hh.hmagic != HDEADMAGIC) {
	fprintf(stderr, "History file not marked as dead\n");
	return;
    }
    if (hh.version != HVERSION) {
	 fprintf(stderr, "ERROR! Version mismatch file V%d, expecting V%d\n", hh.version, HVERSION);
	return;
    }
    hh.hmagic = HMAGIC;
    if (lseek(fd, 0, SEEK_SET) == -1) {
	perror("Unable to seek to pos 0 in history");
	return;
    }
    if (write(fd, &hh, sizeof(hh)) != sizeof(hh)) {
	perror("Unable to write history header");
	return;
    }
    printf("History file %s no longer marked as dead\n", FileName);
}

void
DoExpire(int fd, int hsize, int rsize)
{
    char *hbuf;
    int hlen = rsize * 4096;
    int n;
    int rememberMins = DOpts.RememberSecs / 60;
    uint32 gmt = time(NULL) / 60;
    off_t seekpos = 0;
    uint32 totalentries = 0;
    uint32 okcount = 0;
    uint32 count = 0;
    uint32 failed = 0;
    uint32 ExpireDropCount = 0;
    uint32 ExpireKeepCount = 0;
    uint32 MaxAgeCount = 0;
    int r;
    History *h;
    int finished = 0;
    struct timeval tstart;
    struct timeval tend;
    double elapsed;

    hbuf = (char *)malloc(hlen);
    if (hbuf == NULL) {
	fprintf(stderr, "Unable to malloc %d bytes (%s)\n", hlen,
							strerror(errno));
	exit(1);
    }

    if (HistoryVersion > 1)
	seekpos = lseek(fd, hsize * sizeof(HistIndex) + rsize, 1);
    else
	seekpos = lseek(fd, hsize * sizeof(HistIndex), 1);

    {
	struct stat st;

	if (fstat(fd, &st) == -1) {
	    fprintf(stderr, "Unable to fstat history: %s\n", strerror(errno));
	    exit(1);
	}
	totalentries = (uint32)(((off_t)st.st_size - seekpos) / rsize);
	if (!QuietOpt)
	    printf("History entries start at offset %lld, %d records\n",
						seekpos, totalentries);
    }

    HistoryOpen(NewFileName, HGF_FAST|HGF_NOSEARCH|HGF_EXCHECK);

    gettimeofday(&tstart, NULL);

    while (!finished || MustExit) {
	int i;

	n = read(fd, hbuf, hlen) / rsize;

	if (n == 0) {
#ifdef USE_DEADMAGIC
	    if (DonePause > 0) {
		if (DonePause == 2) {
		    DonePause = 5;
		    HistoryClose();
		    if (UseNewHistory) {
			if (OldFileName[0])
			    remove(OldFileName);
			if (FileCopy) {
			    if (OldFileName[0] && MoveFile(FileName, OldFileName) == -1)
				Fail(NewFileName, "Unable to move history backup");
			    if (MoveFile(NewFileName, FileName) == -1)
				Fail(NewFileName, "Unable to move new history");
			} else {
			    if (OldFileName[0] && link(FileName, OldFileName) == -1)
				Fail(NewFileName, "Unable to link history backup");
			    if (rename(NewFileName, FileName) == -1)
				Fail(NewFileName, "Unable to rename new history");
			}
		    }
		}
		finished = 1;
	    } else {
		off_t seekpos = lseek(fd, 0, SEEK_CUR);
		HistHead hh = { 0 };
		if (lseek(fd, 0, SEEK_SET) == -1)
		    Fail(FileName, "Unable to seek to pos 0 in history");
		if (read(fd, &hh, sizeof(hh)) == -1)
		    Fail(FileName, "Unable to read history header");
		hh.hmagic = HDEADMAGIC;
		if (lseek(fd, 0, SEEK_SET) == -1)
		    Fail(FileName, "Unable to seek to pos 0 in history");
		if (write(fd, &hh, sizeof(hh)) == -1) {
		    perror("historyheadwrite");
		    Fail(FileName, "Unable to write history header");
		}
		if (lseek(fd, seekpos, SEEK_SET) == -1)
		    Fail(FileName, "Unable to seek in history");
		DonePause = 2;
	    }
#else
	    if (DonePause > 0) {
		if (DonePause == 2) {
		    rsignal(SIGINT, sigInt);
		    rsignal(SIGHUP, sigInt);
		    rsignal(SIGTERM, sigInt);
		    rsignal(SIGALRM, SIG_IGN);
		    DonePause = 3;
		    if (ServerCmd("pause") == 0)
			Fail(NewFileName, "Unable to pause diablo server");
		    DonePause = 4;
		    HistoryClose();
		    if (UseNewHistory) {
			if (OldFileName[0])
			    remove(OldFileName);
			if (FileCopy) {
			    if (OldFileName[0] && MoveFile(FileName, OldFileName) == -1)
				Fail(NewFileName, "Unable to move history backup");
			    if (MoveFile(NewFileName, FileName) == -1)
				Fail(NewFileName, "Unable to move new history");
			} else {
			    if (OldFileName[0] && link(FileName, OldFileName) == -1)
				Fail(NewFileName, "Unable to link history backup");
			    if (rename(NewFileName, FileName) == -1)
				Fail(NewFileName, "Unable to rename new history");
			}
		    }
		    if (ServerCmd("go") == 0)
			Fail(NewFileName, "Unable to resume diablo server");
		    DonePause = 5;
		}
		finished = 1;
	    } else {
		if (ServerCmd("flush") == 0)
		    Fail(NewFileName, "Unable to flush diablo server");
		if (ServerCmd("readonly") == 0)
		    Fail(NewFileName, "Unable to set readonly mode");
		if (ServerCmd("flush") == 0)
		    Fail(NewFileName, "Unable to flush diablo server");
		DonePause = 2;
		sleep(2);
	    }
#endif
	    continue;
	}

	gmt = time(NULL) / 60;

	for (i = 0; i < n; ++i) {
	    if (count++ > totalentries)
		totalentries = count;
	    if (ShowProgress && ((count % 32768) == 0)) {
		printf("%u/%u  (%d%%)   \r", count, totalentries,
				(int)((double)count * 100 / totalentries));
	    }

	    h = (History *)(hbuf + i * rsize);

	    /*
	     * If we specified a maximum age, then deal with it
	     */
	    if (MaxAge != -1) {
		int32 dgmt = (gmt - h->gmt) * 60;	/* Delta seconds */
		if (dgmt > MaxAge) {
		    MaxAgeCount++;
		    continue;
		}
	    }

	    if (DoRemember && H_EXPIRED(h->exp)) {
		int32 dgmt = gmt - h->gmt;	/* DELTA MINUTES */
		if (dgmt < -rememberMins || dgmt > rememberMins) {
		    ExpireDropCount++;
		    continue;
		} else {
		    ExpireKeepCount++;
		}
	    }

	    if ((r = HistoryAdd(NULL, h)) == 0)
		++okcount;
	    else if (r != RCTRYAGAIN)
		++failed;
	    else
		Fail(NewFileName, "HistoryAdd: write failed!");
	}
    }

    gettimeofday(&tend, NULL);

    if (DonePause == 3 && ServerCmd("go") == 0)		/* Got signal */
	Fail(NewFileName, "Unable to resume diablo server");
    if (ShowProgress && totalentries > 0)
	printf("%u/%u  (%d%%)   \n", count, totalentries,
				(int)((double)count * 100 / totalentries));
    if (!QuietOpt) {
	elapsed = (tend.tv_sec + tend.tv_usec / 1000000.0) -
				(tstart.tv_sec + tstart.tv_usec / 1000000.0);
	if (elapsed == 0)
	    elapsed = 1;
        printf("%.3f seconds\n", elapsed);
        printf("%u entries processed (%.0f per second)\n",
					count, (double)count / elapsed);
        printf("%u entries kept\n", okcount);
        printf("%u entries failed\n", failed);
        printf("%u expired entries kept\n", ExpireKeepCount);
        printf("%u expired entries dropped\n", ExpireDropCount);
        printf("%u dropped as beyond max age\n", MaxAgeCount);
    }
}

int
ServerCmd(char *cmd)
{
    FILE *fi;
    FILE *fo;
    char buf[256];
    int r = 0;

    /*
     * UNIX domain socket
     */

    {
	struct sockaddr_un soun;
	int ufd;

	memset(&soun, 0, sizeof(soun));

	if ((ufd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
	    perror("udom-socket");
	    return(r);
	}
	soun.sun_family = AF_UNIX;
	sprintf(soun.sun_path, "%s", PatRunExpand(DiabloSocketPat));
	if (connect(ufd, (struct sockaddr *)&soun, offsetof(struct sockaddr_un, sun_path[strlen(soun.sun_path)+1])) < 0) {
	    perror("udom-connect");
	    return(r);
	}
	fo = fdopen(dup(ufd), "w");
	fi = fdopen(ufd, "r");
    }

    fprintf(fo, "%s\n", cmd);
    fprintf(fo, "quit\n");
    fflush(fo);
    while (fgets(buf, sizeof(buf), fi) != NULL) {
	if (VerboseOpt)
	    printf("%s", buf);
	if (strncmp(buf, "200", 3) == 0)
	    r = 1;
	if (strncmp(buf, "211 Flushing feeds", 18) == 0)
	    r = 1;
	if (strcmp(buf, ".\n") == 0)
	    break;
    }
    fclose(fo);
    fclose(fi);
    return(r);
}

void
Fail(char *fname, char *errmsg)
{
    printf("%s\n", errmsg);
    printf("History rebuild is not complete - keeping old history\n");
    HistoryClose();
    if (fname != NULL)
	remove(fname);
    exit(1);
}



syntax highlighted by Code2HTML, v. 0.9.1