/*************************************************
*                      Smailmon                  *
*************************************************/

/* Copyright (c) University of Cambridge 1995. */

/* P. Hazel, February 1995*/
/* Last Modified: February 1995 */

/* This module contains code for scanning the smaill log,
extracting information from it, and displaying a "tail". */

#include "smailmon.h"

#define log_buffer_len 4096      /* For each log entry */



/*************************************************
*             Write to the log display           *
*************************************************/

static int visible = 0;
static int scrolled = FALSE;
static int size = 0;
static int top = 0;

static void show_log(char *s, ...)
{
int length, newtop;
va_list ap;
XawTextBlock b;
char buffer[1024];

/* Initialize the text block structure */

b.firstPos = 0;
b.ptr = buffer;
b.format = FMT8BIT;

/* We want to know whether the window has been scrolled back or not,
so that we can cease automatically scrolling with new text. This turns
out to be tricky with the text widget. We can detect whether the
scroll bar has been operated by checking on the "top" value, but it's
harder to detect that it has been returned to the bottom. The following
heuristic does its best. */

newtop = XawTextTopPosition(log_widget);
if (newtop != top)
  {
  if (!scrolled)
    {
    visible = size - top;      /* save size of window */
    scrolled = newtop < top;
    }
  else if (newtop > size - visible) scrolled = FALSE;
  top = newtop;
  }

/* Format the text that is to be written. */

va_start(ap, s);
vsprintf(buffer, s, ap);
va_end(ap);
length = (int)strlen(buffer);

/* If this would overflow the buffer, throw away 50% of the
current stuff in the buffer. Code defensively against odd
extreme cases that shouldn't actually arise. */

if (size + length > log_buffer_size)
  {
  if (size == 0) length = log_buffer_size/2; else
    {
    int cutcount = log_buffer_size/2;
    if (cutcount > size) cutcount = size; else
      {
      while (cutcount < size && log_display_buffer[cutcount] != '\n')
        cutcount++;
      cutcount++;
      }
    b.length = 0;
    XawTextReplace(log_widget, 0, cutcount, &b);
    size -= cutcount;
    top -= cutcount;
    if (top < 0) top = 0;
    if (top < cutcount) XawTextInvalidate(log_widget, 0, 999999);
    xs_SetValues(log_widget, 1, "displayPosition", top);
    }
  }

/* Insert the new text at the end of the buffer. */

b.length = length;
XawTextReplace(log_widget, 999999, 999999, &b);
size += length;

/* When not scrolled back, we want to keep the bottom line
always visible. */

if (!scrolled)
  {
  XawTextSetInsertionPoint(log_widget, size - 2);
  top = XawTextTopPosition(log_widget);
  }
}




/*************************************************
*            Function to read the log            *
*************************************************/

/* We read any new log entries, and use their data to
updated total counts for the configured stripcharts.
The count for the queue chart is handled separately.
We also munge the log entries and display a one-line
version in the log window. */

