/********************************************************
 * File: crashmaint.c
 * Created at Sun Jan 28 22:10:33 MSK 2001 by raorn // raorn@binec.ru
 * CrashMaint - messagebase maintenance
 * $Id: crashmaint.c,v 1.33 2002/10/11 21:36:19 raorn Exp $
 *******************************************************/
#include <machine/defs.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#else
# include <shared/getopt.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <sys/stat.h>

#include <shared/jblist.h>
#include <shared/jbstrcpy.h>
#include <shared/mystrncpy.h>
#include <shared/path.h>
#include <shared/file.h>
#include <shared/jbdir.h>
#include <shared/pattern.h>
#include <shared/logwrite.h>

#include <shared/storedmsg.h>
#include <shared/fidonet.h>

uchar verstr[] = "CrashMaint/" PLATFORM_NAME " " VERSION;

uchar LogFile[100] = {0};
ulong LogLevel = 3;
mode_t MsgbaseUmask;

struct Area {
  struct Area *Next;
  uchar Tagname[80];
  uchar Path[200];
  uchar Messagebase[20];
  ulong KeepNum, KeepDays;
};

struct jbList AreaList, ProcessList;

#define CMD_PURGE   0
#define CMD_PACK    1
#define CMD_LINK    2
#define CMD_MAX     3

struct Command {
  uchar *Arg, *Str;
};

struct Messagebase {
  uchar *Name;
  bool (*processfunc[CMD_MAX]) (struct Area * area);
};

bool PurgeAreaMSG(struct Area *area);
bool PackAreaMSG(struct Area *area);
#ifdef MSGBASE_JAM
bool PurgeAreaJAM(struct Area *area);
bool PackAreaJAM(struct Area *area);
bool LinkAreaJAM(struct Area *area);

time_t jam_utcoffset;
bool jam_quicklink = FALSE;
#endif
bool relink = FALSE;

struct Command Commands[CMD_MAX] = {
  {"purge", "purge"},
  {"pack", "pack"},
  {"link", "link"}
};

struct Messagebase Messagebases[] = {
#ifdef MSGBASE_JAM
  {"JAM", {
            PurgeAreaJAM,
            PackAreaJAM,
            LinkAreaJAM
          }
  },
#endif
  {"MSG", {
            PurgeAreaMSG,
            PackAreaMSG,
            NULL
          }
  },
  {NULL, {NULL, NULL, NULL}}
};

#define SHOWVER   128

bool ctrlc;

RETSIGTYPE breakfunc(int x)
{
  ctrlc = TRUE;
}

/******************** *.msg *********************/

struct Msg {
  struct Msg *Next;
  ulong Num, NewNum, Day;
};

struct jbList MsgList;

int Compare(const void *a1, const void *a2)
{
  struct Msg **m1, **m2;

  m1 = (struct Msg **) a1;
  m2 = (struct Msg **) a2;

  if ((*m1)->Num > (*m2)->Num)
    return (1);
  if ((*m1)->Num < (*m2)->Num)
    return (-1);
  return (0);
}

bool Sort(struct jbList * list)
{
  struct Msg *msg, **buf, **work;
  ulong nc;

  nc = 0;

  for (msg = (struct Msg *) list->First; msg; msg = msg->Next)
    nc++;

  if (nc == 0)
    return (TRUE);

  if (!(buf = (struct Msg **) malloc(nc * sizeof(struct StatsNode *))))
    return (FALSE);

  work = buf;

  for (msg = (struct Msg *) list->First; msg; msg = msg->Next)
    *work++ = msg;

  qsort(buf, nc, 4, Compare);

  jbNewList(list);

  for (work = buf; nc--;)
    jbAddNode(list, (struct jbNode *) *work++);

  free(buf);

  return (TRUE);
}

void MakeFidoDate(time_t tim, uchar * dest)
{
  struct tm *tp;
  time_t t;
  static uchar *monthnames[] =
    { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
    "Nov", "Dec", "???"
  };

  t = tim;
  tp = localtime(&t);

  snprintf(dest, 20, "%02d %s %02d  %02d:%02d:%02d", tp->tm_mday, monthnames[tp->tm_mon], tp->tm_year % 100, tp->tm_hour, tp->tm_min, tp->tm_sec);
}

uchar *scanfuncarea;
bool nomem;

void scanfunc(uchar * str)
{
  uchar buf[200];
  struct FileEntry *fe;
  ulong num, day;
  struct Msg *msg;

  if (strlen(str) < 5)
    return;

  if (stricmp(&str[strlen(str) - 4], ".msg") != 0)
    return;

  if (atol(str) < 2)
    return;

  MakeFullPath(scanfuncarea, str, buf, 200);

  if (!(fe = GetFileEntry(buf)))
    return;

  num = atol(str);
  day = fe->Date / (24 * 60 * 60);

  free(fe);

  if (!(msg = (struct Msg *) malloc(sizeof(struct Msg)))) {
    nomem = TRUE;
    return;
  }

  jbAddNode(&MsgList, (struct jbNode *) msg);

  msg->Num = num;
  msg->Day = day;
}

