/*
 * W-NEWS       A simple NEWS processing package.
 *              Active.c is the libwnews interface to the active file.
 *
 * Version:     @(#)active.c	4.50    03-Feb-1995
 *
 * Author:      Miquel van Smoorenburg, <miquels@drinkel.ow.org>
 *              Copyright 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "libwnews.h"


/*
 * FIXME: all these functions assume an in-core active file.
 *        This will probably not work on systems with limited
 *        memory (Coherent3, Minix).  It _is_ possible to
 *        change all these routines to open and read the active
 *        file for every time we need access, but it would be
 *        slow, especially with INEWS_UNBATCH.- MvS.
 */

static ACT *active;		/* In-core active file. (head) */
static ACT *last;		/* Pointer to the tail */

static int locked;		/* Did we lock the active file ? */
static char act_fname[128];	/* File name of the active file (for locking) */
static int act_fd = -1;		/* File descriptor of the open active file */
FILE *act_fp = (FILE *)NULL;	/* File pointer (for fast reading) */
#if HAVE_LONGACT
int have_longact = 1;		/* Do we use 10 digits or 5 digits? */
#endif

_PRO( static void act_open, (void));
_PRO( static ACT *getline, (int lock, char *reread));


/* Open the active file, so we can start to use it (and lock it). */
static void
act_open()
{

  /* If needed, open active file for reading. */
  if (act_fp == (FILE *)NULL) {
	sprintf(act_fname, "%s/%s", CONFDIR, ACTIVE);

	/* Open in RW mode if possible, to be able to create a write lock. */
	if ((act_fp = fopen(act_fname, "r+")) == (FILE *)NULL &&
	    (act_fp = fopen(act_fname, "r"))  == (FILE *)NULL) {
		logmsg(LOGERR|LOGSYS, "Couldn't open active file\n");
		log_close();
		exit(1);
	}
  }
}


/* Read the entire active file, used by eg. control.c */
ACT *
act_rd()
{
  while(getline(NO_LOCK, NULL))
		;
  return(active);
}


/* Read the next line from the active file, and return a struct. */
static ACT *
getline(lock, reread)
int lock;
char *reread;
{
  char buff[128];
  off_t offset;
  char *sp, *bp;
  static char *mem = "out of memory";
  ACT *s;
  int do_reread = 0;

  /* Need to open the active file? */
  if (act_fp == (FILE *)NULL) act_open();

  /* See if we need to lock the active file. */
  if (lock != NO_LOCK) {
	if (wlock(act_fname, fileno(act_fp)) != OK) {
		log_close();
		exit(1);
	}
	locked++;
  }

  /* Remember where we are and read one line. */
  offset = ftell(act_fp);
  if (fgets(buff, 128, act_fp) == (char *)NULL) return((ACT *)NULL);

  s = (ACT *)NULL; /* To get gcc to shut up :-( */

  if (reread) {
	/* Find the structure. */
	for(s = active; s; s = s->next)
		if (strcmp(s->group, reread) == 0) {
			/* Found it, use it. */
			do_reread = 1;
			break;
		}
  }

  /* Allocate structure. */
  if (! do_reread) {
	if ((s = (ACT *)malloc(sizeof(ACT))) == (ACT *)NULL) {
		logmsg(LOGERR|LOGSYS, mem);
		if (locked) wunlock(act_fname, fileno(act_fp));
		log_close();
		exit(1);
	}
	/* Fill it in. */
	s->offset = offset;
	s->group = (char *)NULL;
  }

  /* Group name. */
  bp = buff;
  if ((sp = strchr(bp, ' ')) != NULL) {
	*sp++ = 0;
	if (! do_reread) {
		if ((s->group = (char *)malloc(strlen(bp) + 1))
		    == (char *)NULL) {
			logmsg(LOGERR|LOGSYS, mem);
			if (locked) wunlock(act_fname, fileno(act_fp));
			log_close();
			exit(1);
		}
		strcpy(s->group, bp);
	}
	bp = sp;
  }

  /* Last article. */
  if (sp && (sp = strchr(bp, ' ')) != NULL) {
	*sp++ = 0;
	s->last = atol(bp);
#if HAVE_LONGACT
	if (sp - bp == 5)  have_longact = 0;
	if (sp - bp == 10) have_longact = 1;
#endif
	bp = sp;
  }

  /* First article. */
  if (sp && (sp = strchr(bp, ' ')) != NULL) {
	*sp++ = 0;
	s->first = atol(bp);
	bp = sp;
  }

  /* Type. */
  if (sp && (sp = strchr(bp, '\n')) != NULL) {
	*sp++ = 0;
	s->type = *bp;
	/* See if there is trailing garbage. */
	if (bp[1] != 0) sp = (char *)NULL;
  }

  /* See if all went OK. */
  if (sp == (char *)NULL) {
	if (s->group) free(s->group);
	free(s);
	/* Read again. */	
	fseek(act_fp, offset, SEEK_SET);
	fgets(buff, 128, act_fp); 
	logmsg(LOGERR, "active file format error: %s", buff);
	if (locked) wunlock(act_fname, fileno(act_fp));
	log_close();
	exit(1);
  }

  /* Link to the end of the list. */
  if (! do_reread) {
	if (active == (ACT *)NULL) active = s;
	  else last->next = s;
	s->next = (ACT *)NULL;
	last = s;
  }

  return(s);
}


