/*  $Revision: 1.6 $
**
**  Parse input to add to news overview database.
*/
#include <stdio.h>
#include <sys/types.h>
#include "configdata.h"
#include "clibrary.h"
#include <errno.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include "libinn.h"
#include "macros.h"
#include "paths.h"
#include "qio.h"


STATIC BOOL	InSpoolDir;


/*
**  Try to make one directory.  Return FALSE on error.
*/
STATIC BOOL
MakeDir(Name)
    char		*Name;
{
    struct stat		Sb;

    if (mkdir(Name, GROUPDIR_MODE) >= 0)
	return TRUE;

    /* See if it failed because it already exists. */
    return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode);
}


/*
**  Make overview directory if not in spool directory.  Return 0 if ok,
**  else -1.
*/
STATIC BOOL
MakeOverDir(Name)
    register char	*Name;
{
    register char	*p;
    BOOL		made;

    if (InSpoolDir)
	return FALSE;

    /* Optimize common case -- parent almost always exists. */
    if (MakeDir(Name))
	return TRUE;

    /* Try to make each of comp and comp/foo in turn. */
    for (p = Name; *p; p++)
	if (*p == '/') {
	    *p = '\0';
	    made = MakeDir(Name);
	    *p = '/';
	    if (!made)
		return FALSE;
	}

    return MakeDir(Name);
}


/*
**  Get the lock for the group, then open the data file and append the
**  new data.  Return FALSE on error.
*/
STATIC BOOL
WriteData(Dir, Art, Rest, Complain)
    char		*Dir;
    char		*Art;
    char		*Rest;
    BOOL		Complain;
{
    static char		TAB[] = "\t";
    static char		NL[] = "\n";
    struct iovec	iov[4];
    register int	fd;
    register int	i;
    register BOOL	ok;
    char		file[SPOOLNAMEBUFF];
    struct stat		Sb;

    /* Build the I/O vector. */
    i = 0;
    iov[i].iov_base = Art;
    iov[i++].iov_len = strlen(Art);
    iov[i].iov_base = TAB;
    iov[i++].iov_len = 1;
    iov[i].iov_base = Rest;
    iov[i++].iov_len = strlen(Rest);
    iov[i].iov_base = NL;
    iov[i++].iov_len = 1;

    /* Name the data file. */
    (void)sprintf(file, "%s/%s", Dir, _PATH_OVERVIEW);

    /* Open and lock the file. */
    for ( ; ; ) {
	if ((fd = open(file, O_WRONLY | O_CREAT | O_APPEND, ARTFILE_MODE)) < 0) {
	    if (Complain && errno != ENOENT)
		(void)fprintf(stderr, "overchan cant open %s, %s\n",
			file, strerror(errno));
	    return FALSE;
	}
	if (LockFile(fd, FALSE) < 0)
	    /* Wait for it. */
	    (void)LockFile(fd, TRUE);
	else {
	    /* Got the lock; make sure the file is still there. */
	    if (fstat(fd, &Sb) < 0) {
		(void)fprintf(stderr, "overchan cant fstat %s, %s\n",
			file, strerror(errno));
		(void)close(fd);
		return FALSE;
	    }
	    if (Sb.st_nlink > 0)
		break;
	}
	/* Close file -- expireover might have removed it -- and try again. */
	(void)close(fd);
    }

    ok = TRUE;
    if (xwritev(fd, iov, i) < 0) {
	(void)fprintf(stderr, "overchan cant write %s %s\n",
		file, strerror(errno));
	ok = FALSE;
    }

    /* Close up and return. */
    if (close(fd) < 0) {
	(void)fprintf(stderr, "overchan cant close %s %s\n",
		file, strerror(errno));
	ok = FALSE;
    }
    return ok;
}


