/*
* hptlink - areas linker for Highly Portable Tosser (hpt)
* by Serguei Revtov 2:5021/11.10 || 2:5021/19.1
* Some code was taken from hpt/src/link.c by Kolya Nesterov
*
* This file is part of HPT.
*
* HPT 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, or (at your option) any
* later version.
*
* HPT is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HPT; see the file COPYING. If not, write to the Free
* Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*****************************************************************************
* $Id: hptlink.c,v 1.48.2.1 2004/01/23 04:29:07 andr_lukyanov Exp $
*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <smapi/compiler.h>
#ifdef HAS_IO_H
#include <io.h>
#endif
#ifdef HAS_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAS_SHARE_H
#include <share.h>
#endif
#include <smapi/prog.h>
#include <smapi/msgapi.h>
#include <fidoconf/common.h>
#include <fidoconf/xstr.h>
#include <version.h>
#include <global.h>
#include "cvsdate.h"
/* internal structure holding msg's related to link information */
/* used by linkArea */
struct msginfo {
char *replyId;
char *msgId;
char *subject;
UMSGID replyToPos;
UMSGID replies[MAX_REPLY];
UMSGID treeId;
int freeReply;
UMSGID msgPos;
};
typedef struct msginfo s_msginfo;
#define reply1st replies[0]
#define replyNxt replies[1]
struct origlinks {
UMSGID replyToPos;
UMSGID replies[MAX_REPLY];
};
typedef struct origlinks s_origlinks;
#define LOGFILENAME "hptlink.log"
s_log *hptlink_log = NULL;
s_fidoconfig *cfg;
char *version = NULL;
int singleRepl = 1;
int hardSearch = 0;
int useSubj = 1;
int useReplyId = 1;
int loglevel = 10;
int linkNew = 0;
HAREA harea;
int maxreply;
long links_msgid=0L;
long links_replid=0L;
long links_subj=0L;
long links_revmsgid=0L;
long links_total=0L;
long links_ignored=0L;
char *skipReSubj ( char *subjstr )
{
char *ptr;
if ( !subjstr ) return (NULL);
if ( *subjstr != 'R' && *subjstr != 'r' ) return (NULL);
subjstr++;
if ( *subjstr != 'e' && *subjstr != 'E' ) return (NULL);
subjstr++;
if ( *subjstr == '^' ) {
subjstr++;
while ( isdigit (*subjstr) ) subjstr++;
}
if ( *subjstr != ':' ) return (NULL);
subjstr++;
while ( *subjstr == ' ' ) subjstr++;
if ( (ptr = skipReSubj ( subjstr )) != NULL ) subjstr = ptr;
return (subjstr);
}
int cmpMsgIdReply (register char *str1, register char *str2)
{
while (*str1==*str2 && *str1) {
if (*str1=='@') while (*str1 && *str1!=' ') str1++; /* skip domain */
if (*str1) str1++;
if (*str2=='@') while (*str2 && *str2!=' ') str2++; /* skip domain */
if (*str2) str2++;
}
if (*str1=='\0' && *str2=='\0') return 0;
return 1;
}
void linkMsgs ( s_msginfo *crepl, s_msginfo *srepl, dword i, dword j, s_msginfo *replmap )
{
dword linkTo;
if (crepl -> msgId && srepl -> msgId &&
strcmp ( crepl -> msgId, srepl -> msgId) == 0) {
w_log( LL_WARN, "Warning: msg %ld is dupe to %ld", (long)i, (long)j);
links_ignored++;
return;
}
if (maxreply == MAX_REPLY) { /* Squish */
if (crepl -> freeReply >= maxreply)
{
w_log( LL_WARN, "replies count for msg %ld exceeds %d, rest of the replies won't be linked", (long)j, maxreply);
links_ignored++;
} else {
links_total++;
(crepl -> replies)[(crepl -> freeReply)++] = srepl->msgPos;
srepl -> replyToPos = crepl->msgPos;
}
} else { /* Jam, maybe something else? */
if(srepl -> replyToPos) {
w_log( LL_WARN, "Thread linking broken because of dupes");
links_ignored++;
return;
}
srepl -> replyToPos = crepl->msgPos;
links_total++;
if (crepl->reply1st == 0) {
crepl->reply1st = srepl->msgPos;
(crepl -> freeReply)++;
} else {
linkTo = MsgUidToMsgn(harea, crepl->reply1st, UID_EXACT) - 1;
if(linkTo == -1) {
w_log( LL_WARN, "Thread linking broken. MsgUidToMsgn() returned -1");
links_ignored++;
return;
}
while (replmap[linkTo].replyNxt) {
linkTo = MsgUidToMsgn(harea, replmap[linkTo].replyNxt, UID_EXACT) - 1;
if(linkTo == -1) {
w_log( LL_WARN, "Thread linking broken. MsgUidToMsgn() returned -1");
links_ignored++;
return;
}
}
replmap[linkTo].replyNxt = srepl->msgPos;
replmap[linkTo].freeReply++;
}
}
}
static char *GetCtrlValue (char *ctl, char *kludge)
{
char *value, *end, *out, *p;
if ( !ctl || !kludge ) return (NULL);
if ( (value = strstr( ctl, kludge)) == NULL ) return (NULL);
if ( value[-1] != '\001') return (NULL);
value += strlen(kludge); /* skip kludge name (i.e. MSGID: or REPLY:) */
for (end = value; *end != '\001' && *end; end++);
if ((end - value) <= 0) return (NULL);
out = (char *) smalloc((size_t) (end - value) + 1 );
if (out == NULL) return (NULL);
memcpy(out, value, (size_t) (end - value));
out[(size_t) (end - value)] = '\0';
/* fix for upper case msgids */
strLower(out);
/* remove .0 from node address */
if (NULL!=(p=strstr(out,".0 "))) memmove(p,p+2,strlen(p+2)+1);
return out;
}
void linkArea(s_area *area)
{
byte *ctl = NULL;
dword ctlen_curr=0;
dword ctlen;
dword highMsg;
dword i, j, linkTo;
dword newStart=0;
HMSG hmsg;
XMSG xmsg;
s_msginfo *replmap;
s_msginfo *crepl, *srepl;
s_origlinks *links;
s_origlinks *linksptr;
dword treeLinks=0;
int replFound;
int replDone;
char *ptr;
if ((area->msgbType & MSGTYPE_PASSTHROUGH) == MSGTYPE_PASSTHROUGH) {
w_log( LL_LINKING, "PASSTHROUGH area %s, skip", area->areaName);
return;
}
if (area->nolink) {
w_log( LL_LINKING, "area %s has nolink option, skip", area->areaName);
return;
}
w_log( LL_LINKING, "linking area %s...", area->areaName);
if (area->msgbType & MSGTYPE_JAM || area->msgbType & MSGTYPE_SDM) {
maxreply = 2;
} else {
maxreply = MAX_REPLY;
}
harea = MsgOpenArea((byte *) area->fileName, MSGAREA_NORMAL, (word)area->msgbType);
if (harea)
{
highMsg = MsgGetHighMsg(harea);
if ( highMsg < 2 ) {
w_log( LL_LINKING, "nothing to link (%ld messages)", (long)highMsg);
MsgCloseArea(harea);
return;
}
if ( (replmap = (s_msginfo *) scalloc (highMsg, sizeof(s_msginfo))) == NULL){
w_log( LL_CRIT,"Out of memory. Want %ld bytes", (long) sizeof(s_msginfo)*highMsg);
MsgCloseArea(harea);
closeLog();
disposeConfig(cfg);
exit(EX_SOFTWARE);
}
if ( (links = (s_origlinks *) scalloc (highMsg, sizeof(s_origlinks))) == NULL){
w_log( LL_CRIT, "Out of memory: can't get %ld bytes", (long) sizeof(s_origlinks)*highMsg);
MsgCloseArea(harea);
closeLog();
disposeConfig(cfg);
exit(EX_SOFTWARE);
}
/* Pass 1: read all message information in memory */
w_log( LL_LINKPASS, "Pass 1 - reading");
for (i = 1, crepl=replmap, linksptr=links; i <= highMsg; i++, crepl++, linksptr++) {
hmsg = MsgOpenMsg(harea, MOPEN_READ, i);
if (hmsg){
ctlen = MsgGetCtrlLen(hmsg);
if( ctlen == 0 )
{
w_log( LL_WARN, "msg %ld has no control information", (long) i);
MsgReadMsg(hmsg, &xmsg, 0, 0, NULL, 0, NULL);
} else {
if( ctl==NULL || ctlen_curr < ctlen + 1) {
ctl = (byte *) srealloc(ctl, ctlen + 1);
if (ctl == NULL) {
w_log( LL_CRIT,"out of memory while linking on msg %ld", (long) i);
MsgCloseArea(harea);
closeLog();
disposeConfig(cfg);
exit(EX_SOFTWARE);
}
ctlen_curr = ctlen + 1;
}
MsgReadMsg(hmsg, &xmsg, 0, 0, NULL, ctlen, ctl);
ctl[ctlen] = '\0';
if ( useReplyId ) {
crepl -> replyId = (char *) GetCtrlValue( (char *)ctl, "REPLY:");
crepl -> msgId = (char *) GetCtrlValue( (char *)ctl, "MSGID:");
}
}
if ( useSubj && xmsg.subj != NULL) {
if ( (ptr=skipReSubj((char*)xmsg.subj)) == NULL)
ptr = (char*) xmsg.subj;
crepl -> subject = sstrdup(ptr);
}
crepl->msgPos = MsgMsgnToUid(harea, i);
/* Save data for comparing */
if (area->msgbType & MSGTYPE_JAM || area->msgbType & MSGTYPE_SDM) {
linksptr->reply1st = xmsg.xmreply1st;
linksptr->replyNxt = xmsg.xmreplynext;
} else {
memcpy(linksptr->replies, xmsg.replies, sizeof(UMSGID) * MAX_REPLY);
}
linksptr->replyToPos = xmsg.replyto;
if (linkNew) {
if (area->msgbType & MSGTYPE_JAM || area->msgbType & MSGTYPE_SDM) {
if (xmsg.replyto || xmsg.xmreply1st || xmsg.xmreplynext) {
newStart = i+1;
crepl->replyToPos = xmsg.replyto;
crepl->reply1st = xmsg.xmreply1st;
crepl->replyNxt = xmsg.xmreplynext;
}
} else {
if (xmsg.replyto || xmsg.replies[0]) {
newStart = i+1;
memcpy(crepl->replies, xmsg.replies, sizeof(UMSGID) * MAX_REPLY);
crepl->replyToPos = xmsg.replyto;
for (j=0; xmsg.replies[j] && j<MAX_REPLY; j++);
crepl->freeReply = j;
}
}
}
MsgCloseMsg(hmsg);
}
}
/* Pass 2: building relations tree, & filling tree IDs */
if ( loglevel >= 11 ) {
if (linkNew)
w_log(LL_LINKPASS, "Pass 2: building relations for %ld messages, new from %ld", (long) i-1, (long) newStart);
else
w_log(LL_LINKPASS, "Pass 2: building relations for %ld messages", (long) i-1);
}
for (i = 1, crepl=replmap; i < highMsg; i++, crepl++) {
if (
crepl -> replyId ||
crepl -> msgId ||
crepl -> subject
) {
replDone = 0;
j=i+1;
srepl=crepl+1;
if (newStart > j) {
j=newStart;
srepl = &(replmap[j-1]);
}
for (; j <= highMsg && !replDone; j++, srepl++ ) {
replFound = 0;
if (!replFound &&
(crepl -> msgId) && (srepl -> replyId) &&
cmpMsgIdReply (crepl -> msgId, srepl -> replyId) == 0 ) {
replFound++;
links_msgid++;
if ( ! crepl -> treeId ) { /* *crepl isn't linked */
if (srepl -> treeId ) { /* *srepl linked already */
crepl -> treeId = srepl -> treeId;
} else {
crepl -> treeId = i; /* top of new tree */
}
}
srepl -> treeId = crepl -> treeId;
if (singleRepl) {
treeLinks++;
} else {
linkMsgs ( crepl, srepl, i, j, replmap );
}
}
if ( !replFound &&
(crepl -> treeId == 0 || srepl -> treeId == 0) &&
crepl -> replyId && srepl -> replyId) {
if ( cmpMsgIdReply (crepl -> replyId, srepl -> replyId) == 0 &&
strcmp(crepl -> replyId, " ffffffff")) {
replFound++;
links_replid++;
if ( ! crepl -> treeId ) { /* *crepl isn't linked */
if (srepl -> treeId ) { /* *srepl linked already */
crepl -> treeId = srepl -> treeId;
} else {
crepl -> treeId = i; /* top of new tree */
}
}
srepl -> treeId = crepl -> treeId;
treeLinks++;
}
}
if (!replFound && (srepl -> msgId) && (crepl -> replyId)) {
if ( cmpMsgIdReply (srepl -> msgId, crepl -> replyId) == 0 ) {
replFound++;
links_revmsgid++;
if ( ! crepl -> treeId ) { /* *crepl isn't linked */
if (srepl -> treeId ) { /* *srepl linked already */
crepl -> treeId = srepl -> treeId;
} else {
crepl -> treeId = i; /* top of new tree */
}
}
srepl -> treeId = crepl -> treeId;
if (singleRepl) {
treeLinks++;
} else {
linkMsgs ( srepl, crepl, j, i, replmap );
}
}
}
if ( !replFound &&
(srepl -> treeId == 0) &&
crepl -> subject && srepl -> subject ) {
if ( strcmp ( crepl -> subject, srepl -> subject ) == 0 ) {
replFound++;
links_subj++;
if ( ! crepl -> treeId ) { /* *crepl isn't linked */
if (srepl -> treeId ) { /* *srepl linked already */
crepl -> treeId = srepl -> treeId;
} else {
crepl -> treeId = i; /* top of new tree */
}
}
srepl -> treeId = crepl -> treeId;
treeLinks++;
}
}
if (replFound && singleRepl && !hardSearch ) replDone++;
}
}
}
/* Pass 3: finding unlinked messages with filled tree IDs, and link
* them to the tree where possible
*/
w_log(LL_LINKPASS, "Pass 3: buildng relations by treeIds");
for (i = 1, crepl=replmap; i <= highMsg && treeLinks; i++, crepl++) {
if ( crepl->replyToPos == 0 && crepl->freeReply == 0 &&
crepl->treeId && i != crepl->treeId ) {
/* Link unlinked message */
linkTo = (replmap[crepl -> treeId -1 ]).treeId;
if (linkTo > highMsg || linkTo <= 0 ) {
w_log(LL_CRIT,"Programming error 1 while linking linkTo=%ld", (long)linkTo);
closeLog();
disposeConfig(cfg);
exit(EX_SOFTWARE);
}
if (maxreply == MAX_REPLY) { /* Find place to put link for Squish */
while ( (replmap[linkTo-1]).freeReply >= maxreply) {
linkTo = MsgUidToMsgn(harea,(replmap[linkTo-1]).replies[0], UID_EXACT );
if (linkTo > highMsg || linkTo <= 0 ) {
w_log(LL_CRIT,"Programming error 2 while linking linkTo=%ld", (long)linkTo);
closeLog();
disposeConfig(cfg);
exit(EX_SOFTWARE);
}
}
}
linkMsgs ( &(replmap[linkTo-1]), crepl, linkTo, i , replmap );
(replmap[crepl -> treeId - 1]).treeId = i; /* where to link next message */
treeLinks--;
}
}
/* Pass 4: write information back to msgbase */
w_log(LL_LINKPASS, "Pass 4: writing");
for (i = 1, crepl=replmap, linksptr=links; i <= highMsg; i++, crepl++, linksptr++) {
if (area->msgbType & MSGTYPE_JAM || area->msgbType & MSGTYPE_SDM) {
if( (linksptr->replyToPos != crepl->replyToPos) ||
(linksptr->reply1st != crepl->reply1st) ||
(linksptr->replyNxt != crepl->replyNxt) ) {
hmsg = MsgOpenMsg(harea, MOPEN_RW, i);
if (hmsg) {
MsgReadMsg(hmsg, &xmsg, 0, 0, NULL, 0, NULL);
xmsg.replyto = crepl->replyToPos;
xmsg.xmreply1st = crepl->reply1st;
xmsg.xmreplynext = crepl->replyNxt;
MsgWriteMsg(hmsg, 0, &xmsg, NULL, 0, 0, 0, NULL);
MsgCloseMsg(hmsg);
}
}
} else { /* Not Jam */
if ((linksptr->replyToPos != crepl->replyToPos) ||
memcmp(linksptr->replies, crepl->replies, sizeof(UMSGID) * maxreply)) {
hmsg = MsgOpenMsg(harea, MOPEN_RW, i);
if (hmsg) {
MsgReadMsg(hmsg, &xmsg, 0, 0, NULL, 0, NULL);
memcpy(xmsg.replies, crepl->replies, sizeof(UMSGID) * maxreply);
xmsg.replyto = crepl->replyToPos;
MsgWriteMsg(hmsg, 0, &xmsg, NULL, 0, 0, 0, NULL);
MsgCloseMsg(hmsg);
}
}
}
if(crepl -> replyId) nfree(crepl -> replyId);
if(crepl -> subject) nfree(crepl -> subject);
if(crepl -> msgId ) nfree(crepl -> msgId );
}
MsgCloseArea(harea);
nfree(ctl);
nfree(replmap);
nfree(links);
w_log( LL_LINKING, "Linking area \"%s\" done", area->areaName);
} else {
w_log( LL_ERR, "Could not open area %s", area->areaName);
}
}
void usage(void) {
printf( "Usage: hptlink [options] [areaname ...]\n"
"Options: -t\t- build reply TREE\n"
"\t -s\t- do not use Subject\n"
"\t -a\t- search in all messages (for singlethread only)\n"
"\t -r\t- do not use REPLY:/MSGID:\n"
"\t -n\t- link with 'new' messages only ('new' from last linked + 1)\n"
);
}
int main(int argc, char **argv) {
int i, j;
struct _minf m;
char **argareas=NULL;
char *line=NULL;
int nareas=0;
int found;
FILE *f;
s_area *area;
setvar("module", "hpt");
xscatprintf(&line, "%u.%u.%u", VER_MAJOR, VER_MINOR, VER_PATCH);
setvar("version", line);
nfree(line);
SetAppModule(M_HPT);
versionStr = GenVersionStr( "hptlink", VER_MAJOR, VER_MINOR, VER_PATCH,
VER_BRANCH, cvs_date );
printf( "%s\n\n", versionStr);
for (i=1; i<argc; i++) {
if ( argv[i][0] == '-' ) {
switch (argv[i][1])
{
case 't': /* Tree mode */
case 'T':
singleRepl = 0;
break;
case 's': /* do NOT use Subject field */
case 'S':
useSubj = 0;
break;
case 'a': /* search in all messages */
case 'A':
hardSearch = 1;
break;
case 'r': /* do NOT use REPLY:/MSGID: fields */
case 'R':
useReplyId = 0;
break;
case 'l':
case 'L':
break; /* obsolete */
case 'n': /* link with 'new' messages only */
case 'N':
linkNew = 1;
break;
default:
usage();
exit(EX_USAGE);
}
} else {
/* AreaName(s) specified by args */
nareas++;
argareas = (char **)srealloc ( argareas, nareas*sizeof(char *));
argareas[nareas-1] = argv[i];
}
}
cfg = readConfig(NULL);
if (!cfg) {
fprintf(stderr, "Could not read fido config!\n");
return (1);
}
if (cfg->logFileDir) {
xstrscat(&line, cfg->logFileDir, LOGFILENAME, NULL);
hptlink_log = openLog(line, versionStr, cfg);
nfree(line);
}
w_log(LL_PRG, "%s", versionStr);
m.req_version = 0;
m.def_zone = (UINT16) cfg->addr[0].zone;
if (MsgOpenApi(&m)!= 0) {
w_log(LL_CRIT, "MsgOpenApi Error.");
closeLog();
disposeConfig(cfg);
exit(EX_SOFTWARE);
}
if ( argareas )
{
/* link only specified areas */
w_log(LL_LINKING, "Link areas specified by args");
for ( j=0; j<nareas; j++) {
found=0;
/* EchoAreas */
for (i=0, area=cfg->echoAreas;
i < cfg->echoAreaCount && !found;
i++, area++) {
if (stricmp(area->areaName, argareas[j])==0){
if (!area->scn) {
linkArea(area);
area->scn=1;
}
found++;
}
}
/* Local Areas */
for (i=0, area=cfg->localAreas;
i < cfg->localAreaCount && !found;
i++, area++) {
if (stricmp(area->areaName, argareas[j])==0){
if (!area->scn) {
linkArea(area);
area->scn=1;
}
found++;
}
}
/* NetMail areas */
for (i=0, area=cfg->netMailAreas;
i < cfg->netMailAreaCount && !found;
i++, area++) {
if (stricmp(area->areaName, argareas[j])==0){
if (!area->scn) {
linkArea(area);
area->scn=1;
}
found++;
}
}
if(!found) w_log(LL_WARN, "Couldn't find area \"%s\"", argareas[j]);
}
} else {
if (cfg->LinkWithImportlog != lwiNo){
f = fopen(cfg->importlog, "r");
} else {
f = NULL;
}
if ( f ) {
w_log(LL_INFO, "Using importlogfile -> linking only listed Areas");
while (!feof(f)) {
line = readLine(f);
if (line) {
found=0;
/* EchoAreas */
for (i=0, area=cfg->echoAreas;
i < cfg->echoAreaCount && !found;
i++, area++) {
if (stricmp(area->areaName, line)==0){
if (!area->scn) {
linkArea(area);
area->scn=1;
}
found++;
}
}
/* Local Areas */
for (i=0, area=cfg->localAreas;
i < cfg->localAreaCount && !found;
i++, area++) {
if (stricmp(area->areaName, line)==0){
if (!area->scn) {
linkArea(area);
area->scn=1;
}
found++;
}
}
/* NetMail areas */
for (i=0, area=cfg->netMailAreas;
i < cfg->netMailAreaCount && !found;
i++, area++) {
if (stricmp(area->areaName, line)==0){
if (!area->scn) {
linkArea(area);
area->scn=1;
}
found++;
}
}
if(!found) w_log(LL_ERR, "Couldn't find area \"%s\"", line);
nfree(line);
}
}
fclose(f);
if (cfg->LinkWithImportlog == lwiKill) remove(cfg->importlog);
} else {
/* importlog does not exist link all areas */
w_log(LL_INFO, "No ImportLog file, linking all Areas");
/* NetMails */
for (i = 0; i < cfg -> netMailAreaCount; i++)
linkArea (&(cfg->netMailAreas[i]));
/* EchoAreas */
for (i=0; i < cfg->echoAreaCount; i++) linkArea(&(cfg->echoAreas[i]));
/* Local Areas */
for (i=0; i < cfg->localAreaCount; i++) linkArea(&(cfg->localAreas[i]));
}
}
w_log(LL_STAT, "Linked by msgid/reply: %ld, replid: %ld, subj: %ld, revmsgid: %ld", (long)links_msgid, (long)links_replid, (long)links_subj, (long)links_revmsgid);
if (links_ignored)
w_log(LL_SUMMARY, "Linked total: %ld, Ignored: %ld", (long) links_total, (long) links_ignored);
else
w_log(LL_SUMMARY, "Linked total: %ld", (long) links_total);
w_log(LL_STOP, "Done");
MsgCloseApi();
closeLog();
disposeConfig(cfg);
return (0);
}
syntax highlighted by Code2HTML, v. 0.9.1