/* Get the next line of the active file. */
ACT *
next_group()
{
  return(getline(NO_LOCK, NULL));
}


/* Get a group by name. */
ACT *
get_group(name, lock)
char *name;
int lock;
{
  ACT *a;
  off_t offset;
  char temp[256];

  /* name might be part of an (ACT *) so copy it. */
  strcpy(temp, name);

  /* First try the linked list. */
  for(a = active; a; a = a->next)
	if (strcmp(a->group, name) == 0) {
		/* If we don't have to lock we're done. */
		if (lock == NO_LOCK) return(a);
		break;
	}

  /* If not found, continue reading the active file. */
  if (a == (ACT *)NULL) do {
	a = next_group();
  } while (a && strcmp(a->group, name) != 0);

  /* No lock or not found: return now. */
  if (lock == NO_LOCK || a == (ACT *)NULL) return(a);

  /* Yup. Lock and re-read (might have changed!) */
  offset = ftell(act_fp);
  fseek(act_fp, 0L, SEEK_SET);
  fseek(act_fp, a->offset, SEEK_SET);
  a = getline(MAKE_LOCK, temp);
  fseek(act_fp, offset, SEEK_SET);

  /* See if it's still the same group (!) */
  if (a == (ACT *)NULL || strcmp(temp, a->group) != 0) {
	/* FIXME: should we re-read the active file here? */
	if (a == (ACT *)NULL)
		logmsg(LOGSYS|LOGERR,
			"active file changed - cannot re-read %s\n", temp);
	  else
		logmsg(LOGSYS|LOGERR,
			"active file changed - expected %s, got %s\n",
				temp, a->group);
	if (locked) wunlock(act_fname, fileno(act_fp));
	return((ACT *)NULL);
  }

  /* Okay. We have found the group and it's locked if we wanted. */
  return(a);
}


/* Test if a group exists. */
int
testgroup(group)
char *group;
{
  return(get_group(group, NO_LOCK) ? OK : ERROR);
}


/* Update the active file by writing a new structure into it. */
void
act_upd(a)
ACT *a;
{
  char buff[128];

  /* Sanity check. */
  if (sizeof(int) > 2) {
#if HAVE_LONGACT
	if (have_longact) {
		if (a->first > 2000000000) a->first = 1;
		if (a->last  > 2000000000) a->last = 1;
	} else
#endif
	{
		if (a->first > 99999) a->first = 1;
		if (a->last  > 99999) a->last = 1;
	}
  } else {
	if ((unsigned)a->first > 32766U) a->first = 1;
	if ((unsigned)a->last  > 32766U) a->last = 1;
  }

  /* We assume the active file is locked so all will go well. */
  if (act_fd < 0) {
	if ((act_fd = open(act_fname, O_WRONLY)) < 0) {
		logmsg(LOGSYS|LOGERR, "cannot write to active file\n");
		if (locked) wunlock(act_fname, fileno(act_fp));
		log_close();
		exit(1);
	}
  }

  /* Seek to the right position. */
  if (lseek(act_fd, a->offset, SEEK_SET) < 0) {
	logmsg(LOGSYS|LOGERR, "cannot lseek(%d, %ld) in active file\n",
		act_fd, a->offset);
	if (locked) wunlock(act_fname, fileno(act_fp));
	log_close();
	exit(1);
  }

  /* And write the new line. */
#if HAVE_LONGACT
  if (have_longact)
  	sprintf(buff, "%s %010lu %010lu %c\n", a->group, a->last, a->first, a->type);
  else
#endif
  	sprintf(buff, "%s %05lu %05lu %c\n", a->group, a->last, a->first, a->type);
  (void) write(act_fd, buff, strlen(buff));

  /* Now unlock the active file. */
  wunlock(act_fname, fileno(act_fp));
  locked = 0;
}


/* Lock the active file, used by eg. control. */
void
act_lock()
{
  act_open();
  if (wlock(act_fname, fileno(act_fp)) != OK) {
	log_close();
	exit(1);
  }
  locked++;
}


/* Unlock the active file. */
void
act_unlock()
{
  if (act_fp && locked) wunlock(act_fname, fileno(act_fp));
}


/* Close the active file, remove any locks. (unused) */
void
act_close()
{
  ACT *a, *next;
 
  /* Close files. */
  if (act_fp) {
	fflush(act_fp);
	if (locked) wunlock(act_fname, fileno(act_fp));
	(void) fclose(act_fp);
	act_fp = (FILE *)NULL;
	locked = 0;
  }
  if (act_fd > 0) close(act_fd);
  act_fd = -1;

  /* Free memory. */
  for(a = active; a; a = next) {
	next = a->next;
	free(a->group);
	free(a);
  }
  active = (ACT *)NULL;
  last   = (ACT *)NULL;
}
