/*  $Revision: 1.10 $
**
**  Buffered file exploder for innd.
*/
#include <stdio.h>
#include <sys/types.h>
#include "configdata.h"
#include "clibrary.h"
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include "paths.h"
#include "libinn.h"
#include "qio.h"
#include "macros.h"

extern void	MAPread();
extern char	*MAPname();

/*
**  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;
    STRING	Name;
    int		CloseLines;
    int		FlushLines;
    time_t	LastFlushed;
    time_t	LastClosed;
    int 	CloseSeconds;
    int		FlushSeconds;
    FILE	*F;
    STRING	Filename;
    char	*Buffer;
} SITE;


/*
**  Site hashtable bucket.
*/
typedef struct _SITEHASH {
    int		Size;
    int		Used;
    SITE	*Sites;
} SITEHASH;


/* Global variables. */
STATIC char	*Format;
STATIC STRING	Map;
STATIC int	BufferMode;
STATIC int	CloseEvery;
STATIC int	FlushEvery;
STATIC int	CloseSeconds;
STATIC int	FlushSeconds;
STATIC SIGVAR	GotInterrupt;
STATIC SITEHASH	SITEtable[SITE_SIZE];
STATIC TIMEINFO	Now;


#if	defined(DONT_HAVE_FCHMOD)
/*
**  A dummy fchmod.
*/
/* ARGSUSED */
int
fchmod(i)
    int		i;
{
}
#endif	/* defined(DONT_HAVE_FCHMOD) */


/*
**  Set up the site information.  Basically creating empty buckets.
*/
STATIC void
SITEsetup()
{
    register SITEHASH	*shp;

    for (shp = SITEtable; shp < ENDOF(SITEtable); shp++) {
	shp->Size = 3;
	shp->Sites = NEW(SITE, shp->Size);
	shp->Used = 0;
    }
}


/*
**  Close a site
*/
STATIC void
SITEclose(sp)
    register SITE       *sp;
{
    register FILE	*F;

    if ((F = sp->F) != NULL) {
	if (fflush(F) == EOF || ferror(F)
	 || fchmod((int)fileno(F), 0664) < 0
	 || fclose(F) == EOF)
	    (void)fprintf(stderr, "buffchan %s cant close %s, %s\n",
		    sp->Name, sp->Filename, strerror(errno));
	sp->F = NULL;
    }
}

/*
**  Close all open sites.
*/
STATIC void
SITEcloseall()
{
    register SITEHASH	*shp;
    register SITE	*sp;
    register int	i;

    for (shp = SITEtable; shp < ENDOF(SITEtable); shp++)
	for (sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
	    SITEclose(sp);
}


/*
**  Open the file for a site.
*/
STATIC void
SITEopen(sp)
    register 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)) {
	(void)fprintf(stderr, "buffchan %s cant fopen %s, %s\n",
		sp->Name, sp->Filename, strerror(e));
	if ((sp->F = fopen("/dev/null", "w")) == NULL) {
	    /* This really should not happen. */
	    (void)fprintf(stderr, "buffchan %s cant fopen %s, %s\n",
		    sp->Name, "/dev/null", strerror(errno));
	    exit(1);
	}
    }
    else if (fchmod((int)fileno(sp->F), 0444) < 0)
	(void)fprintf(stderr, "buffchan %s cant fchmod %s %s\n",
		sp->Name, sp->Filename, strerror(errno));
	
    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(Name, CanCreate)
    char		*Name;
    BOOL		CanCreate;
{
    register char	*p;
    register int	i;
    unsigned int	j;
    register SITE	*sp;
    SITEHASH		*shp;
    char		c;
    char		buff[BUFSIZ];

    /* Look for site in the hash table. */
    /* SUPPRESS 6 *//* Over/underflow from plus expression */
    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] && caseEQ(Name, sp->Name))
	    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;
	RENEW(shp->Sites, SITE, shp->Size);
    }
    sp = &shp->Sites[shp->Used++];

    /* Fill in the structure for the new site. */
    sp->Name = COPY(Name);
    (void)sprintf(buff, (STRING)Format, Map ? MAPname(Name) : sp->Name);
    sp->Filename = COPY(buff);
    if (BufferMode == 'u')
	sp->Buffer = NULL;
    else if (BufferMode == 'b')
	sp->Buffer = NEW(char, BUFSIZ);
    SITEopen(sp);

    return sp;
}


/*
**  Flush a site -- close and re-open the file.
*/
STATIC void
SITEflush(sp)
    register SITE	*sp;
{
    register FILE	*F;

    if ((F = sp->F) != NULL) {
	if (fflush(F) == EOF || ferror(F)
	 || fchmod((int)fileno(F), 0664) < 0
	 || fclose(F) == EOF)
	    (void)fprintf(stderr, "buffchan %s cant close %s, %s\n",
		    sp->Name, sp->Filename, strerror(errno));
	sp->F = NULL;
    }
    if (!sp->Dropped)
	SITEopen(sp);
}


