#include #include #include #define same(s1,s2) (strcmp(s1,s2)==0) char date[SS]; /* todays date */ int msgtotal; /* total messages found */ int msghighest; /* highest message found */ struct _msg msg; /* current open message */ int msgfile; /* its handle */ struct _fido fido; char logname[SS]; /* log file name */ char *getmem(); long sizmem(); int caller_purge; /* age to purge callers */ struct { char purge; /* purge this many days old */ char renum; /* renumber this area */ } areas[MAXAREA]; struct _area msgarea; /* current msg area */ struct _clr caller; /* current open caller */ int age; /* age to purge messages */ int log; /* log file handle */ main(argc,argv) int argc; char **argv; { unsigned brkflg,i; char fname[SS]; printf("MSGMGR (4 June 90) Message Manager for "); copr(); allmem(); /* free memory, */ if (! fastfile(8)) { /* start the file system */ cprintf("Can't start the file system?\r\n"); exit(1); } meminit(); strcpy(fname,"MSGMGR.INI"); /* default file name, */ if (argc > 1) strcpy(fname,argv[1]); /* local name */ i= open("fido.sys",0); /* load system stuff */ if (i == -1) { printf("The Fido system file FIDO.SYS is missing!\r\n"); exit(1); } read(i,&fido,sizeof(fido)); close(i); if (fido.fido_version != FIDOVER) { cprintf("Fido/FidoNet version doesn't match MsgMgr version!\r\n"); exit(1); } caller_purge= 0; /* dont purge callers by default */ age= 90; /* default age */ for (i= 0; i < MAXAREA; i++) { areas[i].purge= 0; /* dont purge messages */ areas[i].renum= 0; /* and renumber all areas, */ } strcpy(logname,"fido.log"); /* default log file name */ finit(fname); /* look for a startup file */ brkflg= _break(0,0); /* get break, */ _break(1,0); /* turn it off */ log= append(logname); /* add to the log file, */ if (log == -1) { stoupper(logname); cprintf("ERROR: cant open \"%s\"!\r\n",logname); _break(1,brkflg); exit(1); } gtod(date); /* get todays date for purging */ lprintf("-------------- MSGMGR started at %s, using file: %s \r\n",date,fname); /* If we have nothing to do, say so and go home. */ for (i= 0; i < MAXAREA; i++) { /* search for something */ if (areas[i].purge || areas[i].renum) break; } if ((i < MAXAREA) || caller_purge) { if (caller_purge) usrpurge(caller_purge); /* kill off some callers */ for (i= 0; i < fido.marea_max; i++) { if (! getarea(i,&msgarea)) { cprintf(" There is no message area #%d!\r\n",i + 1); lprintf("! There is no message area #%d\r\n",i + 1); break; } if (areas[i].purge || areas[i].renum) { dsparea(&msgarea); } if (areas[i].purge) msgpurge(i,areas[i].purge,0); /* purge first */ if (iskey() == 3) break; /* keyboard abort */ if (areas[i].renum) msgrenum(i); /* then renum */ if (iskey() == 3) break; /* keyboard abort */ } } else { cprintf(" MsgMgr has nothing to do\r\n"); lprintf("MSGMGR has nothing to do\r\n"); } _break(1,brkflg); /* restore break */ gtod(date); lprintf("-------------- MSGMGR done at %s\r\n",date); close(log); /* done with the log */ exit(0); } /* Message file renumberer. Speeds up the system by renumbering the messages so they are all sequential. */ msgrenum(area) int area; { int *xl,xlsize; /* reply translate table & its size */ int maxmsgs; /* total msgs we can translate */ char *cp; int i,n,f,lm; unsigned r,bad; char oldname[SS],newname[SS],buff[SS]; struct _clr caller; struct _xfbuf xfbuf; long p; xlsize= getbuf(400,10000,&xl); if (xlsize == 0) { /* reply translate table */ cprintf(" Not enough memory to RENUM?\r\n"); lprintf("! Cant get memory to RENUM\r\n"); return; } cprintf(" Renumbering Messages\r\n"); lprintf(" Renum area #%d\r\n",area); /* Make a table of 0's, which we then mark with 1's for message numbers that exist. We use this table initially to find the first non-sequential message number. */ maxmsgs= xlsize / sizeof(int); /* make it integers */ for (n= 0; n < maxmsgs; n++) xl[n]= 0; /* clear the table */ msgtotal= 0; /* total found, */ msghighest= 0; makemname(buff,"????????.msg"); /* real pathname */ xfbuf.s_attrib= 0; i= 0; while (_find(buff,i++,&xfbuf)) { n= atoi(xfbuf.name); /* find its number, */ if (n < maxmsgs) xl[n]= n; /* mark as found, */ ++msgtotal; /* remember the total, */ if (n > msghighest) msghighest= n; /* remember the highest */ } /* Now go look through the table for a hole. If no holes, nothing to do. */ for (n= 1; n < maxmsgs; n++) { /* search the table */ if (! xl[n]) break; /* for a missing msg */ } if (n > msghighest) { cprintf(" Messages are already sequential\r\n"); lprintf("Already sequential\r\n"); rlsbuf(xl,xlsize); /* free the buffer */ return; } /* Messages starting at (n) are non-sequential; we only have to start renumbering from here. (n) is the first free message number; start searching for an existing message and renumber it to that. */ lm= n; /* first new msg */ i= n + 1; /* next highest */ while (i < maxmsgs) { if (! xl[i]) { /* find one that exists */ ++i; continue; } sprintf(buff,"%d.msg",n); /* free msg number */ makemname(newname,buff); sprintf(buff,"%d.msg",i); /* old msg number */ makemname(oldname,buff); rename(oldname,newname); /* rename it */ /* Now use this slot in the table to remember the old messages new number so we can translate replies. */ xl[i++]= n++; /* new number */ } /* Now translate the reply numbers: xl[] is now a table of new message numbers. */ cprintf(" Translating Replies and See-Also's\r\n"); makemname(oldname,"????????.msg"); /* real pathname */ xfbuf.s_attrib= 0; i= 0; while (_find(oldname,i++,&xfbuf)) { n= atoi(xfbuf.name); /* find a message, */ if (n > maxmsgs) continue; /* out of range */ if (n < lm) continue; /* no need to xlate */ sprintf(buff,"%d.msg",n); /* open it, */ makemname(newname,buff); f= open(newname,2); if (f != -1) { /* read the header, */ read(f,&msg,sizeof(struct _msg)); if ((msg.reply != xl[msg.reply]) || (msg.up != xl[msg.up])) { /* translate replies */ msg.reply= xl[msg.reply]; msg.up= xl[msg.up]; lseek(f,0L,0); /* seek back, */ write(f,&msg,sizeof(struct _msg)); } close(f); } } cprintf(" Updating callers' last-read-message\r\n"); strcpy(buff,fido.syspath); strcat(buff,"caller.sys"); f= open(buff,2); if (f == -1) { cprintf("Cant find the caller file %s!\r\n",buff); lprintf("! Cant find %s\r\n",buff); } p= 0L; bad= r= 0; while (read(f,&caller,sizeof(struct _clr)) == sizeof(struct _clr)) { ++r; if (caller.version != CLRVER) { cprintf(" ! Bad record #%u in %s -- use SYSOP.EXE's \"#\" command\r\n",r,buff); cprintf(" to locate it, and \"C\" to repair it\r\n"); lprintf("! Bad record #%u in %s -- use SYSOP.EXE's \"#\" command\r\n",r,buff); lprintf("! to locate it, and \"C\" to repair it\r\n"); if (++bad >= 10) { cprintf(" ! Too many contiguous bad CALLER.SYS records -- last-message update aborted\r\n"); lprintf("! Too many contiguous bad CALLER.SYS records -- last-message update aborted\r\n"); break; } continue; } bad= 0; /* If the message area being renumbered is in the caller record, translate the "highest msg read" to the new message number; if that message was deleted it will translate to zero; find the closest message we can. */ for (i= 0; i < MAXLREAD; i++) { if (caller.lastmsg[i].area == area) { n= caller.lastmsg[i].msg; while (xl[n] == 0) { /* if msg gone, */ if (--n <= 0) break; /* find next lowest */ } if (n <= 0) break; /* nothing set */ caller.lastmsg[i].msg= xl[n]; /* new msg number */ lseek(f,p,0); write(f,&caller,sizeof(struct _clr)); break; } } p += (long) sizeof(struct _clr); } close(f); xlt_file("HW.DAT",xl,maxmsgs); /* do DCM high water marker */ xlt_file("LASTREAD",xl,maxmsgs); /* do SEADOG last-read marker */ rlsbuf(xl,xlsize); /* free the buffer */ } /* Attempt to translate DCMs HW.DAT file; it is a two byte file containing a message number; simply translate it. */ xlt_file(fn,xl,maxmsgs) int *xl; /* message number translate table */ int maxmsgs; /* highest input message number */ { char buff[SS]; int o,f; unsigned n; makemname(buff,fn); /* blindly try it */ f= open(buff,2); if (f == -1) return; /* doesnt exist */ if (read(f,&n,sizeof(n)) != sizeof(n)) n= 0;/* if bad reset it */ o= n; /* save for report */ if (n > maxmsgs) n= maxmsgs; /* bound it */ /* Translate the message number; if the table entry is zero, then that message doesn't exist; use the next-lowest message number, or zero. */ while (n && (xl[n] == 0)) --n; /* til table entry, or n == 0 */ if (n) if (xl[n]) n= xl[n]; /* if found, use it */ cprintf(" \"%s\" translated from #%d to #%d\r\n",fn,o,n); lprintf("%s from %d to %d\r\n",fn,o,n); lseek(f,0L,0); /* rewrite those two bytes */ write(f,&n,sizeof(n)); close(f); } /* Purge old callers. Works by copying to a temp file, except the deleted ones, then renaming. */ usrpurge(age) int age; { int i,u,t,o,n; unsigned r,bad; /* record number, bad in a row */ #define SIZE (sizeof(struct _clr)) cprintf(" Purging callers %d days old\r\n",age); lprintf(" Purge callers %d days old\r\n",age); u= open("caller.sys",0); if (u == -1) { lprintf("! Cant find CALLER.SYS\r\n"); return; } t= creat("caller$$.$$$",2); if (t == -1) { lprintf("! Cant create temp file CALLER$$.$$$\r\n"); close(u); return; } o= creat("caller.old",2); if (o == -1) { lprintf("! cant create old caller file CALLER.OLD\r\n"); close(u); close(t); return; } bad= r= n= 0; while (read(u,&caller,SIZE) == SIZE) { ++r; if (caller.version != CLRVER) { cprintf(" ! Bad record #%u in CALLER.SYS -- use SYSOP.EXE's \"#\" command\r\n",r); cprintf(" to locate it, and \"C\" to repair it\r\n"); lprintf("! Bad record #%u in CALLER.SYS -- use SYSOP.EXE's \"#\" command\r\n",r); lprintf("! to locate it, and \"C\" to repair it\r\n"); if (++bad >= 10) { cprintf(" ! Too many contiguous bad CALLER.SYS records -- PURGE aborted\r\n"); lprintf(" ! Too many contiguous bad CALLER.SYS records -- PURGE aborted\r\n"); n= -1; break; } } else if ((days(caller.date,date) < age) || /* if new, */ (caller.stuff & (1L << CLR_KEPS)) || /* marked KEEP, */ caller.credit || /* has credit, */ (uval(CLR_PRV,CLR_PRVS) == SYSOP)) { /* or is SYSOP */ bad= 0; if (write(t,&caller,SIZE) != SIZE) { /* keep 'em */ lprintf("! Purge callers: Disk full!\r\n"); n= -1; /* flag the error */ break; } } else { /* purge 'em */ bad= 0; if (write(o,&caller,SIZE) != SIZE) { /* write to old file */ lprintf("! Purge callers: Disk full!\r\n"); n= -1; break; } ++n; /* count another purged */ } } close(u); close(t); close(o); if (n < 0) return; /* if write error */ delete("caller.bak"); rename("caller.sys","caller.bak"); rename("caller$$.$$$","caller.sys"); lprintf(" Purged %d callers\r\n",n); } /* Return the value from the caller bit record. */ uval(mask,shift) int mask,shift; { int n; n= (caller.stuff >> shift) & mask; return(n); } /* Set a field in the caller bit record. */ setuval(val,mask,shift) int val,mask,shift; { long n; n= mask; n <<= shift; /* we NEED long arith */ caller.stuff &= ~n; /* remove old value */ n= val & mask; n <<= shift; /* set it THEN shift it */ caller.stuff |= n; /* add in new value */ } /* Purge messages so many days old. */ msgpurge(area,daysold,killrecvd) int area; /* which area */ int daysold; /* message age */ int killrecvd; /* kill if PRIVATE & RECV'D */ { int count,kill_it; int i; struct _xfbuf xfbuf; char *dp,*cp,name[SS],buff[SS]; cprintf(" Purging messages %d days old\r\n",daysold); lprintf(" purge msgs %d days old\r\n",daysold); makemname(name,"*.msg"); count= 0; xfbuf.s_attrib= 0; i= 0; while (_find(name,i++,&xfbuf)) { /* find a msg file */ makemname(buff,xfbuf.name); /* assemble its name */ msgfile= open(buff,0); /* read its header */ if (msgfile == -1) continue; read(msgfile,&msg,sizeof(struct _msg)); /* load the header, */ close(msgfile); kill_it= killrecvd && (msg.attr & MSGREAD) && (msg.attr & MSGPRIVATE); kill_it |= daysold && ((days(msg.date,date) > daysold)); if (kill_it) { delete(buff); ++count; } } cprintf(" Purged %d messages\r\n",count); lprintf(" Purged %d msgs\r\n",count); } /* Get message area #n; return 0 if not set, illegal number or other error. */ getarea(n,a) unsigned n; /* area number */ struct _area *a; /* struct to load */ { int off,f,i; if (n < 0) return(0); /* no such area */ if (n >= fido.marea_max) return(0); /* msg area max */ f= open("fido.sys",0); if (f == -1) return(0); /* no system file! */ off= sizeof(struct _fido); /* offset to beg msg areas */ off += n * sizeof(struct _area); /* offset plus area # */ lseek(f,(0L + off),0); /* seek there, */ i= read(f,a,sizeof(struct _area)); /* read some, */ close(f); /* immediately close it */ if (i != sizeof(struct _area)) return(0); /* check read error */ a-> number= n; /* set the path number */ return(*a-> path != NUL); /* invalid if no path set */ } /* Display the area contents. */ dsparea(area) struct _area *area; { cprintf("\r\n%d: ",area-> number + 1); cputs(area-> desc); cputs("\r\n"); } #define ERROR -1 #define IDLE -2 #define IGNORE -3 #define AGE 10 /* AGE 2 states, */ #define AGE2 11 /* keyword, number */ #define PURGE 20 /* PURGE areas ... */ #define PURGE2 21 /* keyword, numbers */ #define RENUM 30 /* RENUM areas ... */ #define RENUM2 31 /* areas ... */ #define LOG 40 /* LOG */ #define LOG2 41 /* filename */ static struct { char word[20]; int state; } rct[] = { "renum",RENUM, "purge",PURGE, "age",AGE, "logfile",LOG, "",ERROR }; finit(fn) char *fn; { int i,f,lineno,value; char line[200],atom[SS],*cp; FLAG callers,all,not; int state; cprintf(" Reading startup file %s\r\n",fn); f= open(fn,0); if (f == -1) { cprintf(" It's not there!\r\n"); return(0); } lineno= 0; /* for error reports */ callers= all= not= state= 0; /* reset all */ while (rline(f,line,sizeof(line))) { ++lineno; for (cp= line; *cp; ++cp) { /* delete comments */ if (*cp == ';') { /* by marking */ *cp= NUL; /* as the end of the line */ break; } } cp= skip_delim(line); while (num_args(cp)) { cpyatm(atom,cp); /* this atom, */ stolower(atom); /* make it pretty, */ cp= next_arg(cp); /* for next time through */ /* Search for the keyword in the table; if not found, leave as state ERROR. Digits are always OK, so no error if a digit. (Even if there is no state to accept them.) */ if (same(atom,"all")) all= 1; else if (same(atom,"none")) {not= 1; all= 1;} else if (same(atom,"not")) not= 1; else if (same(atom,"callers")) callers= 1; else if (isdigit(*atom)) value= atoi(atom); else switch (state) { case LOG2: break; /* ignore; arg is filename */ default: for (i= 0; *rct[i].word; i++) { if (same(atom,rct[i].word)) break; } if (*rct[i].word) { if (rct[i].state != IGNORE) state= rct[i].state; } else state= ERROR; break; } /* We switch states when we find a keyword, but only start executing states until the thing following a keyword. Ie. after SEND-TO we have to wait for a number. */ switch (state) { case IDLE: break; case IGNORE: break; case LOG: state= LOG2; break; case LOG2: cpyarg(logname,atom); state= IDLE; break; case ERROR: cprintf(" Unknown word '%s'\r\n",atom); break; case AGE: state= AGE2; break; case AGE2: if ((value > 1) && (value < 1000)) age= value; else { cprintf("AGE must be 2 to 999 days\r\n"); state= ERROR; } callers= all= not= 0; break; case PURGE: state= PURGE2; break; case PURGE2: if (callers) { if (! not) caller_purge= age; } else if (all) { for (i= 0; i <= fido.marea_max; i++) { areas[i].purge= (not ? 0 : age); } } else if (value <= fido.marea_max) { areas[value - 1].purge= (not ? 0 : age); } else cprintf("There is no area %d to PURGE!\r\n",value); callers= all= not= 0; break; case RENUM: state= RENUM2; break; case RENUM2: if (all) { for (i= 0; i <= fido.marea_max; i++) areas[i].renum= ! not; } else if (value <= fido.marea_max) { areas[value - 1].renum= ! not; } else cprintf("There is no area %d to RENUM!\r\n",value); callers= all= not= 0; break; } } } close(f); } /* Write out the open message. */ wrtmsg() { lseek(msgfile,0L,0); /* seek to start, */ write(msgfile,&msg,sizeof(struct _msg)); /* write it out, */ close(msgfile); } /* Assemble a full msg filename */ makemname(d,p) char *d,*p; { strcpy(d,msgarea.path); /* put in the path, */ strcat(d,p); /* add the filename */ } /* Formatted print to the log file. If the log fills up, mark the handle as -1, stop writing to it. */ lprintf(f) char *f; { char *cp,buf[500]; if (log == -1) return; _spr(buf,&f); if (write(log,buf,strlen(buf)) != strlen(buf)) { cprintf("LOG FILE: DISK FULL!!\r\n"); log= -1; } } /* Open a file for appending, creating it if necessary. Return the open handle, or -1 if error. */ append(s) char *s; { int h; h= open(s,2); /* open or create the */ if (h == -1) h= creat(s,2); /* file, if opened OK */ else lseek(h,0L,2); /* seek to the end */ return(h); /* handle or error */ } /* Dummy */ clprintf() {} /* Error printer */ error(s) char *s; { printf("!!! %s\r\n",s); }