bool PurgeAreaMSG(struct Area *area)
{
  time_t today;
  ulong num, del, highwater, oldhighwater;
  struct Msg *msg;
  uchar buf[200], buf2[100];
  struct StoredMsg StoredMsg;
  FILE *fh;
  mode_t oldumask;

  highwater = 0;
  oldhighwater = 0;

  jbNewList(&MsgList);

  LogWrite(2, MISCINFO, "Purging %s...", area->Tagname);

  scanfuncarea = area->Path;

  if (!(ScanDir(area->Path, scanfunc))) {
    LogWrite(1, SYSTEMERR, "Error: Couldn't scan directory %s", area->Path);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    jbFreeList(&MsgList);
    return (TRUE);
  }

  if (nomem) {
    LogWrite(1, SYSTEMERR, "Out of memory");
    jbFreeList(&MsgList);
    return (FALSE);
  }

  if (!Sort(&MsgList)) {
    LogWrite(1, SYSTEMERR, "Out of memory");
    jbFreeList(&MsgList);
    return (FALSE);
  }

  if (!MsgList.First) {
    LogWrite(3, MISCINFO, " Area is empty");
    return (TRUE);
  }

  if (ctrlc) {
    jbFreeList(&MsgList);
    return (TRUE);
  }

  MakeFullPath(area->Path, "1.msg", buf, 200);

  if ((fh = fopen(buf, "rb"))) {
    if (fread(&StoredMsg, 1, sizeof(struct StoredMsg), fh) == sizeof(struct StoredMsg)) {
      highwater = StoredMsg.ReplyTo;
      oldhighwater = StoredMsg.ReplyTo;
    }
    fclose(fh);
  }

  if (area->KeepNum != 0) {
    num = 0;

    for (msg = (struct Msg *) MsgList.First; msg; msg = msg->Next)
      num++;

    msg = (struct Msg *) MsgList.First;
    del = 0;

    while (num > area->KeepNum && !ctrlc) {
      while (msg->Num == 0)
        msg = msg->Next;

      snprintf(buf2, 100, "%lu.msg", msg->Num);
      MakeFullPath(area->Path, buf2, buf, 200);

      if (msg->Num == highwater)
        highwater = 0;

      LogWrite(6, DEBUG, " Deleting message #%lu by number", msg->Num);

      unlink(buf);

      msg->Num = 0;
      num--;
      del++;
    }

    if (ctrlc) {
      jbFreeList(&MsgList);
      return (TRUE);
    }

    LogWrite(3, MISCINFO, " %lu messages deleted by number, %lu messages left", del, num);
  }

  if (area->KeepDays != 0) {
    del = 0;
    num = 0;

    today = time(NULL) / (24 * 60 * 60);

    for (msg = (struct Msg *) MsgList.First; msg && !ctrlc; msg = msg->Next) {
      if (today - msg->Day > area->KeepDays && msg->Num != 0) {
        snprintf(buf2, 100, "%lu.msg", msg->Num);
        MakeFullPath(area->Path, buf2, buf, 200);

        if (msg->Num == highwater)
          highwater = 0;

        LogWrite(6, DEBUG, " Deleting message #%lu by date", msg->Num);

        unlink(buf);

        msg->Num = 0;
        del++;
      } else {
        num++;
      }
    }

    if (ctrlc) {
      jbFreeList(&MsgList);
      return (TRUE);
    }

    LogWrite(3, MISCINFO, " %lu messages deleted by date, %lu messages left", del, num);
  }

  jbFreeList(&MsgList);

  if (highwater != oldhighwater) {
    strcpy(StoredMsg.From, "CrashEcho");
    strcpy(StoredMsg.To, "All");
    strcpy(StoredMsg.Subject, "HighWater mark");
    MakeFidoDate(time(NULL), StoredMsg.DateTime);

    StoredMsg.TimesRead = 0;
    StoredMsg.DestNode = 0;
    StoredMsg.OrigNode = 0;
    StoredMsg.Cost = 0;
    StoredMsg.OrigNet = 0;
    StoredMsg.DestNet = 0;
    StoredMsg.DestZone = 0;
    StoredMsg.OrigZone = 0;
    StoredMsg.OrigPoint = 0;
    StoredMsg.DestPoint = 0;
    StoredMsg.ReplyTo = highwater;
    StoredMsg.Attr = FLAG_SENT | FLAG_PVT;
    StoredMsg.NextReply = 0;

    MakeFullPath(area->Path, "1.msg", buf, 200);

    oldumask = umask(MsgbaseUmask);
    if ((fh = fopen(buf, "wb"))) {
      fwrite(&StoredMsg, sizeof(struct StoredMsg), 1, fh);
      fwrite("", 1, 1, fh);
      fclose(fh);
    }
    umask(oldumask);
  }

  return (TRUE);
}

