/*
 * W-NEWS       A simple NEWS processing package.
 *              Unbatch is used by the NEWS system to unbatch the news
 *              batches that were prepared and queued by the 'rnews'
 *              program.  Its task is to (optionally) uncompress a batch,
 *              and then to unpack all articles from the news batch, and
 *              to put those articles in the article spool directories.
 *
 * Usage:       unbatch [-d] [ batch_file ]
 *
 * Version:     @(#)unbatch.c	4.50	03-Feb-1995
 *
 * Authors:	Fred N. van Kempen, <waltje@Infomagic.COM>
 *              Edvard Tuinder, <ed@ow.org>
 *		Miquel van Smoorenburg, <miquels@drinkel.ow.org>
 *
 *		Copyright 1988-1993 MicroWalt Corporation
 *		Copyright 1994,1995 Cistron Software C.V.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 */
#include "wnews.h"


char *Release = VERSION;
char *Version = "@(#) unbatch 4.50 (03-Feb-1995)";


char *program;				/* Program name			*/


_PRO( int unbatch, (char *batch)					);


/*
 * Unpack the current batch of articles contained in 'batch'.
 * A batch consists of a header, followed by the actual articles
 * in a format like:
 *
 *      #! rnews 1234\n
 *      article-text
 *      #! rnews 5523\n
 *      article-text
 *      ...
 *
 * The number following the 'rnews' text is the size of the message
 * in bytes.
 *
 * Optionally, we may have a compressed batch file, which is essentially
 * a normal batch (see above), but now routed through the compress(1)
 * program, and prepended with a header like:
 *
 *      #! cunbatch\n
 *
 * So, we have to perform the following actions:
 *
 *      1) (optionally) decompress a compressed batch.
 *      2) Separate the messages.
 *      3) Write the separated messages into their dirs by calling the
 *         'inews' program for local posting.
 */
int
unbatch(batch)
char *batch;
{
  char buff[1024], fname[128];
  char cmd[128];
  char *uncomp;
  int s;
  register int c;		/* char could be unsigned (!) so no EOF */
  register FILE *fp, *ap;
#if !INEWS_UNBATCH
  char *bp;
  long count;
  int maybe_batch, isbatch = 0;
#else
  char batchname[128];
#endif

  logmsg(LOGINFO, "processing %s\n", batch);

  /* First of all, open the batch file. */
  if ((fp = fopen(batch, "r")) == (FILE *)NULL) {
	logmsg(LOGERR|LOGSYS, "cannot open batch %s\n", batch);
	return(ERROR);
  }

  /* Now, check the type of this batch. */
  if (fgets(buff, 128, fp) == (char *)NULL) {
	logmsg(LOGERR, "cannot read batch header from %s\n", batch);
	(void) fclose(fp);
	return(ERROR);
  }

  /* Make two temporary files. */
  strcpy(fname, "/tmp/unb.XXXXXX");
  mktemp(fname);

  /* Is this a compressed batch? */
  if ((uncomp = p_uncomp(buff)) != (char *)NULL) {

#if INEWS_UNBATCH
	strcpy(batchname, fname);
#endif
	/* Pipe the compressed file into the uncompressor. */
	sprintf(buff, "%s > %s 2>/dev/null", uncomp, fname);
	if ((ap = wpopen(buff, "w")) == (FILE *)NULL) {
		logmsg(LOGERR, "cannot popen(%s)\n", buff);
		(void) unlink(fname);
		return(ERROR);
	}
	while((c = fgetc(fp)) != EOF)
		fputc(c, ap);

	/* Close the pipe, check for errors. */
	fclose(fp);
	if (wpclose(ap) != 0) {
		logmsg(LOGERR, "error while exec'ing(%s)\n", buff);
		(void) unlink(fname);
		return(ERROR);
	}

  	/* We now have a decompressed file in /tmp. Open it! */
	if ((fp = fopen(fname, "r")) == (FILE *)NULL) {
		logmsg(LOGERR, "cannot open(%s)\n", fname);
		(void) unlink(fname);
		return(ERROR);
	}
  } else {
#if INEWS_UNBATCH
	strcpy(batchname, batch);
#endif
	rewind(fp);
	/* Do some checking. */
	if (strncmp(buff, "#!", 2) == 0 &&
	    strncmp(buff, RNEWSHDR, strlen(RNEWSHDR)) != 0) {
		logmsg(LOGERR, "unknown batch type %s", buff);
		(void) unlink(fname);
		return(ERROR);
	}
  }

#if INEWS_UNBATCH

  /* Send the whole batch into inews. */
  sprintf(cmd, "%s/inews %s-p %s", LIBDIR, debug ? "-v " : "", batchname);
  s = wsystem(cmd);
  if (s != 0) {
	logmsg(LOGINFO|LOGSYS, "INEWS error %d, quitting\n", s);
	(void) fclose(fp);
	return(ERROR);
  }

#else

  /* Build a calling sequence for 'inews'. */
  sprintf(cmd, "%s/inews %s-p -", LIBDIR, debug ? "-v" : "");

  /*
   * We are now ready to post articles.
   * If this is a batch of files, then the first line will start
   * with the "!# rnews" header (RNEWSHDR) text.  Otherwise, the
   * text might be a single article.  Start reading the text and
   * post the generated articles by calling inews(8).
   */
  while(1) {
	if (fgets(buff, 1024, fp) == (char *)NULL) break;
	/* Start the inews program. */
	if ((ap = wpopen(cmd, "w")) == (FILE *)NULL) {
		logmsg(LOGERR|LOGSYS, "cannot execute(%s)\n", cmd);
		(void) fclose(fp);
		(void) unlink(fname);
		return(ERROR);
	}

	/* Check if we are on the correct boundary. */
	maybe_batch = strncmp(buff, RNEWSHDR, strlen(RNEWSHDR)) == 0;
	if (isbatch && !maybe_batch) {

		/* Give a warning and keep on reading for a valid start. */
		logmsg(LOGERR, "batch out of sync, trying to re-sync.\n");
		while(!maybe_batch) {
			if (fgets(buff, 1024, fp) == (char *)NULL) break;
			maybe_batch =
				strncmp(buff, RNEWSHDR, strlen(RNEWSHDR)) == 0;
		}
		if (maybe_batch == 0) {
		    logmsg(LOGERR, "rest of batch contained garbage, dumped.\n");
		    break;
		} else
		    logmsg(LOGERR, "re-synced, maybe lost one article.\n");

	}
	if (maybe_batch) isbatch = 1;

	if (!isbatch) {
		(void) rewind(fp);	/* single article, no header!	*/
		count = (long) -1L;	/* all of the "batch" file..	*/
	} else {
		/* Normal article.  Determine its length. */
		if ((bp = strchr(buff, '\n')) != (char *)NULL) *bp = '\0';
		bp = buff + strlen(RNEWSHDR);
		count = atol(bp);
	}

	/* Extract the article from the batch (or single article) file. */
	while(count != 0L) {
		c = fgetc(fp);
		if (c == EOF) break;
		fputc(c, ap);
		if (count > 0L) count--;
	}
	(void) fflush(ap);
	s = wpclose(ap);

	/* Article done.  Should we continue? */
	if (s != 0) {
		logmsg(LOGINFO|LOGSYS, "INEWS error %d, quitting\n", s);
		(void) fclose(fp);
		return(ERROR);
	}

  }
#endif

  /* Close batch file and remove (decompressed temporary) files. */
  (void) fclose(fp);
  (void) unlink(fname);

  return(OK);
}


