/* $Id: buffchan.c 6163 2003-01-19 22:56:34Z rra $
**
** Buffered file exploder for innd.
*/
#include "config.h"
#include "clibrary.h"
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include "inn/innconf.h"
#include "inn/messages.h"
#include "inn/qio.h"
#include "libinn.h"
#include "paths.h"
#include "map.h"
/*
** Hash functions for hashing sitenames.
*/
#define SITE_HASH(Name, p, j) \
for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
#define SITE_SIZE 128
#define SITE_BUCKET(j) &SITEtable[j & (SITE_SIZE - 1)]
/*
** Entry for a single active site.
*/
typedef struct _SITE {
bool Dropped;
const char *Name;
int CloseLines;
int FlushLines;
time_t LastFlushed;
time_t LastClosed;
int CloseSeconds;
int FlushSeconds;
FILE *F;
const char *Filename;
char *Buffer;
} SITE;
/*
** Site hashtable bucket.
*/
typedef struct _SITEHASH {
int Size;
int Used;
SITE *Sites;
} SITEHASH;
/* Global variables. */
static char *Format;
static const char *Map;
static int BufferMode;
static int CloseEvery;
static int FlushEvery;
static int CloseSeconds;
static int FlushSeconds;
static sig_atomic_t GotInterrupt;
static SITEHASH SITEtable[SITE_SIZE];
static TIMEINFO Now;
/*
** Set up the site information. Basically creating empty buckets.
*/
static void
SITEsetup(void)
{
SITEHASH *shp;
for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++) {
shp->Size = 3;
shp->Sites = xmalloc(shp->Size * sizeof(SITE));
shp->Used = 0;
}
}
/*
** Close a site
*/
static void
SITEclose(SITE *sp)
{
FILE *F;
if ((F = sp->F) != NULL) {
if (fflush(F) == EOF || ferror(F)
|| fchmod((int)fileno(F), 0664) < 0
|| fclose(F) == EOF)
syswarn("%s cannot close %s", sp->Name, sp->Filename);
sp->F = NULL;
}
}
/*
** Close all open sites.
*/
static void
SITEcloseall(void)
{
SITEHASH *shp;
SITE *sp;
int i;
for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++)
for (sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
SITEclose(sp);
}
/*
** Open the file for a site.
*/
static void SITEopen(SITE *sp)
{
int e;
if ((sp->F = xfopena(sp->Filename)) == NULL
&& ((e = errno) != EACCES || chmod(sp->Filename, 0644) < 0
|| (sp->F = xfopena(sp->Filename)) == NULL)) {
syswarn("%s cannot fopen %s", sp->Name, sp->Filename);
if ((sp->F = fopen("/dev/null", "w")) == NULL)
/* This really should not happen. */
sysdie("%s cannot fopen /dev/null", sp->Name);
}
else if (fchmod((int)fileno(sp->F), 0444) < 0)
syswarn("%s cannot fchmod %s", sp->Name, sp->Filename);
if (BufferMode != '\0')
setbuf(sp->F, sp->Buffer);
/* Reset all counters. */
sp->FlushLines = 0;
sp->CloseLines = 0;
sp->LastFlushed = Now.time;
sp->LastClosed = Now.time;
sp->Dropped = false;
}
/*
** Find a site, possibly create if not found.
*/
static SITE *
SITEfind(char *Name, bool CanCreate)
{
char *p;
int i;
unsigned int j;
SITE *sp;
SITEHASH *shp;
char c;
char buff[BUFSIZ];
/* Look for site in the hash table. */
SITE_HASH(Name, p, j);
shp = SITE_BUCKET(j);
for (c = *Name, sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
if (c == sp->Name[0] && strcasecmp(Name, sp->Name) == 0)
return sp;
if (!CanCreate)
return NULL;
/* Adding a new site -- grow hash bucket if we need to. */
if (shp->Used == shp->Size - 1) {
shp->Size *= 2;
shp->Sites = xrealloc(shp->Sites, shp->Size * sizeof(SITE));
}
sp = &shp->Sites[shp->Used++];
/* Fill in the structure for the new site. */
sp->Name = xstrdup(Name);
snprintf(buff, sizeof(buff), Format, Map ? MAPname(Name) : sp->Name);
sp->Filename = xstrdup(buff);
if (BufferMode == 'u')
sp->Buffer = NULL;
else if (BufferMode == 'b')
sp->Buffer = xmalloc(BUFSIZ);
SITEopen(sp);
return sp;
}
/*
** Flush a site -- close and re-open the file.
*/
static void
SITEflush(SITE *sp)
{
FILE *F;
if ((F = sp->F) != NULL) {
if (fflush(F) == EOF || ferror(F)
|| fchmod((int)fileno(F), 0664) < 0
|| fclose(F) == EOF)
syswarn("%s cannot close %s", sp->Name, sp->Filename);
sp->F = NULL;
}
if (!sp->Dropped)
SITEopen(sp);
}
/*
** Flush all open sites.
*/
static void
SITEflushall(void)
{
SITEHASH *shp;
SITE *sp;
int i;
for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++)
for (sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
SITEflush(sp);
}
/*
** Write data to a site.
*/
static void
SITEwrite(char *name, char *text, size_t len)
{
SITE *sp;
sp = SITEfind(name, true);
if (sp->F == NULL)
SITEopen(sp);
if (fwrite(text, 1, len, sp->F) != len)
syswarn("%s cannot write", sp->Name);
/* Bump line count; see if time to close or flush. */
if (CloseEvery && ++(sp->CloseLines) >= CloseEvery) {
SITEflush(sp);
return;
}
if (CloseSeconds && sp->LastClosed + CloseSeconds < Now.time) {
SITEflush(sp);
return;
}
if (FlushEvery && ++(sp->FlushLines) >= FlushEvery) {
if (fflush(sp->F) == EOF || ferror(sp->F))
syswarn("%s cannot flush %s", sp->Name, sp->Filename);
sp->LastFlushed = Now.time;
sp->FlushLines = 0;
}
else if (FlushSeconds && sp->LastFlushed + FlushSeconds < Now.time) {
if (fflush(sp->F) == EOF || ferror(sp->F))
syswarn("%s cannot flush %s", sp->Name, sp->Filename);
sp->LastFlushed = Now.time;
sp->FlushLines = 0;
}
}
/*
** Handle a command message.
*/
static void
Process(char *p)
{
SITE *sp;
if (*p == 'b' && strncmp(p, "begin", 5) == 0)
/* No-op. */
return;
if (*p == 'f' && strncmp(p, "flush", 5) == 0) {
for (p += 5; ISWHITE(*p); p++)
continue;
if (*p == '\0')
SITEflushall();
else if ((sp = SITEfind(p, false)) != NULL)
SITEflush(sp);
/*else
fprintf(stderr, "buffchan flush %s unknown site\n", p);*/
return;
}
if (*p == 'd' && strncmp(p, "drop", 4) == 0) {
for (p += 4; ISWHITE(*p); p++)
continue;
if (*p == '\0')
SITEcloseall();
else if ((sp = SITEfind(p, false)) == NULL)
warn("drop %s unknown site", p);
else {
SITEclose(sp);
sp->Dropped = true;
}
return;
}
if (*p == 'r' && strncmp(p, "readmap", 7) == 0) {
MAPread(Map);
return;
}
/* Other command messages -- ignored. */
warn("unknown message %s", p);
}
/*
** Mark that we got a signal; let two signals kill us.
*/
static RETSIGTYPE
CATCHinterrupt(int s)
{
GotInterrupt = true;
xsignal(s, SIG_DFL);
}
int
main(int ac, char *av[])
{
QIOSTATE *qp;
int i;
int Fields;
char *p;
char *next;
char *line;
char *Directory;
bool Redirect;
FILE *F;
char *ERRLOG;
/* First thing, set up our identity. */
message_program_name = "buffchan";
/* Set defaults. */
if (!innconf_read(NULL))
exit(1);
ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG);
Directory = NULL;
Fields = 1;
Format = NULL;
Redirect = true;
GotInterrupt = false;
umask(NEWSUMASK);
xsignal(SIGHUP, CATCHinterrupt);
xsignal(SIGINT, CATCHinterrupt);
xsignal(SIGQUIT, CATCHinterrupt);
xsignal(SIGPIPE, CATCHinterrupt);
xsignal(SIGTERM, CATCHinterrupt);
xsignal(SIGALRM, CATCHinterrupt);
/* Parse JCL. */
while ((i = getopt(ac, av, "bc:C:d:f:l:L:m:p:rs:u")) != EOF)
switch (i) {
default:
die("usage error");
break;
case 'b':
case 'u':
BufferMode = i;
break;
case 'c':
CloseEvery = atoi(optarg);
break;
case 'C':
CloseSeconds = atoi(optarg);
break;
case 'd':
Directory = optarg;
if (Format == NULL)
Format =xstrdup("%s");
break;
case 'f':
Fields = atoi(optarg);
break;
case 'l':
FlushEvery = atoi(optarg);
break;
case 'L':
FlushSeconds = atoi(optarg);
break;
case 'm':
Map = optarg;
MAPread(Map);
break;
case 'p':
if ((F = fopen(optarg, "w")) == NULL)
sysdie("cannot fopen %s", optarg);
fprintf(F, "%ld\n", (long)getpid());
if (ferror(F) || fclose(F) == EOF)
sysdie("cannot fclose %s", optarg);
break;
case 'r':
Redirect = false;
break;
case 's':
Format = optarg;
break;
}
ac -= optind;
av += optind;
if (ac)
die("usage error");
/* Do some basic set-ups. */
if (Redirect)
freopen(ERRLOG, "a", stderr);
if (Format == NULL) {
Format = concatpath(innconf->pathoutgoing, "%s");
}
if (Directory && chdir(Directory) < 0)
sysdie("cannot chdir to %s", Directory);
SITEsetup();
/* Read input. */
for (qp = QIOfdopen((int)fileno(stdin)); !GotInterrupt ; ) {
if ((line = QIOread(qp)) == NULL) {
if (QIOerror(qp)) {
syswarn("cannot read");
break;
}
if (QIOtoolong(qp)) {
warn("long line");
QIOread(qp);
continue;
}
/* Normal EOF. */
break;
}
/* Command? */
if (*line == EXP_CONTROL && *++line != EXP_CONTROL) {
Process(line);
continue;
}
/* Skip the right number of leading fields. */
for (i = Fields, p = line; *p; p++)
if (*p == ' ' && --i <= 0)
break;
if (*p == '\0')
/* Nothing to write. Probably shouldn't happen. */
continue;
/* Add a newline, get the length of all leading fields. */
*p++ = '\n';
i = p - line;
if (GetTimeInfo(&Now) < 0) {
syswarn("cannot get time");
break;
}
/* Rest of the line is space-separated list of filenames. */
for (; *p; p = next) {
/* Skip whitespace, get next word. */
while (*p == ' ')
p++;
for (next = p; *next && *next != ' '; next++)
continue;
if (*next)
*next++ = '\0';
SITEwrite(p, line, i);
}
}
SITEcloseall();
exit(0);
/* NOTREACHED */
}
syntax highlighted by Code2HTML, v. 0.9.1