/*
**  Flush all open sites.
*/
STATIC void
SITEflushall()
{
    register SITEHASH	*shp;
    register SITE	*sp;
    register int	i;

    for (shp = SITEtable; shp < ENDOF(SITEtable); shp++)
	for (sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
	    SITEflush(sp);
}


/*
**  Write data to a site.
*/
STATIC void
SITEwrite(name, text, len)
    register char	*name;
    register char	*text;
    register int	len;
{
    register SITE	*sp;

    sp = SITEfind(name, TRUE);
    if (sp->F == NULL)
	SITEopen(sp);

    if (fwrite((POINTER)text, (SIZE_T)1, (SIZE_T)len, sp->F) != len)
	(void)fprintf(stderr, "buffchan %s cant write %s\n",
		sp->Name, strerror(errno));

    /* 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))
	    (void)fprintf(stderr, "buffchan %s cant flush %s, %s\n",
		    sp->Name, sp->Filename, strerror(errno));
	sp->LastFlushed = Now.time;
	sp->FlushLines = 0;
    }
    else if (FlushSeconds && sp->LastFlushed + FlushSeconds < Now.time) {
	if (fflush(sp->F) == EOF || ferror(sp->F))
	    (void)fprintf(stderr, "buffchan %s cant flush %s, %s\n",
		    sp->Name, sp->Filename, strerror(errno));
	sp->LastFlushed = Now.time;
	sp->FlushLines = 0;
    }
}


/*
**  Handle a command message.
*/
STATIC void
Process(p)
    register char	*p;
{
    register SITE	*sp;

    if (*p == 'b' && EQn(p, "begin", 5))
	/* No-op. */
	return;

    if (*p == 'f' && EQn(p, "flush", 5)) {
	for (p += 5; ISWHITE(*p); p++)
	    continue;
	if (*p == '\0')
	    SITEflushall();
	else if ((sp = SITEfind(p, FALSE)) != NULL)
	    SITEflush(sp);
	else
	    /*(void)fprintf(stderr, "buffchan flush %s unknown site\n", p);*/
	    ;
	return;
    }

    if (*p == 'd' && EQn(p, "drop", 4)) {
	for (p += 4; ISWHITE(*p); p++)
	    continue;
	if (*p == '\0')
	    SITEcloseall();
	else if ((sp = SITEfind(p, FALSE)) == NULL)
	    (void)fprintf(stderr, "buffchan drop %s unknown site\n", p);
	else {
	    SITEclose(sp);
	    sp->Dropped = TRUE;
	}
	return;
    }

    if (*p == 'r' && EQn(p, "readmap", 7)) {
	MAPread(Map);
	return;
    }

    /* Other command messages -- ignored. */
    (void)fprintf(stderr, "buffchan unknown message %s\n", p);
}


/*
**  Print usage message and exit.
*/
STATIC NORETURN
Usage()
{
    (void)fprintf(stderr, "Usage error.\n");
    exit(1);
}


/*
**  Mark that we got a signal; let two signals kill us.
*/
STATIC SIGHANDLER
CATCHinterrupt(s)
    int		s;
{
    GotInterrupt = TRUE;
    (void)signal(s, SIG_DFL);
}


int
main(ac, av)
    int			ac;
    char		*av[];
{
    static char		BATCHDIR[] = _PATH_BATCHDIR;
    register QIOSTATE	*qp;
    register int	i;
    register int	Fields;
    register char	*p;
    register char	*next;
    register char	*line;
    char		*Directory;
    BOOL		Redirect;
    FILE		*F;

    /* Set defaults. */
    Directory = NULL;
    Fields = 1;
    Format = NULL;
    Redirect = TRUE;
    GotInterrupt = FALSE;
    (void)umask(NEWSUMASK);

    (void)signal(SIGHUP, CATCHinterrupt);
    (void)signal(SIGINT, CATCHinterrupt);
    (void)signal(SIGQUIT, CATCHinterrupt);
    (void)signal(SIGPIPE, CATCHinterrupt);
    (void)signal(SIGTERM, CATCHinterrupt);
    (void)signal(SIGALRM, CATCHinterrupt);

    /* Parse JCL. */
    while ((i = getopt(ac, av, "bc:C:d:f:l:L:m:p:rs:u")) != EOF)
	switch (i) {
	default:
	    Usage();
	    /* NOTREACHED */
	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 ="%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) {
		(void)fprintf(stderr, "buffchan cant fopen %s %s\n",
			optarg, strerror(errno));
		exit(1);
	    }
	    (void)fprintf(F, "%ld\n", (long)getpid());
	    if (ferror(F) || fclose(F) == EOF) {
		(void)fprintf(stderr, "buffchan cant fclose %s %s\n",
			optarg, strerror(errno));
		exit(1);
	    }
	    break;
	case 'r':
	    Redirect = FALSE;
	    break;
	case 's':
	    Format = optarg;
	    break;
	}
    ac -= optind;
    av += optind;
    if (ac)
	Usage();

    /* Do some basic set-ups. */
    if (Redirect)
	(void)freopen(_PATH_ERRLOG, "a", stderr);
    if (Format == NULL) {
	Format = NEW(char, STRLEN(BATCHDIR) + 1 + 2 + 1);
	(void)sprintf(Format, "%s/%%s", BATCHDIR);
    }
    if (Directory && chdir(Directory) < 0) {
	(void)fprintf(stderr, "buffchan cant chdir %s %s\n",
	    Directory, strerror(errno));
	exit(1);
    }
    SITEsetup();

    /* Read input. */
    for (qp = QIOfdopen((int)fileno(stdin), 0); !GotInterrupt ; ) {
	if ((line = QIOread(qp)) == NULL) {
	    if (QIOerror(qp)) {
		(void)fprintf(stderr, "buffchan cant read %s\n",
			strerror(errno));
		break;
	    }
	    if (QIOtoolong(qp)) {
		(void)fprintf(stderr, "buffchan long_line");
		(void)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) {
	    (void)fprintf(stderr, "buffchan cant gettime %s\n",
		    strerror(errno));
	    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 */
}