void read_log(void)
{
struct stat statdata;
char buffer[log_buffer_len];

fseek(LOG, log_position, SEEK_SET);

while (fgets(buffer, log_buffer_len, LOG) != NULL)
  {
  int i;
  int length = (int)strlen(buffer);
  char *time, *id;
  char *p = buffer;

  /* Skip totally blank lines */

  while (*p == ' ' || *p == '\t') p++;
  if (*p == '\n') continue;

  /* We jump back here from the "delivered" handler when the
  next non-blank line is not "completed". */

  REDO:

  /* Concatenate lines until a blank line is reached.
  This should get in both forms of 3.1.29 log entries.
  Program defensively against the possiblity of very
  long lines or absence of blanks. */

  for (;;)
    {
    p = buffer + length;
    if (fgets(p, log_buffer_len - length, LOG) == NULL) break;
    length += (int)strlen(p);
    if (length > log_buffer_len - 100) break;   /* insurance */
    while (*p == ' ' || *p == '\t') p++;
    if (*p == '\n') break;
    }

  /* We should now have a complete log entry in the buffer; check
  it for various regular expression matches and take appropriate
  action. First, reset the substring handling to recover its store. */

  substr_reset();

  /* First, update any stripchart data values, noting that the zeroth
  stripchart is the queue length, which is handled elsewhere. */

  for (i = 1; i < stripchart_number; i++)
    {
    if (regexec(stripchart_regexp[i], buffer)) stripchart_total[i]++;
    }

  /* Munge the log entry and display shortened form on one line.
  We omit the date and show only the time, and do other general
  shortening operations. */

  time = substr(buffer, 9, 8);
  id = substr(buffer, 21, 14);

  str_remove(buffer, "... ", NULL);     /* Remove "... " and */
  str_remove(buffer, "(ERR_", ") ");    /* "(ERR_xxx) " to save space */


  /* Deal with "received" messages. */

  if (strstric(buffer, "] received", TRUE) != NULL)
    {
    char *from = str_subfield(buffer, "from: ", 1);
    char *host = str_subfield(buffer, "host:", 2);
    char *ident = str_subfield(buffer, "ident:", 1);

    /* If host = [ip-address] [ip-address] and the addresses are the
    same (common from PCs), remove one of them */

    if (host[1] == '[')
      {
      char *p = host + 2;
      while (*p && *p != '[') p++;
      if (*p == '[' && strncmp(host+1, p, (int)strlen(p)) == 0) *p = 0;
      }

    /* Display the line */

    show_log("%s %s <== %s%s%s\n", time, id, from, host, ident);
    }


  /* Deal with "delivered" messages */

  else if (strstric(buffer, "] delivered", TRUE) != NULL)
    {
    int redo = FALSE;
    char *arrow = "-->";
    char *to = str_subfield(buffer, "to: ", 1);
    char *orig_to = str_subfield(buffer, "orig-to: ", 1);
    char *via = str_subfield(buffer, "via: ", 1);

    /* Remove <> from to and orig_to if present */

    if (to[0] == '<') { to++; to[(int)strlen(to)-1] = 0; }
    if (orig_to[0] == '<') { orig_to++; orig_to[(int)strlen(orig_to)-1] = 0; }

    /* Blank orig_to if the same as to */

    if (strcmp(to, orig_to) == 0) orig_to = "";

    /* Or if orig_to = to@visible_domain */

    if (orig_to[0])
      {
      sprintf(buffer, "%s@%s", to, visible_name);
      if (strcmp(orig_to, buffer) == 0) orig_to  = "";
      }

    /* If the next non-blank log line is "completed" for
    this message, change the indicator and don't display
    "completed". Otherwise set the flag to reprocess the
    already-read new line. */

    while (fgets(buffer, log_buffer_len, LOG) != NULL)
      {
      char *nextid;
      char *p = buffer;
      length = (int)strlen(buffer);
      while (*p == ' ' || *p == '\t') p++;
      if (*p == '\n') continue;
      nextid = substr(buffer, 21, 14);
      if (strcmp(id, nextid) == 0 &&
        strstric(buffer, "Completed.", TRUE) != NULL) arrow = "==>";
      else redo = TRUE;
      break;
      }

    fseek(LOG, ftell(LOG), SEEK_SET);  /* in case hit eof */

    /* Display the line */

    show_log("%s %s %s %s%s%s%s%s%s\n", time, id, arrow, to,
      (orig_to[0]? " <" : ""),
      orig_to,
      (orig_to[0]? ">" : ""),
      (via[0]? " via " : ""),
      via);

    /* Argh! A dreaded goto - but seems the simplest way of
    achieving what's wanted here. */

    if (redo) goto REDO;
    }

  /* Show all non-reception/delivery messages, except for
  "No such file or directory" & "lock failed: Permission denied"
  Distinguish between message-related and other entries. */

  else if (strstric(buffer, "no such file or directory", FALSE) == NULL &&
           strstric(buffer, "lock failed: permission denied", FALSE) == NULL)
    {
    char *p = buffer + (int)strlen(buffer);
    while (--p >= buffer && (*p == '\n' || *p == ' '));
    *(++p) = 0; 
    if (buffer[19] == '[') show_log("%s %s %s\n", time, id, buffer+37);
      else show_log("%s %s\n", time, buffer+19); 
    }  
    
  /* Notice defer messages, and add the destination if it
  isn't already on the list for this message. This
  code picks up deferrals that don't cause msglog entries. */

  if ((p = strstric(buffer, "deferred:", TRUE)) != NULL)
    { 
    char *q; 
    queue_item *qq;
      
    while (*(--p) == ' ');
    q = p;
    while (*(--p) != ' ');
    
    if (p[1] != '<')
      {
      *p = '<';
      q[1] = '>';
      q[2] = 0;
      }
    else
      {
      p++;
      q[1] = 0;
      }            
    
    if ((qq = find_queue(id, queue_noop)) != NULL) 
      find_dest(qq, p, dest_add); 
    }
  }


/* We have to detect when the log file is changed, and switch
to the new file. In practice, this means that some deliveries
might go unrecorded, since they'll be written to the old file,
but this happens in the middle of the night, and I don't think
the hassle of keeping track of two log files is worth it. */

stat(log_file, &statdata);
if (log_inode != statdata.st_ino)
  {
  FILE *TEST;

  /* Experiment shows that sometimes you can't immediately open
  the new log file - presumably immediately after the old one
  is renamed and before the new one exists. Therefore do a
  trial open first to be sure. */

  if ((TEST = fopen(log_file, "r")) != NULL)
    {
    fclose(TEST);
    fclose(LOG);
    LOG = fopen(log_file, "r");
    if (LOG == NULL)
      {
      printf("*** Can't open log file\n");
      exit(99);
      }
    log_inode = statdata.st_ino;
    }
  }

/* Save the position we have got to in the log. */

log_position = ftell(LOG);
}

/* End of log.c */