/*
**  Process the input.  Data can come from innd:
**	news/group/name/<number> [space news/group/<number>]... \t data
**  or from mkov:
**	news/group/name \t number \t data
*/
STATIC void
ProcessIncoming(INNinput, qp)
    BOOL		INNinput;
    QIOSTATE		*qp;
{
    register char	*Dir;
    register char	*Art;
    register char	*Rest;
    register char	*p;

    for ( ; ; ) {
	/* Read the first line of data. */
	if ((p = QIOread(qp)) == NULL) {
	    if (QIOtoolong(qp)) {
		(void)fprintf(stderr, "overchan line too long\n");
		continue;
	    }
	    break;
	}

	/* If doing mkov output, process that line and continue. */
	if (!INNinput) {
	    /* Split up the fields. */
	    Dir = p;
	    if ((p = strchr(p, '\t')) == NULL || p[1] == '\0') {
		(void)fprintf(stderr, "overchan missing field 1: %s\n", Dir);
		return;
	    }
	    *p++ = '\0';
	    Art = p;
	    if ((p = strchr(p, '\t')) == NULL || p[1] == '\0') {
		(void)fprintf(stderr, "overchan missing field 2: %s\n", Dir);
		return;
	    }
	    *p++ = '\0';

	    /* Write data. */
	    if (!WriteData(Dir, Art, p, FALSE)
	     && (!MakeOverDir(Dir) || !WriteData(Dir, Art, p, TRUE)))
		(void)fprintf(stderr, "overchan cant update %s %s\n",
			Dir, strerror(errno));
	    continue;
	}

	/* Nip off the first part. */
	Dir = p;
	if ((Rest = strchr(p, '\t')) == NULL) {
	    (void)fprintf(stderr, "overchan bad input\n");
	    continue;
	}
	*Rest++ = '\0';

	/* Process all fields in the first part. */
	for ( ; *Dir; Dir = p) {

	    /* Split up this field, then split it up. */
	    for (p = Dir; *p; p++)
		if (ISWHITE(*p)) {
		    *p++ = '\0';
		    break;
		}

	    if ((Art = strrchr(Dir, '/')) == NULL || Art[1] == '\0') {
		(void)fprintf(stderr, "overchan bad entry %s\n", Dir);
		continue;
	    }
	    *Art++ = '\0';

	    /* Write data. */
	    if (!WriteData(Dir, Art, Rest, FALSE)
	     && (!MakeOverDir(Dir) || !WriteData(Dir, Art, Rest, TRUE)))
		(void)fprintf(stderr, "overchan cant update %s %s\n",
			Dir, strerror(errno));
	}
    }

    if (QIOerror(qp))
	(void)fprintf(stderr, "overchan cant read %s\n", strerror(errno));
    QIOclose(qp);
}


STATIC NORETURN
Usage()
{
    (void)fprintf(stderr, "usage:  overchan [-c] [-D dir] [files...]\n");
    exit(1);
}


int
main(ac, av)
    int			ac;
    char		*av[];
{
    register int	i;
    QIOSTATE		*qp;
    char		*Dir;
    BOOL		INNinput;

    /* Set defaults. */
    Dir = _PATH_OVERVIEWDIR;
    INNinput = TRUE;
    (void)umask(NEWSUMASK);

    /* Parse JCL. */
    while ((i = getopt(ac, av, "cD:")) != EOF)
	switch (i) {
	default:
	    Usage();
	    /* NOTREACHED */
	case 'c':
	    INNinput = FALSE;
	    break;
	case 'D':
	    Dir = optarg;
	    break;
	}
    ac -= optind;
    av += optind;
    InSpoolDir = EQ(Dir, _PATH_SPOOL);

    if (chdir(Dir) < 0) {
	(void)fprintf(stderr, "overchan cant chdir %s %s\n",
		Dir, strerror(errno));
	exit(1);
    }

    if (ac == 0)
	ProcessIncoming(INNinput, QIOfdopen(STDIN, QIO_BUFFER));
    else {
	for ( ; *av; av++)
	    if (EQ(*av, "-"))
		ProcessIncoming(INNinput, QIOfdopen(STDIN, QIO_BUFFER));
	    else if ((qp = QIOopen(*av, QIO_BUFFER)) == NULL)
		(void)fprintf(stderr, "overchan cant open %s %s\n",
			*av, strerror(errno));
	    else
		ProcessIncoming(INNinput, qp);
    }

    exit(0);
    /* NOTREACHED */
}
