/*
* DHISCTL.C - Perform history maintenance operations
*
* (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"
int VerboseOpt = 0;
int ShowProgress = 0;
int DonePause = 0;
time_t MaxAge = -1;
char *MsgID = NULL;
int ExpireArt = 0;
int UnExpireArt = 0;
int MustExit = 0;
char *FileName = NULL;
char *MsgIdList = NULL;
int HistoryFd = -1;
int DoAll = 0;
int ForReal = 1;
int StructSizes = 0;
int HistoryHead = 0;
void DumpHeader(int fd);
void DoEntry(char *msgid);
void ScanFile(char *fname);
void ScanAll(void);
int ServerCmd(char *cmd);
void Fail(char *fname, char *errmsg);
void
Usage(void)
{
printf("Perform maintenance operations on the history file.\n\n");
printf("dhisctl [-e] [-f id_file] [-p] [-S] [-v]\n");
printf(" [-C diablo.config] [-d[n]] [-V] historyfile [<MsgId>|hash]\n");
printf(" where:\n");
#if 0
printf("\t-a\t- perform the action on all history entries\n");
#endif
printf("\t-e\t- expire the article(s)\n");
printf("\t-f\t- file containing list of msgid's or '-' for stdin\n");
printf("\t-h\t- display history header and total size details\n");
printf("\t-p\t- show progress on stdout\n");
printf("\t-S\t- display sizes of internal history structures and exit\n");
printf("\t-u\t- unexpire the article(s)\n");
printf("\t-v\t- be a little more verbose\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 i;
LoadDiabloConfig(ac, av);
for (i = 1; i < ac; ++i) {
char *ptr = av[i];
if (*ptr != '-') {
if (FileName != NULL) {
if (MsgID != NULL) {
fprintf(stderr, "unexpected argument: %s\n", ptr);
exit(1);
}
MsgID = ptr;
continue;
}
FileName = ptr;
continue;
}
ptr += 2;
switch(ptr[-1]) {
case 'a':
DoAll = 1;
break;
case 'e':
ExpireArt = 1;
break;
case 'f':
if (!*ptr)
ptr = av[++i];
MsgIdList = ptr;
break;
case 'h':
HistoryHead = 1;
break;
case 'p':
ShowProgress = 1;
break;
case 'S':
StructSizes = 1;
break;
case 'u':
UnExpireArt = 1;
break;
case 'v':
if (*ptr)
VerboseOpt = strtol(ptr, NULL, 0);
else
++VerboseOpt;
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 (StructSizes) {
printf("History Header : %2d bytes\n", sizeof(HistHead));
printf("History Hash Entry : %2d bytes\n", sizeof(uint32));
printf("History Entry : %2d bytes\n", sizeof(History));
printf("Hash Table Entries : %d\n", DOpts.HashSize);
printf("Expected hash table size : %d bytes\n", DOpts.HashSize * sizeof(uint32));
exit(0);
}
if (FileName == NULL)
Usage();
HistoryOpen(FileName, 0);
if ((HistoryFd = open(FileName, O_RDWR)) == -1) {
perror("history open");
exit(1);
}
if (HistoryHead)
DumpHeader(HistoryFd);
if (MsgID != NULL)
DoEntry(MsgID);
if (MsgIdList != NULL)
ScanFile(MsgIdList);
if (DoAll)
ScanAll();
return(0);
}
void
DumpHeader(int fd)
{
HistHead hh;
if (read(fd, &hh, sizeof(hh)) != sizeof(hh)) {
perror("ERROR in history header read");
exit(1);
}
printf("Magic : 0x%x\n", hh.hmagic);
printf("HashSize : %d\n", hh.hashSize);
printf("Version : %d\n", hh.version);
printf("EntrySize : %d\n", hh.henSize);
printf("HeaderSize: %d\n\n", hh.headSize);
{
struct stat sb;
if (fstat(fd, &sb) == -1)
exit(0);
printf("Total Size : %.0f Bytes (%s)\n", (double)sb.st_size,
ftos((double)sb.st_size));
printf("Hash Table Size : %d Bytes (%s)\n",
hh.hashSize * sizeof(HistIndex),
ftos((double)hh.hashSize * sizeof(HistIndex)));
printf("Total Entries : %.0f\n",
((double)sb.st_size - (double)hh.headSize -
(double)hh.hashSize * sizeof(HistIndex)) /
hh.henSize);
}
exit(0);
}
int
FixEntry(History *h, char *msgid)
{
int mod = 0;
if (UnExpireArt) {
if (H_EXPIRED(h->exp)) {
h->exp &= ~EXPF_EXPIRED;
mod = 1;
if (VerboseOpt)
printf("UnExpiring: %s\n", msgid);
} else if (VerboseOpt)
printf("Not expired: %s\n", msgid);
} else if (ExpireArt) {
if (!H_EXPIRED(h->exp)) {
h->exp |= EXPF_EXPIRED;
mod = 1;
if (VerboseOpt)
printf("Expiring: %s\n", msgid);
} else if (VerboseOpt)
printf("Already expired: %s\n", msgid);
}
return(mod);
}
/*
* XXX This could be handled better by doing a HistoryExpire()
* XXX but we also need the capability to modify other history fields
* XXX FUTURE WORK: Check for (un)expire and run HistoryExpire()
*/
void
DoEntry(char *msgid)
{
History h;
hash_t hv;
int32 pos;
char *p;
if (msgid[0] == '<' && (p = strchr(msgid, '>')) != NULL)
hv = hhash(msgid);
else if (sscanf(msgid, "%x.%x", &hv.h1, &hv.h2) != 2) {
fprintf(stderr, "Invalid msgid/hash: %s\n", msgid);
exit(1);
}
pos = HistoryPosLookupByHash(hv, &h);
if (pos == -1) {
fprintf(stderr, "History lookup failed for %s\n", msgid);
return;
}
if (FixEntry(&h, msgid) && ForReal)
HistoryStoreExp(&h, (HistIndex)pos);
}
void
ScanFile(char *fname)
{
FILE *f;
char buf[1024];
if (strcmp(fname, "-") == 0)
f = stdin;
else
f = fopen(fname, "r");
if (f == NULL) {
fprintf(stderr, "Unable to open msgid file: %s\n", fname);
exit(1);
}
while (fgets(buf, sizeof(buf), f) != NULL) {
buf[strlen(buf) - 1] = '\0';
DoEntry(buf);
}
fclose(f);
}
void
ScanAll()
{
char hbuf[4096];
int n;
int hlen = (sizeof(hbuf) / 5) * 5;
uint32 gmt = time(NULL) / 60;
off_t seekpos = 0;
int totalentries = 0;
int okcount = 0;
int count = 0;
int r;
int failed = 0;
History *h;
int finished = 0;
int fd = -1;
int hsize = 0;
seekpos = lseek(fd, hsize * sizeof(int32), 1);
{
struct stat st;
if (fstat(fd, &st) == -1) {
fprintf(stderr, "Unable to fstat history: %s\n", strerror(errno));
exit(1);
}
totalentries = (int)(st.st_size - seekpos) / 5;
fprintf(stderr, "History entries start at offset %ld, %d records\n",
(long)seekpos, totalentries);
}
HistoryOpen(FileName, HGF_FAST|HGF_NOSEARCH|HGF_EXCHECK);
while (!finished || MustExit) {
int i;
n = read(fd, &hbuf, hlen) / 5;
for (i = 0; i < n; ++i) {
h = (History *)(hbuf + i * 5);
if ((i & 1024) == 0)
gmt = time(NULL) / 60;
/*
* If we specified a maximum age, then deal with it
*/
if (MaxAge != -1) {
int32 dgmt = (gmt - h->gmt) * 60; /* Delta seconds */
if (dgmt > MaxAge)
continue;
}
if ((r = HistoryAdd(NULL, h)) == 0)
++okcount;
else if (r != RCTRYAGAIN)
++failed;
else
Fail(FileName, "HistoryAdd: write failed!");
if (count++ > totalentries)
totalentries = count;
if (ShowProgress && ((count % 8192) == 0)) {
fprintf(stderr, "%d/%d\r", count, totalentries);
}
}
}
if (DonePause == 3 && ServerCmd("go") == 0) /* Got signal */
Fail(FileName, "Unable to resume diablo server");
if (totalentries > 0)
fprintf(stderr, "%d/%d\n", count, totalentries);
}
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