bool PackAreaMSG(struct Area *area)
{
  ulong num, highwater, oldhighwater;
  struct Msg *msg;
  uchar buf[200], newbuf[200], buf2[100];
  struct StoredMsg StoredMsg;
  FILE *fh;
  mode_t oldumask;

  highwater = 0;
  oldhighwater = 0;

  jbNewList(&MsgList);

  LogWrite(2, MISCINFO, "Renumbering %s...", area->Tagname);

  scanfuncarea = area->Path;

  if (!(ScanDir(area->Path, scanfunc))) {
    LogWrite(1, SYSTEMERR, "Error: Couldn't scan directory %s", area->Path);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    jbFreeList(&MsgList);
    return (TRUE);
  }

  if (nomem) {
    LogWrite(1, SYSTEMERR, "Out of memory");
    jbFreeList(&MsgList);
    return (FALSE);
  }

  if (!Sort(&MsgList)) {
    LogWrite(1, SYSTEMERR, "Out of memory");
    jbFreeList(&MsgList);
    return (FALSE);
  }

  if (!MsgList.First) {
    LogWrite(3, MISCINFO, " Area is empty");
    return (TRUE);
  }

  if (ctrlc) {
    jbFreeList(&MsgList);
    return (TRUE);
  }

  MakeFullPath(area->Path, "1.msg", buf, 200);

  if ((fh = fopen(buf, "rb"))) {
    if (fread(&StoredMsg, 1, sizeof(struct StoredMsg), fh) == sizeof(struct StoredMsg)) {
      highwater = StoredMsg.ReplyTo;
      oldhighwater = StoredMsg.ReplyTo;
    }
    fclose(fh);
  }

  num = 2;

  msg = (struct Msg *) MsgList.First;

  while (msg && !ctrlc) {
    while (msg && msg->Num == 0)
      msg = msg->Next;

    if (msg) {
      msg->NewNum = num++;
      msg = msg->Next;
    }
  }

  for (msg = (struct Msg *) MsgList.First; msg && !ctrlc; msg = msg->Next)
    if (msg->Num != 0 && msg->Num != msg->NewNum) {
      snprintf(buf2, 100, "%lu.msg", msg->Num);
      MakeFullPath(area->Path, buf2, buf, 200);

      snprintf(buf2, 100, "%lu.msg", msg->NewNum);
      MakeFullPath(area->Path, buf2, newbuf, 200);

      if (highwater == msg->Num)
        highwater = msg->NewNum;

      LogWrite(6, DEBUG, " Renaming message %lu to %lu\n", msg->Num, msg->NewNum);

      rename(buf, newbuf);
    }

  if (ctrlc) {
    jbFreeList(&MsgList);
    return (TRUE);
  }

  LogWrite(3, MISCINFO, " Area renumbered");

  jbFreeList(&MsgList);

  if (highwater != oldhighwater) {
    strcpy(StoredMsg.From, "CrashEcho");
    strcpy(StoredMsg.To, "All");
    strcpy(StoredMsg.Subject, "HighWater mark");
    MakeFidoDate(time(NULL), StoredMsg.DateTime);

    StoredMsg.TimesRead = 0;
    StoredMsg.DestNode = 0;
    StoredMsg.OrigNode = 0;
    StoredMsg.Cost = 0;
    StoredMsg.OrigNet = 0;
    StoredMsg.DestNet = 0;
    StoredMsg.DestZone = 0;
    StoredMsg.OrigZone = 0;
    StoredMsg.OrigPoint = 0;
    StoredMsg.DestPoint = 0;
    StoredMsg.ReplyTo = highwater;
    StoredMsg.Attr = FLAG_SENT | FLAG_PVT;
    StoredMsg.NextReply = 0;

    MakeFullPath(area->Path, "1.msg", buf, 200);

    oldumask = umask(MsgbaseUmask);
    if ((fh = fopen(buf, "wb"))) {
      fwrite(&StoredMsg, sizeof(struct StoredMsg), 1, fh);
      fwrite("", 1, 1, fh);
      fclose(fh);
    }
    umask(oldumask);
  }

  return (TRUE);
}

/*************************** JAM ************************/

#ifdef MSGBASE_JAM

