/*
* 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