void
usage()
{
  fputs("Usage: unbatch [-d] [ batch_file ]\n", stderr);
  exit(ERROR);
}


int
main(argc, argv)
int argc;
char *argv[];
{
  char buff[128];
  char unblock[128];
  struct dirent *de;
  DIR *dp;
  int st;

  program = strrchr(*argv, '/');
  if (program == (char *)NULL) program = *argv;
    else program++;

#ifndef COHERENT
  opterr = 0;
#endif
  while ((st = getopt(argc, argv, "d")) != EOF) switch(st) {
	case 'd':
		debug = 1;
		break;

	default:
		usage();
  }

  (void) umask(033);                    /* set protection to rw-r--r-- */
  signal(SIGPIPE, SIG_IGN);

  /* Check if we are already (or still?) running. */
  sprintf(unblock, "%s/%s", VARDIR, UNBLOCK);
  if (open(unblock, (O_CREAT|O_WRONLY|O_EXCL), 0) < 0) {
	logmsg(LOGERR, "locked (lockfile %s)\n", unblock);
	exit(ERROR);
  }

  /* At most one more argument allowed. */
  if (optind == (argc - 1)) {
	st = unbatch(argv[optind]);
	if (st == 0) (void) unlink(argv[optind]);
	(void) unlink(unblock);
	exit(st ? 1 : 0);
  } else if (optind != argc) usage();

  (void) chdir(SPOOLDIR);
  wmkspooldir(INCOMING, 0);
  if ((dp = opendir(INCOMING)) == (DIR *)NULL) {
	logmsg(LOGERR|LOGSYS, "cannot opendir(%s/%s)\n", SPOOLDIR, INCOMING);
	(void) unlink(unblock);
	exit(ERROR);
  }

  while ((de = readdir(dp)) != (struct dirent *)0) {
	if (de->d_name[0] == '.') continue;
	sprintf(buff, "%s/%s", INCOMING, de->d_name);
	if ((st = unbatch(buff)) != 0) {
		if ((link(buff, de->d_name)) == OK)
		  logmsg(LOGERR|LOGSYS, "inbound news garbled; saved in %s\n",
			 de->d_name);
		else
		  logmsg(LOGERR|LOGSYS, "inbound news garbled; Couldn't save it. Batch %s is lost\n", de->d_name);
	}
	(void) unlink(buff);
  }
  (void) closedir(dp);

  /* Remove the lockfile. */
  (void) unlink(unblock);

  exit(OK);
}
