/********************************************************
* 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