bool PurgeAreaJAM(struct Area * area)
{
  time_t today;
  ulong active, basenum, total, del, num, day;
  s_JamBase *Base_PS;
  s_JamBaseHeader BaseHeader_S;
  s_JamMsgHeader Header_S;
  int res;

  LogWrite(2, MISCINFO, "Purging %s...", area->Tagname);

  if (JAM_OpenMB(area->Path, &Base_PS)) {
    LogWrite(1, USERERR, "Failed to open messagebase \"%s\"", area->Path);
    return (TRUE);
  }

  if (JAM_LockMB(Base_PS, 10)) {
    LogWrite(1, USERERR, "Timeout when trying to lock messagebase \"%s\"", area->Path);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  if (JAM_ReadMBHeader(Base_PS, &BaseHeader_S)) {
    LogWrite(1, USERERR, "Failed to read header of messagebase \"%s\"", area->Path);
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  if (JAM_GetMBSize(Base_PS, &total)) {
    LogWrite(1, USERERR, "Failed to get size of messagebase \"%s\"", area->Path);
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  basenum = BaseHeader_S.BaseMsgNum;
  active = BaseHeader_S.ActiveMsgs;

  if (total == 0) {
    LogWrite(3, MISCINFO, " Area is empty");
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  if (area->KeepNum != 0) {
    num = 0;
    del = 0;

    while (num < total && active > area->KeepNum && !ctrlc) {
      res = JAM_ReadMsgHeader(Base_PS, num, &Header_S, NULL);

      if (res == 0) {
        /* Read success */
        if (!(Header_S.Attribute & MSG_DELETED) && !(Header_S.Attribute & MSG_LOCKED)) {
          /* Not already deleted or locked */
          LogWrite(6, DEBUG, " Deleting message #%lu by number", basenum + num);

          Header_S.Attribute |= MSG_DELETED;
          JAM_ChangeMsgHeader(Base_PS, num, &Header_S);

          BaseHeader_S.ActiveMsgs--;
          JAM_WriteMBHeader(Base_PS, &BaseHeader_S);

          active--;
          del++;
        }
      }

      num++;
    }

    if (ctrlc) {
      JAM_UnlockMB(Base_PS);
      JAM_CloseMB(Base_PS);
      return (TRUE);
    }

    LogWrite(3, MISCINFO, " %lu messages deleted by number, %lu messages left", del, active);
  }

  if (area->KeepDays != 0) {
    del = 0;
    num = 0;

    today = (time(NULL) - jam_utcoffset) / (24 * 60 * 60);

    while (num < total && !ctrlc) {
      res = JAM_ReadMsgHeader(Base_PS, num, &Header_S, NULL);

      if (res == 0) {
        /* Read success */
        day = Header_S.DateReceived / (24 * 60 * 60);

        if (day == 0)
          day = Header_S.DateProcessed / (24 * 60 * 60);

        if (day == 0)
          day = Header_S.DateWritten / (24 * 60 * 60);

        if (today - day > area->KeepDays && !(Header_S.Attribute & MSG_DELETED) && !(Header_S.Attribute & MSG_LOCKED)) {
          /* Not already deleted or locked and too old */
          LogWrite(6, DEBUG, " Deleting message #%lu by date\n", basenum + num);

          Header_S.Attribute |= MSG_DELETED;
          JAM_ChangeMsgHeader(Base_PS, num, &Header_S);

          BaseHeader_S.ActiveMsgs--;
          JAM_WriteMBHeader(Base_PS, &BaseHeader_S);

          del++;
          active--;
        }
      }

      num++;
    }

    if (ctrlc) {
      JAM_UnlockMB(Base_PS);
      JAM_CloseMB(Base_PS);
      return (TRUE);
    }

    LogWrite(3, MISCINFO, " %lu messages deleted by date, %lu messages left", del, active);
  }

  JAM_UnlockMB(Base_PS);
  JAM_CloseMB(Base_PS);

  return (TRUE);
}

bool PackAreaJAM(struct Area * area)
{
  ulong active, basenum, total, del, num;
  s_JamBase *Base_PS, *NewBase_PS;
  s_JamBaseHeader BaseHeader_S;
  s_JamMsgHeader Header_S;
  s_JamSubPacket *SubPacket_PS;
  int res, res1, res2;
  uchar buf[200], oldname[200], tmpname[200];
  bool firstwritten;
  uchar *msgtext;
  mode_t oldumask;

  LogWrite(2, MISCINFO, "Packing %s...", area->Tagname);

  if (JAM_OpenMB(area->Path, &Base_PS)) {
    LogWrite(1, USERERR, "Failed to open messagebase \"%s\"", area->Path);
    return (TRUE);
  }

  if (JAM_LockMB(Base_PS, 10)) {
    LogWrite(1, USERERR, "Timeout when trying to lock messagebase \"%s\"", area->Path);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  if (JAM_ReadMBHeader(Base_PS, &BaseHeader_S)) {
    LogWrite(1, USERERR, "Failed to read header of messagebase \"%s\"", area->Path);
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  if (JAM_GetMBSize(Base_PS, &total)) {
    LogWrite(1, USERERR, "Failed to get size of messagebase \"%s\"", area->Path);
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  basenum = BaseHeader_S.BaseMsgNum;
  active = BaseHeader_S.ActiveMsgs;

  if (total == 0) {
    LogWrite(3, MISCINFO, " Area is empty");
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  snprintf(buf, 200, "%s.cmtemp", area->Path);

  oldumask = umask(MsgbaseUmask);
  if (JAM_CreateMB(buf, 1, &NewBase_PS)) {
    umask(oldumask);
    LogWrite(1, USERERR, "Failed to create new messagebase \"%s\"", buf);
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }
  umask(oldumask);

  if (JAM_LockMB(NewBase_PS, 10)) {
    LogWrite(1, USERERR, "Timeout when trying to lock messagebase \"%s\"", buf);
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    JAM_CloseMB(NewBase_PS);
    JAM_RemoveMB(NewBase_PS, buf);
    return (TRUE);
  }

  /* Copy messages */
  del = 0;
  num = 0;
  firstwritten = FALSE;

  BaseHeader_S.ActiveMsgs = 0;

  while (num < total && !ctrlc) {
    res = JAM_ReadMsgHeader(Base_PS, num, &Header_S, NULL);

    if (res) {
      if (res == JAM_NO_MESSAGE) {
        if (firstwritten) {
          JAM_AddEmptyMessage(NewBase_PS);
        } else {
          BaseHeader_S.BaseMsgNum++;
          del++;
        }
      } else {
        LogWrite(1, USERERR, "Failed to read message %ld, cannot pack messagebase", num + basenum);
        JAM_UnlockMB(Base_PS);
        JAM_CloseMB(Base_PS);
        JAM_UnlockMB(NewBase_PS);
        JAM_CloseMB(NewBase_PS);
        JAM_RemoveMB(NewBase_PS, buf);
        return (TRUE);
      }
    } else {
      if (Header_S.Attribute & MSG_DELETED) {
        if (firstwritten) {
          JAM_AddEmptyMessage(NewBase_PS);
        } else {
          BaseHeader_S.BaseMsgNum++;
          del++;
        }
      } else {
        if (!firstwritten) {
          /* Set basenum */
          res = JAM_WriteMBHeader(NewBase_PS, &BaseHeader_S);

          if (res) {
            LogWrite(1, USERERR, "Failed to write messagebase header, cannot pack messagebase");
            JAM_UnlockMB(Base_PS);
            JAM_CloseMB(Base_PS);
            JAM_UnlockMB(NewBase_PS);
            JAM_CloseMB(NewBase_PS);
            JAM_RemoveMB(NewBase_PS, buf);
            return (TRUE);
          }

          firstwritten = TRUE;
        }

        /* Read header with all subpackets */
        res = JAM_ReadMsgHeader(Base_PS, num, &Header_S, &SubPacket_PS);

        if (res) {
          LogWrite(1, USERERR, "Failed to read message %ld, cannot pack messagebase", num + basenum);
          JAM_UnlockMB(Base_PS);
          JAM_CloseMB(Base_PS);
          JAM_UnlockMB(NewBase_PS);
          JAM_CloseMB(NewBase_PS);
          JAM_RemoveMB(NewBase_PS, buf);
          return (TRUE);
        }

        /* Read message text */
        msgtext = NULL;

        if (Header_S.TxtLen) {
          if (!(msgtext = malloc(Header_S.TxtLen))) {
            LogWrite(1, SYSTEMERR, "Out of memory");
            JAM_DelSubPacket(SubPacket_PS);
            JAM_UnlockMB(Base_PS);
            JAM_CloseMB(Base_PS);
            JAM_UnlockMB(NewBase_PS);
            JAM_CloseMB(NewBase_PS);
            JAM_RemoveMB(NewBase_PS, buf);
            return (FALSE);
          }

          res = JAM_ReadMsgText(Base_PS, Header_S.TxtOffset, Header_S.TxtLen, msgtext);

          if (res) {
            LogWrite(1, USERERR, "Failed to read message %ld, cannot pack messagebase", num + basenum);
            JAM_DelSubPacket(SubPacket_PS);
            JAM_UnlockMB(Base_PS);
            JAM_CloseMB(Base_PS);
            JAM_UnlockMB(NewBase_PS);
            JAM_CloseMB(NewBase_PS);
            JAM_RemoveMB(NewBase_PS, buf);
            return (TRUE);
          }
        }

        /* Write new message */
        res = JAM_AddMessage(NewBase_PS, &Header_S, SubPacket_PS, msgtext, Header_S.TxtLen);

        if (msgtext)
          free(msgtext);
        JAM_DelSubPacket(SubPacket_PS);

        BaseHeader_S.ActiveMsgs++;

        if (res) {
          LogWrite(1, USERERR, "Failed to copy message %ld (disk full?), cannot pack messagebase", num + basenum);
          JAM_UnlockMB(Base_PS);
          JAM_CloseMB(Base_PS);
          JAM_UnlockMB(NewBase_PS);
          JAM_CloseMB(NewBase_PS);
          JAM_RemoveMB(NewBase_PS, buf);
          return (TRUE);
        }
      }
    }

    num++;
  }

  /* Write back header */
  BaseHeader_S.ModCounter++;

  res = JAM_WriteMBHeader(NewBase_PS, &BaseHeader_S);

  if (res) {
    LogWrite(1, USERERR, "Failed to write messagebase header, cannot pack messagebase");
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    JAM_UnlockMB(NewBase_PS);
    JAM_CloseMB(NewBase_PS);
    JAM_RemoveMB(NewBase_PS, buf);
    return (TRUE);
  }

  JAM_UnlockMB(Base_PS);
  JAM_CloseMB(Base_PS);

  JAM_UnlockMB(NewBase_PS);
  JAM_CloseMB(NewBase_PS);

  if (ctrlc) {
    JAM_RemoveMB(NewBase_PS, buf);
    return (TRUE);
  }

  /* This could not be done with JAMLIB...  */
  snprintf(oldname, 200, "%s%s", area->Path, EXT_HDRFILE);
  snprintf(tmpname, 200, "%s.cmtemp%s", area->Path, EXT_HDRFILE);
  res1 = unlink(oldname);
  res2 = rename(tmpname, oldname);

  if (!res1 && !res2) {
    snprintf(oldname, 200, "%s%s", area->Path, EXT_TXTFILE);
    snprintf(tmpname, 200, "%s.cmtemp%s", area->Path, EXT_TXTFILE);
    res1 = unlink(oldname);
    res2 = rename(tmpname, oldname);
  }

  if (!res1 && !res2) {
    snprintf(oldname, 200, "%s%s", area->Path, EXT_IDXFILE);
    snprintf(tmpname, 200, "%s.cmtemp%s", area->Path, EXT_IDXFILE);
    res1 = unlink(oldname);
    res2 = rename(tmpname, oldname);
  }

  if (!res1 && !res2) {
    /* snprintf(oldname, 200, "%s%s", area->Path, EXT_LRDFILE); */
    snprintf(tmpname, 200, "%s.cmtemp%s", area->Path, EXT_LRDFILE);
    /* Keep lastread file */
    res2 = unlink(tmpname);
  }

  if (res1 || res2) {
    LogWrite(1, SYSTEMERR, "Failed to update area. The area might be in use by another program");
    return (FALSE);
  }

  LogWrite(3, MISCINFO, " %ld deleted messages removed from messagebase", del);

  return (TRUE);
}

/************************** Linking ***********************/

struct jMsg {
  unsigned long MsgIdCRC;
  unsigned long ReplyCRC;
  uchar *MsgIdData;
  uchar *ReplyData;
  unsigned long ReplyTo;
  unsigned long Reply1st;
  unsigned long ReplyNext;
  unsigned long OldReplyTo;
  unsigned long OldReply1st;
  unsigned long OldReplyNext;
};

int jam_CompareMsgIdReply(s_JamBase * Base_PS, struct jMsg *msgs, ulong msgidmsg, ulong replymsg)
{
  if (msgs[msgidmsg].MsgIdCRC != msgs[replymsg].ReplyCRC)
    return FALSE;

  if (jam_quicklink ||
      (msgs[msgidmsg].MsgIdData && msgs[replymsg].ReplyData &&
       !strcmp(msgs[replymsg].ReplyData, msgs[msgidmsg].MsgIdData)))
    return TRUE;

  return FALSE;
}

/* dest is a reply to num */
void jam_setreply(struct jMsg *msgs, ulong nummsgs, ulong base, ulong num, ulong dest)
{
  int n, times;

  if (msgs[dest].ReplyTo)
    /* Already linked */
    return;

  LogWrite(6, DEBUG, " Linking #%lu as a reply to #%ld", base + dest, base + num);

  msgs[dest].ReplyTo = num + base;

  if (msgs[num].Reply1st == 0) {
    msgs[num].Reply1st = dest + base;
  } else {
    n = msgs[num].Reply1st - base;
    if (n == dest)
      return;

    if (n < 0 || n >= nummsgs) {
      /* Oops! Base seems to be b0rken */
      LogWrite(2, ACTIONINFO, "Warning: message #%ld is linked to something outside the base", num + base);
      return;
    }

    times = 0;
    while (msgs[n].ReplyNext) {
      times++;

      if (times > 1000) {
        /* Something appears to have gone wrong */
        LogWrite(2, ACTIONINFO, "Warning: >1000 replies to message %ld or circular reply links", num + base);
        return;
      }

      n = msgs[n].ReplyNext - base;
      if (n == dest)
        return;

      if (n < 0 || n >= nummsgs) {
        /* Oops! Base seems to be b0rken */
        LogWrite(2, ACTIONINFO, "Warning: message #%ld is linked to something outside the base", num + base);
        return;
      }
    }

    msgs[n].ReplyNext = dest + base;
  }
}

int LinkAreaJAM(struct Area *area)
{
  ulong nummsgs, basenum, res;
  int c, d;
  s_JamBase *Base_PS;
  s_JamBaseHeader BaseHeader_S;

  struct jMsg *msgs;

  LogWrite(2, MISCINFO, "Linking %s...", area->Tagname);
  fflush(stdout);

  if (JAM_OpenMB(area->Path, &Base_PS)) {
    LogWrite(1, USERERR, "Failed to open messagebase \"%s\"", area->Path);
    return (TRUE);
  }

  if (JAM_LockMB(Base_PS, 10)) {
    LogWrite(1, USERERR, "Timeout when trying to lock JAM messagebase \"%s\"", area->Path);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  if (JAM_ReadMBHeader(Base_PS, &BaseHeader_S)) {
    LogWrite(1, USERERR, "Failed to read header of messagebase \"%s\"", area->Path);
    JAM_UnlockMB(Base_PS);
    JAM_CloseMB(Base_PS);
    return (TRUE);
  }

  basenum = BaseHeader_S.BaseMsgNum;

  if (JAM_GetMBSize(Base_PS, &nummsgs)) {
    LogWrite(1, USERERR, "Failed to get size of JAM area \"%s\"", area->Path);
    return (TRUE);
  }

  if (nummsgs == 0)
    return (TRUE);              /* Nothing to do */

  /* Read msgid/reply */
  if (!(msgs = calloc(nummsgs, sizeof(struct jMsg)))) {
    LogWrite(1, SYSTEMERR, "Out of memory, cannot link %s", area->Tagname);
    return (TRUE);
  }

  for (c = 0; c < nummsgs; c++) {
    s_JamMsgHeader Header_S;
    s_JamSubPacket *SubPacket_PS;

    res = JAM_ReadMsgHeader(Base_PS, c, &Header_S, &SubPacket_PS);

    msgs[c].MsgIdCRC = -1;
    msgs[c].ReplyCRC = -1;

    if (!res) {
      msgs[c].MsgIdCRC = Header_S.MsgIdCRC;
      msgs[c].ReplyCRC = Header_S.ReplyCRC;
	  if (!relink) {
        msgs[c].ReplyTo = Header_S.ReplyTo;
        msgs[c].Reply1st = Header_S.Reply1st;
        msgs[c].ReplyNext = Header_S.ReplyNext;
	  }
      msgs[c].OldReplyTo = Header_S.ReplyTo;
      msgs[c].OldReply1st = Header_S.Reply1st;
      msgs[c].OldReplyNext = Header_S.ReplyNext;

      if (!jam_quicklink) {
        s_JamSubfield *Field_PS;

        for (Field_PS = JAM_GetSubfield(SubPacket_PS); Field_PS; Field_PS = JAM_GetSubfield(NULL))
          switch(Field_PS->LoID) {
          case JAMSFLD_MSGID:
            msgs[c].MsgIdData = calloc(Field_PS->DatLen + 1, 1);
            if (msgs[c].MsgIdData)
              mystrncpy(msgs[c].MsgIdData, Field_PS->Buffer, Field_PS->DatLen + 1);
            break;
          case JAMSFLD_REPLYID:
            msgs[c].ReplyData = calloc(Field_PS->DatLen + 1, 1);
            if (msgs[c].ReplyData)
              mystrncpy(msgs[c].ReplyData, Field_PS->Buffer, Field_PS->DatLen + 1);
            break;
          }

        JAM_DelSubPacket(SubPacket_PS);
      }
    }
  }

  for (c = 0; c < nummsgs; c++) {
    for (d = c+1; d < nummsgs; d++) {
      /* See if this is a reply to a message */
      if (msgs[c].ReplyCRC != -1 && jam_CompareMsgIdReply(Base_PS, msgs, d, c))
        jam_setreply(msgs, nummsgs, basenum, d, c);

      /* See if there are any replies to this message */
      if (msgs[c].MsgIdCRC != -1 && jam_CompareMsgIdReply(Base_PS, msgs, c, d))
        jam_setreply(msgs, nummsgs, basenum, c, d);
    }
  }

  /* Update links */
  for (c = 0; c < nummsgs; c++) {
    if (msgs[c].ReplyTo != msgs[c].OldReplyTo || msgs[c].Reply1st != msgs[c].OldReply1st || msgs[c].ReplyNext != msgs[c].OldReplyNext) {
      s_JamMsgHeader Header_S;

      LogWrite(6, DEBUG, " Updating message #%lu", basenum + c);

      res = JAM_ReadMsgHeader(Base_PS, c, &Header_S, NULL);

      if (!res) {
        Header_S.ReplyTo = msgs[c].ReplyTo;
        Header_S.Reply1st = msgs[c].Reply1st;
        Header_S.ReplyNext = msgs[c].ReplyNext;

        JAM_ChangeMsgHeader(Base_PS, c, &Header_S);
      }
    }

    if (msgs[c].MsgIdData)
      free(msgs[c].MsgIdData);

    if (msgs[c].ReplyData)
      free(msgs[c].ReplyData);
  }

  free(msgs);
  JAM_UnlockMB(Base_PS);
  JAM_CloseMB(Base_PS);

  return (TRUE);
}

#endif

/************************** end of messagebases *******************/

bool ReadConfig(uchar * file)
{
  FILE *fh;
  uchar cfgword[20];
  uchar tag[80], aka[80], path[200], mb[20], areafile[100];
  struct Area *tmparea, *LastArea;
  ulong jbcpos;
  uchar *cfgbuf;

  if (!(cfgbuf = malloc(4000))) {
    fprintf(stderr, "Out of memory\n");
    return (FALSE);
  }

  if (!(fh = fopen(file, "rt"))) {
    fprintf(stderr, "Failed to open %s for reading\n", file);
    fprintf(stderr, "Error: %s\n", strerror(errno));
    free(cfgbuf);
    return (FALSE);
  }

  areafile[0] = 0;

  while (fgets(cfgbuf, 4000, fh)) {
    jbcpos = 0;
    jbstrcpy(cfgword, cfgbuf, 20, &jbcpos);

    if (stricmp(cfgword, "AREAFILE") == 0)
      jbstrcpy(areafile, cfgbuf, 100, &jbcpos);

    if (stricmp(cfgword, "LOGFILE") == 0)
      jbstrcpy(LogFile, cfgbuf, 100, &jbcpos);

    if (stricmp(cfgword, "LOGLEVEL") == 0)
      if (jbstrcpy(tag, cfgbuf, 80, &jbcpos))
        LogLevel = atol(tag);

    if (stricmp(cfgword, "MSGBASEUMASK") == 0)
      if (jbstrcpy(tag, cfgbuf, 200, &jbcpos))
        sscanf(tag, "%o", &MsgbaseUmask);

#ifdef MSGBASE_JAM
    if (stricmp(cfgword, "JAM_QUICKLINK") == 0)
      jam_quicklink = TRUE;
#endif
  }
  fclose(fh);

  if (areafile[0] == 0) {
    fprintf(stderr, "AREAFILE not defined in %s\n", file);
    free(cfgbuf);
    return (FALSE);
  }

  if (!(fh = fopen(areafile, "rt"))) {
    fprintf(stderr, "Failed to open %s for reading\n", areafile);
    fprintf(stderr, "Error: %s\n", strerror(errno));
    free(cfgbuf);
    return (FALSE);
  }

  LastArea = NULL;

  while (fgets(cfgbuf, 4000, fh)) {
    jbcpos = 0;
    jbstrcpy(cfgword, cfgbuf, 20, &jbcpos);

    if (stricmp(cfgword, "AREA") == 0 || stricmp(cfgword, "NETMAIL") == 0 || stricmp(cfgword, "LOCALAREA") == 0) {
      jbstrcpy(tag, cfgbuf, 80, &jbcpos);
      jbstrcpy(aka, cfgbuf, 80, &jbcpos);

      if (stricmp(tag, "DEFAULT") != 0 && strnicmp(tag, "DEFAULT_", 8) != 0) {
        if (jbstrcpy(mb, cfgbuf, 20, &jbcpos)) {
          jbstrcpy(path, cfgbuf, 200, &jbcpos);

          if (!(tmparea = (struct Area *) calloc(1, sizeof(struct Area)))) {
            fprintf(stderr, "Out of memory\n");
            fclose(fh);
            return (FALSE);
          }

          jbAddNode(&AreaList, (struct jbNode *) tmparea);
          LastArea = tmparea;

          strcpy(tmparea->Tagname, tag);
          strcpy(tmparea->Messagebase, mb);
          strcpy(tmparea->Path, path);
        }
      }
    }

    if (stricmp(cfgword, "KEEPDAYS") == 0 && LastArea)
      if (jbstrcpy(tag, cfgbuf, 80, &jbcpos))
        LastArea->KeepDays = atol(tag);

    if (stricmp(cfgword, "KEEPNUM") == 0 && LastArea)
      if (jbstrcpy(tag, cfgbuf, 80, &jbcpos))
        LastArea->KeepNum = atol(tag);
  }
  fclose(fh);
  free(cfgbuf);

  return (TRUE);
}

int main(int argc, char **argv)
{
  struct Area *area;
  uchar *cfg = CONFIG_NAME, *afile = NULL, *pattern = NULL;
  int command, criteria = 0;
  int i;
  bool delafter = FALSE;
  uchar *only_type = NULL;
#ifdef MSGBASE_JAM
  time_t t1, t2;
  struct tm *tp;
#endif
  static struct option lopts[] = {
    {"help", 0, NULL, 'h'},
    {"config", required_argument, NULL, 'c'},
    {"file", required_argument, NULL, 'f'},
    {"delete-after", 0, NULL, 'd'},
    {"relink", 0, NULL, 'r'},
    {"msg", 0, NULL, 'M'},
#ifdef MSGBASE_JAM
    {"jam", 0, NULL, 'J'},
#endif
    {"version", 0, NULL, SHOWVER},
    {0, 0, 0, 0}
  };
#ifdef MSGBASE_JAM
  static uchar optstr[] = "+hc:f:drMJ";
#else
  static uchar optstr[] = "+hc:f:drM";
#endif
  static uchar helpstr[] =
"Usage: crashmaint [OPTION]... COMMAND [PATTERN]\n"
"Display CrashEcho statistics.\n"
"\n"
"  -h, --help            display this help and exit\n"
"  -c, --config=FILE     use this configuration file instead of the default\n"
"  -f, --file=FILE       read list of areas from FILE\n"
"  -d, --delete-after    delete list of areas after processing\n"
"  -r, --relink          do full relink of messagebase instead of updating\n"
"                        new replylinks\n"
"  -M, --msg             process only MSG areas\n"
#ifdef MSGBASE_JAM
"  -J, --jam             process only JAM areas\n"
#endif
"      --version         print version number and exit\n"
"\n"
"The following commands rcognized:\n"
"\n"
"  purge       [JM] delete messages according to age and/or number\n"
"  pack        [JM] pack messagebase and optionaly renumber messages\n"
"  link        [J-] link messagebase\n"
"\n"
"You can use only one of -f, -M"
#ifdef MSGBASE_JAM
", -J"
#endif
" or PATTERN."
"\n";
  int ch, lopt_index;

  signal(SIGINT, breakfunc);

  while((ch=getopt_long(argc, argv, optstr, lopts, &lopt_index)) != -1){
    switch(ch){
      case 'h':
      case '?':
      default:
        fprintf(stderr, helpstr);
        exit(EXIT_OK);
        /* Not reached */
      case SHOWVER:
        fprintf(stderr, "%s\n", verstr);
        exit(EXIT_OK);
        /* Not reached */
      case 'c':
        cfg = optarg;
        break;
      case 'f':
        afile = optarg;
        criteria++;
        break;
      case 'd':
        delafter = TRUE;
        break;
      case 'r':
        relink = TRUE;
        break;
      case 'M':
        only_type = "MSG"; /* FIXME this is UGLY */
        criteria++;
        break;
#ifdef MSGBASE_JAM
      case 'J':
        only_type = "JAM"; /* FIXME this is UGLY */
        criteria++;
        break;
#endif
    }
  }

  argc -= optind;
  argv += optind;

  if(argc < 1){
    fprintf(stderr, helpstr);
    exit(EXIT_ERROR);
  }

  jbNewList(&AreaList);
  jbNewList(&ProcessList);

  for (command = 0; command < CMD_MAX; command++)
    if (!stricmp(argv[0], Commands[command].Arg))
      break;

  if (command == CMD_MAX) {
    fprintf(stderr, helpstr);
    exit(EXIT_ERROR);
  }

  if (argc > 1) {
    if (!(CheckPattern(pattern=argv[1]))) {
      fprintf(stderr, "Invalid pattern \"%s\"\n", pattern);
      exit(EXIT_ERROR);
    }
    criteria++;
  }

  if (criteria > 1) {
    fprintf(stderr, helpstr);
    exit(EXIT_ERROR);
  }

  MsgbaseUmask = umask(0777);
  umask(MsgbaseUmask);

  if (!(ReadConfig(cfg))) {
    jbFreeList(&AreaList);
    exit(EXIT_ERROR);
  }

  if (!LogFile[0]) {
    fprintf(stderr, "No logfile defined.\n");
    exit(EXIT_ERROR);
  }

  if (!OpenLogfile(LogFile, LogLevel)) {
    exit(EXIT_ERROR);
  }

#ifdef MSGBASE_JAM
  t1 = time(NULL);
  tp = gmtime(&t1);
  tp->tm_isdst = -1;
  t2 = mktime(tp);
  jam_utcoffset = t2 - t1;
#endif

  if (!afile && delafter) {
    fprintf(stderr, helpstr);
    jbFreeList(&AreaList);
    exit(EXIT_ERROR);
  }

  LogWrite(2, SYSTEMINFO, "%s started successfully!", verstr);

  if (pattern) {
    for (area = (struct Area *) AreaList.First; area && !ctrlc; area = area->Next)
      if (MatchPattern(pattern, area->Tagname)) {
        struct Area *tmparea;

        if (!(tmparea = (struct Area *) calloc(1, sizeof(struct Area)))) {
          LogWrite(1, SYSTEMERR, "Out of memory");
          jbFreeList(&AreaList);
          jbFreeList(&ProcessList);
          return (FALSE);
        }

        memcpy(tmparea, area, sizeof(struct Area));
        jbAddNode(&ProcessList, (struct jbNode *) tmparea);
        jbFreeNode(&AreaList, (struct jbNode *) area);
      }
  } else if (only_type) {
    for (area = (struct Area *) AreaList.First; area && !ctrlc; area = area->Next)
      if (stricmp(only_type, area->Messagebase) == 0) {
        struct Area *tmparea;

        if (!(tmparea = (struct Area *) calloc(1, sizeof(struct Area)))) {
          LogWrite(1, SYSTEMERR, "Out of memory");
          jbFreeList(&AreaList);
          jbFreeList(&ProcessList);
          return (FALSE);
        }

        memcpy(tmparea, area, sizeof(struct Area));
        jbAddNode(&ProcessList, (struct jbNode *) tmparea);
        jbFreeNode(&AreaList, (struct jbNode *) area);
      }
  } else if (afile && Exists(afile)){
    FILE *fh;
    uchar *buf;
    struct Area *tmparea;

    if (!(buf = malloc(1024))) {
      LogWrite(1, SYSTEMERR, "Out of memory");
      jbFreeList(&AreaList);
      exit(EXIT_ERROR);
    }

    if ((fh=fopen(afile, "rt")) == NULL){
      LogWrite(1, SYSTEMERR, "Failed to open file %s for reading", afile);
      LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
      jbFreeList(&AreaList);
      exit(EXIT_ERROR);
    }

    while (fgets(buf, 1024, fh)) {
      if (buf[0] && buf[strlen(buf)-1] == '\n')
        buf[strlen(buf)-1] = '\0';

      for (area = (struct Area *) AreaList.First; area; area = area->Next)
        if (!stricmp(buf, area->Tagname)) {
          if (!(tmparea = (struct Area *) calloc(1, sizeof(struct Area)))) {
            LogWrite(1, SYSTEMERR, "Out of memory");
            jbFreeList(&AreaList);
            jbFreeList(&ProcessList);
            fclose(fh);
            free(buf);
            return (FALSE);
          }

          memcpy(tmparea, area, sizeof(struct Area));
          jbAddNode(&ProcessList, (struct jbNode *) tmparea);
          jbFreeNode(&AreaList, (struct jbNode *) area);
        }
    }
    fclose(fh);
    free(buf);
  }

  for (area = (struct Area *) (criteria==0 ? AreaList.First : ProcessList.First); area && !ctrlc; area = area->Next) {
    for(i=0;Messagebases[i].Name;i++)
      if(stricmp(Messagebases[i].Name,area->Messagebase)==0)
        break;

    if (Messagebases[i].processfunc[command]) {
      if (!Messagebases[i].processfunc[command](area)) {
        jbFreeList(&AreaList);
        jbFreeList(&ProcessList);
        exit(EXIT_ERROR);
      }
    } /* else {
      fprintf(stderr, "Area %s skipped: can\'t %s %s messagebase\n", area->Tagname, Commands[command].Str, area->Messagebase);
    } */
  }

  if (ctrlc)
    LogWrite(1, SYSTEMERR, "*** User Break ***");
  else if (delafter && Exists(afile))
    unlink(afile);

  jbFreeList(&AreaList);
  jbFreeList(&ProcessList);

  LogWrite(2, SYSTEMINFO, "CrashMaint end");
  CloseLogfile();

  exit(EXIT_OK);
}


syntax highlighted by Code2HTML, v. 0.9.1