home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 35 Internet
/
35-Internet.zip
/
trn_12.zip
/
src
/
mthreads.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-12-04
|
40KB
|
1,557 lines
/* $Id: mthreads.c,v 2.3 1992/12/14 00:14:06 davison Trn $
*/
/* mthreads.c -- for making and updating a discussion-thread database
**
** We use the active file to read the high/low counts for each newsgroup
** and compare them with the corresponding values in a file called active2
** (which we created to keep the database high/lows in one place). If they
** don't match, we read/update/write the group's thread file and move on.
** If the active2 file is missing or corrupted, it will be repaired in the
** normal course of operation.
**
** Usage: mthreads [-d[MM]] [-e[HHMM]] [-s[hsec]] [-aDfknv] [hierarchy_list]
*/
/*** OS2: we need OS2.H for the double-fopen-patch, where
we use a DosCopy call to doublicate the active2-file ***/
#include <OS2.H>
#include "EXTERN.h"
#include "common.h"
#include "threads.h"
#ifdef SERVER
#include "server.h"
#endif
#include "INTERN.h"
#include "mthreads.h"
#if !defined(FTRUNCATE) && !defined(CHSIZE)
# ifdef F_FREESP
# define MYCHSIZE
# else
# define MVTRUNC
# endif
#endif
#ifdef USESYSLOG
#include <syslog.h>
#else
FILE *fp_log;
#endif
FILE *fp_tmp, *fp_active, *fp_active2, *fp_active2w = Nullfp;
bool eof_active = FALSE, eof_active2 = FALSE;
long first, last, first2, last2;
char ch, ch2;
struct stat filestat;
char buf[LBUFLEN+1];
char line[256];
static char line2[256];
char fmt_active2[] = "%s %010ld %07ld %c\n";
/*** OS2: now we need two filenames, one for the old active
file, which is only read, and one for the new one,
which will be written. So we define a new variable
which is called "filenameold". ***/
char *filename;
char filenameold[254+1];
typedef struct _active_line {
struct _active_line *link;
char *name;
long last;
long first;
char type;
} ACTIVE_LINE;
#define Nullact Null(ACTIVE_LINE*)
ACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
bool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
bool add_new = FALSE, rebuild = FALSE, zap_thread = FALSE;
bool acttimes_flag = FALSE, grevious_error;
bool initializing = TRUE;
int daemon_delay = 0, log_verbosity = 0, debug = 0, slow_down = 0;
long expire_time = 0;
char *hierarchy_list = NULL;
long truncate_len = -1;
char nullstr[1] = "";
extern int locked, cron_locking;
#ifdef TZSET
time_t tnow;
#else
struct timeb ftnow;
#endif
#define TIMER_FIRST 1
#define TIMER_DEFAULT (10 * 60)
int processed_groups = 0, added_groups = 0, removed_groups = 0;
int action;
#define NG_DEFAULT 0
#define NG_MATCH 1
#define NG_SKIP 2
#ifdef SERVER
char *server;
#else
time_t last_modified = 0;
#endif
SIGRET alarm_handler(), int_handler();
bool makethreads ANSI((void));
void log_startup ANSI((void));
void log_stats ANSI((void));
int
main(argc, argv)
int argc;
char *argv[];
{
int fd;
long pid;
/*** OS2: Now we load the settings out of the uupc-rc-files.
This settings contain the information, which was
previously hardcoded in the config.h-file by
the Configure-shell-script. ***/
if (!load_uupc_rc())
{ fprintf(stderr,"An error occurred while reading the uupc-rc-files!\n\
The following settings must be defined in the UUPCSYSRC-file:\n\
MailServ, NodeName, NewsDir, Domain, TempDir, MailDir, rmail\n");
fprintf(stderr,"These settings must be defined in the UUPCUSRRC-file:\n\
Mailbox, Signature, Name, Home, Organization, Editor\n");
fprintf(stderr,"And these settings must be defined in the UUPCTRNRC-file:\n\
Newsadmin, TrnLib, LocalDist, OrganizationDist, CityDist,\n\
StateDist, Countrydist, ContinentDist\n");
fprintf(stderr,"\nDon't forget, these settings are required, not optional!\n");
fflush(stderr);
exit(1);
}
while (--argc) {
if (**++argv == '-') {
while (*++*argv) {
switch (**argv) {
case 'a': /* automatically thread new groups */
add_new = TRUE;
break;
case 'c': /* continue trying to lock */
cron_locking = TRUE;
break;
case 'D': /* run in debug mode */
debug++;
break;
case 'd': /* run in daemon mode */
if (*++*argv <= '9' && **argv >= '0') {
daemon_delay = atoi(*argv) * 60;
while (*++*argv <= '9' && **argv >= '0') {
;
}
} else {
daemon_delay = TIMER_DEFAULT;
}
--*argv;
break;
case 'e': { /* enhanced expire processing */
struct tm *ts;
long desired;
(void) time(&expire_time);
ts = localtime(&expire_time);
if (*++*argv <= '9' && **argv >= '0') {
desired = atol(*argv);
if (desired/100 > 23 || desired%100 > 59) {
fprintf(stderr, "Illegal expire time: '%04d'\n",
desired);
exit(1);
}
desired = (desired/100)*60 + desired%100;
while (*++*argv <= '9' && **argv >= '0') {
;
}
} else {
desired = 30; /* 0030 = 12:30am */
}
--*argv;
desired -= ts->tm_hour * 60 + ts->tm_min;
if (desired < 0) {
desired += 24 * 60;
}
expire_time += desired * 60 - ts->tm_sec;
break;
}
case 'f': /* force each group to process */
force_flag = TRUE;
break;
case 'k': /* kill running mthreads */
kill_mthreads = TRUE;
break;
case 'n': /* don't process anything */
no_processing = TRUE;
break;
case 's': /* sleep between articles */
if (*++*argv <= '9' && **argv >= '0') {
slow_down = atoi(*argv);
while (*++*argv <= '9' && **argv >= '0') {
;
}
} else {
slow_down = 1L * 1000 * 1000;
}
--*argv;
break;
case 't': /* maintain active.times file */
#ifdef ACTIVE_TIMES
if (strEQ(ACTIVE_TIMES, "nntp")) {
fprintf(stderr, "Ignoring the -t option.\n");
} else {
acttimes_flag = TRUE;
}
#else
fprintf(stderr, "Ignoring the -t option.\n");
#endif
break;
case 'v': /* get more verbose in the log file */
log_verbosity++;
break;
case 'z': /* destroy .thread on severe signal */
zap_thread = TRUE;
break;
default:
fprintf(stderr, "Unknown option: '%c'\n", **argv);
exit(1);
}
}
} else {
if (hierarchy_list) {
fprintf(stderr, "Specify the newsgroups in one comma-separated list.\n");
exit(1);
}
hierarchy_list = *argv;
}
}
/* Initialize umask(), file_exp(), etc. */
mt_init();
/* If this is a kill request, look for the daemon lock file. */
if (kill_mthreads) {
if (!mt_lock(DAEMON_LOCK, SIGTERM)) {
fprintf(stderr, "No mthreads daemon is running.\n");
wrap_it_up(1);
}
fprintf(stderr, "Killed mthreads daemon.\n");
wrap_it_up(0);
}
/* See if an mthreads pass is already going. */
if (mt_lock(PASS_LOCK, 0) != 0 && !daemon_delay) {
fprintf(stderr, "mthreads is already running.\n");
wrap_it_up(1);
}
#ifdef USESYSLOG
# ifdef LOG_DAEMON
openlog("mthreads", LOG_PID, USESYSLOG);
# else
openlog("mthreads", LOG_PID);
# endif
#else
/* Open our log file */
filename = file_exp(MTLOG);
change_bsl2sl(filename);
if ((fp_log = fos2open(filename, "a")) == Nullfp) {
fprintf(stderr, "Unable to open `%s'.\n", filename);
wrap_it_up(1);
}
#endif
#ifdef SIGHUP
if (sigset(SIGHUP, SIG_IGN) != SIG_IGN) {
sigset(SIGHUP, int_handler);
}
#endif
if (sigset(SIGINT, SIG_IGN) != SIG_IGN) {
sigset(SIGINT, int_handler);
}
#ifdef SIGQUIT
if (sigset(SIGQUIT, SIG_IGN) != SIG_IGN) {
sigset(SIGQUIT, int_handler);
}
#endif
sigset(SIGTERM, int_handler);
#ifdef SIGBUS
sigset(SIGBUS, int_handler);
#endif
sigset(SIGSEGV, int_handler);
#ifdef SIGTTIN
sigset(SIGTTIN, SIG_IGN);
sigset(SIGTTOU, SIG_IGN);
#endif
sigset(SIGALRM, SIG_IGN);
#ifdef SERVER
sigset(SIGPIPE, int_handler);
#endif
#ifdef lint
alarm_handler(); /* foolishness for lint's sake */
int_handler(SIGINT);
#endif
/* Ensure this machine has the right byte-order for the database */
filename = file_exp(DBINIT);
change_bsl2sl(filename);
if ((fp_tmp = fos2open(filename, FOPEN_RB)) == Nullfp
|| fread((char*)&mt_bmap,1,sizeof (BMAP), fp_tmp) < sizeof (BMAP)-1) {
if (fp_tmp != Nullfp) {
fclose(fp_tmp);
}
write_db_init:
mybytemap(&mt_bmap);
if ((fp_tmp = fos2open(filename, FOPEN_WB)) == Nullfp) {
log_entry("Unable to create file: `%s'.\n", filename);
wrap_it_up(1);
}
mt_bmap.version = DB_VERSION;
fwrite((char*)&mt_bmap, 1, sizeof (BMAP), fp_tmp);
fclose(fp_tmp);
} else {
int i;
fclose(fp_tmp);
if (mt_bmap.version != DB_VERSION) {
if (mt_bmap.version == DB_VERSION-1) {
rebuild = TRUE;
log_entry("Upgrading database to version %d.\n", DB_VERSION);
goto write_db_init;
}
log_entry("** Database is not the right version (%d instead of %d) **\n",
mt_bmap.version, DB_VERSION);
wrap_it_up(1);
}
mybytemap(&my_bmap);
for (i = 0; i < sizeof (LONG); i++) {
if (my_bmap.l[i] != mt_bmap.l[i]
|| (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i])) {
log_entry("\
** Byte-order conflict -- re-run from a compatible machine **\n\
\t\tor remove the current thread files, including db.init **\n");
wrap_it_up(1);
}
}
}
#ifdef SERVER
if ((server = get_server_name(0)) == NULL) {
log_entry("Couldn't find name of news server.\n");
wrap_it_up(1);
}
#endif
initializing = FALSE;
/*** OS2: Ok, let's disable daemon mode, this is for
now the easiest way to avoid the fork's which
are needed in daemon-mode. ***/
/* If we're not in daemon mode, run through once and quit. */
daemon_delay = 0;
if (!daemon_delay) {
log_startup();
setbuf(stdout, Nullch);
extra_expire = (expire_time != 0);
makethreads();
} else {
cron_locking = FALSE;
if (mt_lock(DAEMON_LOCK, 0) != 0) {
fprintf(stderr, "An mthreads daemon is already running.\n");
wrap_it_up(1);
}
/* For daemon mode, we cut ourself off from anything tty-related and
** run in the background (involves forks, but no knives).
*/
close(0);
if (open("/dev/null", 2) != 0) {
fprintf(stderr, "unable to open /dev/null!\n");
wrap_it_up(1);
}
close(1);
close(2);
dup(0);
dup(0);
/*** OS2: And now let's patch out this fork's. ***/
/* while ((pid = fork()) < 0) { */
/* sleep(2); */
/* } */
/* if (pid) { */
/* exit(0); */
/* } */
/*#ifdef TIOCNOTTY */
/* if ((fd = open("/dev/tty", 1)) >= 0) { */
/* ioctl(fd, TIOCNOTTY, (int*)0); */
/* close(fd); */
/* } */
/*#else */
/* (void) setpgrp(); */
/* while ((pid = fork()) < 0) { */
/* sleep(2); */
/* } */
/* if (pid) { */
/* exit(0); */
/* } */
/*#endif */
/* Put our pid in the lock file for death detection */
if ((fp_tmp = fos2open(file_exp(MTDLOCK), "w")) != Nullfp) {
fprintf(fp_tmp, "%ld\n", (long)getpid());
fclose(fp_tmp);
}
log_startup();
sigset(SIGALRM, alarm_handler);
/* Start timer -- first interval is shorter than all others */
alarm(TIMER_FIRST);
for (;;) {
/*** OS2: This is the daemon mode, but it is disabled
in this version, so let's patch out the pause ***/
/* pause(); /* let alarm go off */
alarm(0);
#ifndef USESYSLOG
/* Re-open our log file, if needed */
if (!fp_log && !(fp_log = fos2open(file_exp(MTLOG), "a"))) {
wrap_it_up(1);
}
#endif
#ifndef SERVER
/*** OS2: because we get settings out of uupc-rc-files,
we don't use file_exp to expand the filename ***/
/* if (stat(file_exp(ACTIVE), &filestat) < 0) { */
if (stat(ACTIVE, &filestat) < 0) {
if (stat(ACTIVE_OPT, &filestat) < 0) {
log_entry("Unable to stat active file -- quitting.\n");
wrap_it_up(1);
}
}
#endif
if (expire_time && time(Null(time_t*)) > expire_time) {
expire_time += 24L * 60 * 60;
extra_expire = TRUE;
}
#ifdef SERVER
makethreads(); /* NNTP version always compares files */
#else
if (extra_expire || filestat.st_mtime != last_modified) {
last_modified = filestat.st_mtime;
if (!makethreads()) {
last_modified--;
}
}
#endif
alarm(daemon_delay);
#ifndef USESYSLOG
fclose(fp_log); /* close the log file while we sleep */
fp_log = Nullfp;
#endif
} /* for */
}/* if */
wrap_it_up(0);
return 0; /* NOTREACHED */
}
SIGRET
alarm_handler()
{
sigset(SIGALRM, alarm_handler);
}
SIGRET
int_handler(sig)
int sig;
{
static int visits = 0;
int ret = 0;
if (++visits > 4) {
wrap_it_up(1);
}
#ifndef USESYSLOG
/* Re-open our log file, if needed */
if (fp_log || (fp_log = fos2open(file_exp(MTLOG), "a")))
#endif
{
switch (sig) {
case SIGTERM:
#ifdef SIGHUP
case SIGHUP:
#endif
#ifdef SIGQUIT
case SIGQUIT:
#endif
log_entry("halt requested.\n");
zap_thread = 0;
break;
#ifdef SERVER
case SIGPIPE:
log_entry("broken pipe -- trying to continue.\n");
sigset(SIGPIPE, int_handler);
return;
#endif
#ifdef SIGBUS
case SIGBUS:
#endif
case SIGSEGV:
log_error("** Severe signal: %d **\n", sig);
/* Destroy offending thread file if requested to do so. */
if (zap_thread) {
unlink(thread_name(line));
log_entry("Destroyed thread file for %s\n", line);
}
break;
default:
log_entry("interrupt %d received.\n", sig);
zap_thread = 0;
ret = 1;
break;
}
}
if (!daemon_delay) {
printf("Interrupt %d!\n", sig);
if (zap_thread) {
printf("Destroyed thread file for %s\n", line);
}
}
/* If we're in the middle of writing the new active2 file, finish it. */
if (fp_active2w) {
if (*line2) {
if (index(line2, ' ')) {
fputs(line2, fp_active2w);
} else {
fprintf(fp_active2w, fmt_active2, line2, last2, first2, ch2);
}
}
for (pline = line_root; pline; pline = pline->link) {
fprintf(fp_active2w, fmt_active2,
pline->name, pline->last, pline->first, pline->type);
}
if (!eof_active2) {
while (fgets(line2, sizeof line2, fp_active2)) {
fputs(line2, fp_active2w);
}
}
log_stats();
}
wrap_it_up(ret);
}
void
wrap_it_up(ret)
int ret;
{
mt_unlock(locked);
(void) chdir(MTLIB); /* for *mon.out files, etc. */
exit(ret);
}
/* Process the active file, creating/modifying the active2 file and
** creating/modifying the thread data files.
*/
bool
makethreads()
{
register char *cp, *cp2;
char data_file_open;
bool update_successful, old_groups, touch_thread;
#ifdef SERVER
int server_failure = 0;
#endif
/* See if an mthreads pass is already going. */
if (!(locked & PASS_LOCK) && mt_lock(PASS_LOCK, 0) != 0) {
log_entry("unable to get a lock for this pass.\n");
return FALSE;
}
#ifdef SERVER
if (!open_server()) {
return FALSE;
}
put_server("LIST"); /* ask server for the active file */
get_server(line, sizeof line);
if (*line != CHAR_OK) {
log_entry("Unable to get active file from server.\n");
close_server();
return FALSE;
}
if ((fp_active = fos2open(file_exp(ACTIVE1), "w+")) == Nullfp) {
log_entry("Unable to write the active1 file -- quitting.\n");
wrap_it_up(1);
}
while (1) {
if (get_server(line, sizeof line) < 0) {
log_entry("Server failed to send entire active file.\n");
fclose(fp_active);
close_server();
return FALSE;
}
if (*line == '.') {
break;
}
fputs(line, fp_active);
putc('\n', fp_active);
}
if (ferror(fp_active)) {
log_entry("Error writing to active1 file.\n");
fclose(fp_active);
close_server();
return FALSE;
}
fseek(fp_active, 0L, 0); /* rewind for read */
#else /* not SERVER */
/*** OS2: because we get settings out of the uupc-rc-files,
we don't use the file_exp-function here ***/
/* if ((fp_active = fos2open(file_exp(ACTIVE), "r")) == Nullfp) { */
if ((fp_active = fos2open(ACTIVE, "r")) == Nullfp) {
if ((fp_active = fos2open(ACTIVE_OPT, "r")) == Nullfp) {
log_entry("Unable to open the active file.\n");
wrap_it_up(1);
}
}
#endif
/*** OS2: emx gcc cannot open a file twice, although that
would be possible under OS/2. So we try to copy the
file first, and then open the copied(old)-one only
for reading, and the other one for writing.
We need to allocate the string filenameold, because
file_exp returns only a pointer to a string which
is statically defined in file_exp (side offect). ***/
/*OS2-patch:*/
strcpy(filenameold,file_exp(ACTIVE2OLD));
filename = file_exp(ACTIVE2);
if (DosCopy(filename,filenameold,DCPY_EXISTING)) {
/*** DosCopy not successful ? ***/
if ((fp_active2w = fos2open(filename, "r")) != Nullfp) {
/*** DosCopy not successful, although active2 exists?***/
log_entry("Cannot copy active2 file (OS/2 port).\n");
wrap_it_up(1);
}
else {
if ((fp_active2 = fos2open(filenameold, "w")) == Nullfp) {
log_entry("Unable to create the active2.old file (OS/2 port).\n");
wrap_it_up(1);
}
else {
fclose(fp_active2);
}
}
}
/*OS2-patch-end*/
if ((fp_active2w = fos2open(filename, "r+")) == Nullfp) {
if ((fp_active2w = fos2open(filename, "w")) == Nullfp) {
log_entry("Unable to open the active2 file for update.\n");
wrap_it_up(1);
}
/* Add existing groups to active.times file with ancient date. */
old_groups = TRUE;
} else {
old_groups = FALSE;
}
/*OS2-patch:*/
/* if ((fp_active2 = fos2open(filename, "r")) == Nullfp) { */
if ((fp_active2 = fos2open(filenameold, "r")) == Nullfp) {
/*OS2-patch-end*/
log_entry(filename);
log_entry(filenameold);
log_entry("Unable to open the active2 file.\n");
wrap_it_up(1);
}
if (extra_expire && log_verbosity) {
log_entry("Using enhanced expiration for this pass.\n");
}
/* What time is it? */
#ifdef TZSET
(void) time(&tnow);
(void) tzset();
#else
(void) ftime(&ftnow);
#endif
eof_active = eof_active2 = FALSE;
fp_tmp = Nullfp;
/* Loop through entire active file. */
for (;;) {
if (eof_active || !fgets(line, sizeof line, fp_active)) {
if (eof_active2 && !line_root) {
break;
}
eof_active = TRUE;
ch = 'x';
} else {
cp = line + strlen(line) - 1;
if (*cp == '\n') {
*cp = '\0';
}
if (!(cp = index(line, ' '))) {
log_entry("** line in 'active' has no space: %s **\n", line);
continue;
}
*cp = '\0';
if (sscanf(cp+1, "%ld %ld %c", &last, &first, &ch) != 3) {
log_entry("** digits corrupted in 'active': %s %s **\n",
line, cp+1);
continue;
}
if (last < first - 1) {
log_entry("** bogus group values in 'active': %s %s **\n",
line, cp+1);
continue;
}
}
if (debug > 1 || log_verbosity > 3) {
log_entry("Processing %s:\n", line);
}
data_file_open = 0;
/* If we've allocated some lines in memory while searching for
** newsgroups (they've scrambled the active file on us), check
** them first.
*/
last_line = Nullact;
for (pline = line_root; pline; pline = pline->link) {
if (eof_active || strEQ(line, pline->name)) {
strcpy(line2, pline->name);
free(pline->name);
first2 = pline->first;
last2 = pline->last;
ch2 = pline->type;
if (last_line) {
last_line->link = pline->link;
} else {
line_root = pline->link;
}
free(pline);
break;
}
last_line = pline;
}/* for */
touch_thread = FALSE;
/* If not found yet, check the active2 file. */
if (!pline) {
for (;;) {
if (eof_active2 || !fgets(line2, sizeof line2, fp_active2)) {
/* At end of file, check if the thread data file exists.
** If so, use its high/low values. Else, default to
** some initial values.
*/
eof_active2 = TRUE;
if (eof_active) {
break;
}
strcpy(line2, line);
if ((data_file_open = init_data(thread_name(line)))) {
last2 = total.last;
first2 = total.first;
ch2 = 'y';
} else {
total.first = first2 = first;
if (add_new && (!hierarchy_list
|| ngmatch(hierarchy_list, line) == NG_MATCH)) {
total.last = last2 = first - 1;
ch2 = (ch == '=' ? 'x' : ch);
touch_thread = TRUE;
added_groups += (ch2 != 'x');
} else {
total.last = last2 = last;
ch2 = (ch == '=' ? 'X' : toupper(ch));
}
}
data_file_open++; /* (1 == empty, 2 == open) */
#ifdef ACTIVE_TIMES
/* Found a new group -- see if we need to log it. */
if (acttimes_flag) {
if (!fp_tmp && !(fp_tmp = fos2open(ACTIVE_TIMES, "a"))) {
log_entry("unable to append to %s.\n",ACTIVE_TIMES);
acttimes_flag = FALSE;
} else {
fprintf(fp_tmp, "%s %ld mthreads@%s\n", line,
old_groups ? 30010440L : time(Null(time_t)),
OURDOMAIN);
}
}
#endif
break;
}
if (!(cp2 = index(line2, ' '))) {
log_entry("active2 line has no space: %s\n", line2);
continue;
}
*cp2 = '\0';
if (sscanf(cp2+1,"%ld %ld %c",&last2,&first2,&ch2) != 3) {
log_entry("active2 digits corrupted: %s %s\n",
line2, cp2+1);
continue;
}
/* Check if we're still in-sync */
if (eof_active || strEQ(line, line2)) {
break;
}
/* Nope, we've got to go looking for this line somewhere
** down in the file. Save each non-matching line in memory
** as we go.
*/
pline = (ACTIVE_LINE*)safemalloc(sizeof (ACTIVE_LINE));
pline->name = savestr(line2);
pline->last = last2;
pline->first = first2;
pline->type = ch2;
pline->link = Nullact;
if (!last_line) {
line_root = pline;
} else {
last_line->link = pline;
}
*line2 = '\0';
last_line = pline;
}/* for */
if (eof_active && eof_active2) {
break;
}
}/* if !pline */
if (eof_active) {
strcpy(line, line2);
if (truncate_len < 0) {
truncate_len = ftell(fp_active2w);
}
}
if (rebuild) {
unlink(thread_name(line));
}
update_successful = FALSE;
if (hierarchy_list && !add_new) {
switch ((action = ngmatch(hierarchy_list, line))) {
case NG_MATCH: /* if unthreaded, add it */
if (ch2 < 'a' && ch != 'x' && ch != '=') {
total.last = last2 = first2 - 1;
touch_thread = TRUE;
added_groups++;
}
break;
case NG_SKIP: /* if threaded, remove it */
if (ch2 >= 'a') {
unlink(thread_name(line));
removed_groups++;
}
break;
}
} else {
action = (ch2 < 'a' ? NG_SKIP : NG_MATCH);
}
if (action == NG_DEFAULT || (debug && action == NG_SKIP)) {
dont_read_data(data_file_open); /* skip silently */
if (touch_thread) {
(void) write_data(thread_name(line));
}
} else if (ch == 'x' || ch == '=') {
if (!daemon_delay) { /* skip 'x'ed groups */
putchar('x');
}
ch = (action == NG_SKIP ? 'X' : 'x');
if ((ch2 >= 'a' && ch2 != 'x') || force_flag) {
/* Remove thread file if group is newly 'x'ed out */
unlink(thread_name(line));
}
update_successful = TRUE;
dont_read_data(data_file_open);
} else if (action == NG_SKIP) { /* skip excluded groups */
if (!daemon_delay) {
putchar('X');
}
ch = toupper(ch);
if (force_flag) {
unlink(thread_name(line));
}
update_successful = TRUE;
dont_read_data(data_file_open);
} else if (no_processing) {
if (!daemon_delay) {
putchar(',');
}
ch2 = ch;
dont_read_data(data_file_open);
if (touch_thread) {
(void) write_data(thread_name(line));
}
} else if (!force_flag && !extra_expire && !rebuild
&& first == first2 && last == last2) {
/* We're up-to-date here. Skip it. */
if (!daemon_delay) {
putchar('.');
}
update_successful = TRUE;
dont_read_data(data_file_open);
if (touch_thread) {
(void) write_data(thread_name(line));
}
} else {
/* Looks like we need to process something. */
#ifdef SERVER
if (server_failure == 1) {
if (!open_server()) {
log_entry("failed to re-open server for this pass.\n");
server_failure = 2;
} else {
server_failure = 0;
}
}
if (!server_failure) {
sprintf(buf, "GROUP %s", line);
put_server(buf); /* go to next group */
if (get_server(buf, sizeof buf) < 0
|| *buf != CHAR_OK) {
log_error("NNTP failure -- %s.\n", buf);
if (strnNE(buf, "400", 3)) {
close_server();
}
server_failure = 1;
}
}
if (server_failure) {
#else
strcpy(cp = buf, line2);
while ((cp = index(cp, '.'))) {
*cp = '/';
}
filename = file_exp(buf); /* relative to spool dir */
change_bsl2sl(filename);
change_bsl2sl(buf);
if (chdir(filename) < 0) {
if (errno != ENOENT) {
log_entry("Unable to chdir to `%s'.\n", filename);
}
#endif
if (!daemon_delay) {
putchar('*');
}
dont_read_data(data_file_open);
} else {
filename = thread_name(line);
/* Try to open the data file only if we didn't try it
** in the name matching code above.
*/
if (!data_file_open--) { /* (0 == haven't tried yet) */
if (!(data_file_open = init_data(filename))) {
total.last = first - 1;
total.first = first;
}
}
if (data_file_open) { /* (0 == empty, 1 == open) */
if (!read_data()) { /* did read fail? */
if (debug) {
strcpy(buf, filename);
cp = rindex(buf, '/') + 1;
strcpy(cp, "bad.read");
/*** OS2: rename fails if destination already exists, so delete it before ***/
unlink(buf);
rename(filename, buf);
}
data_file_open = init_data(Nullch);
total.last = first - 1;
total.first = first;
}
}
grevious_error = FALSE;
process_articles(first, last);
processed_groups++;
if (!added_count && !expired_count && !touch_thread
&& last == last2) {
(void) write_data(Nullch);
if (!daemon_delay) {
putchar(':');
}
update_successful = TRUE;
} else {
strcpy(buf, filename);
cp = rindex(buf, '/') + 1;
strcpy(cp, NEW_THREAD); /* write data as .new */
if (write_data(buf) && !grevious_error) {
/*** OS2: rename fails if destination already exists, so delete it before ***/
unlink(filename);
rename(buf, filename);
added_articles += added_count;
expired_articles += expired_count;
if (!daemon_delay) {
if (!total.root) {
putchar('-');
} else {
putchar('#');
}
}
update_successful = TRUE;
} else {
if (debug) {
cp = rindex(filename, '/') + 1;
strcpy(cp, "bad.write");
/*** OS2: rename fails if destination already exists, so delete it before ***/
unlink(filename);
rename(buf, filename);
} else {
unlink(buf); /* blow away bad write */
if (grevious_error) {
unlink(filename); /* blow away the .thread, */
(void) init_data(Nullch); /* set totals */
total.last = first-1;
total.first = first;
(void) write_data(filename); /* write it null */
}
}
if (!daemon_delay) {
putchar('!');
}
}/* if */
}/* if */
}/* if */
}/* if */
/* Finally, update the active2 entry for this newsgroup. */
if (!eof_active) {
if (update_successful) {
fprintf(fp_active2w, fmt_active2, line, last, first, ch);
} else {
fprintf(fp_active2w, fmt_active2, line, last2, first2, ch2);
}
}
*line2 = '\0';
/* If we're not out of sync, keep active2 file flushed. */
if (!line_root) {
fflush(fp_active2w);
}
#ifdef CHECKLOAD
checkload();
#endif
}/* for */
#ifdef SERVER
if (!server_failure) {
close_server();
}
#endif
fclose(fp_active);
fclose(fp_active2);
if (truncate_len >= 0) {
#ifdef FTRUNCATE
if (ftruncate(fileno(fp_active2w), truncate_len) == -1)
#else
#ifdef MVTRUNC
if (mvtrunc(file_exp(ACTIVE2), truncate_len) == -1)
#else
if (chsize(fileno(fp_active2w), truncate_len) == -1)
#endif
#endif
{
log_entry("Unable to truncate the active2 file.\n");
}
truncate_len = -1;
}
fclose(fp_active2w);
fp_active2w = Nullfp;
if (fp_tmp) {
fclose(fp_tmp);
}
log_stats();
processed_groups = added_groups = removed_groups = 0;
added_articles = expired_articles = 0;
extra_expire = FALSE;
rebuild = FALSE;
mt_unlock(PASS_LOCK); /* remove single-pass lock */
return TRUE;
}
#ifdef SERVER
int
open_server()
{
switch (server_init(server)) {
case OK_NOPOST:
case OK_CANPOST:
return 1;
case ERR_ACCESS:
log_entry("Server %s rejected connection -- quitting.\n", server);
wrap_it_up(1);
default:
log_entry("Couldn't connect with server %s.\n", server);
return 0;
}
}
#endif
/*
** ngmatch - newsgroup name matching
**
** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
** and NG_DEFAULT if the group doesn't match at all.
**
** "all" in a pattern is a wildcard that matches exactly one word;
** it does not cross "." (NGDELIM) delimiters.
**
** This matching code was borrowed from C news.
*/
#define ALL "all" /* word wildcard */
#define NGNEG '!'
#define NGSEP ','
#define NGDELIM '.'
int
ngmatch(ngpat, grp)
char *ngpat, *grp;
{
register char *patp; /* point at current pattern */
register char *patcomma;
register int depth;
register int faildeepest = 0, hitdeepest = 0; /* in case no match */
register bool negation;
for (patp = ngpat; patp != Nullch; patp = patcomma) {
negation = FALSE;
patcomma = index(patp, NGSEP);
if (patcomma != Nullch) {
*patcomma = '\0'; /* will be restored below */
}
if (*patp == NGNEG) {
++patp;
negation = TRUE;
}
depth = onepatmatch(patp, grp); /* try 1 pattern, 1 group */
if (patcomma != Nullch) {
*patcomma++ = NGSEP; /* point after the comma */
}
if (depth == 0) { /* mis-match */
; /* ignore it */
} else if (negation) {
/* record depth of deepest negated matched word */
if (depth > faildeepest) {
faildeepest = depth;
}
} else {
/* record depth of deepest plain matched word */
if (depth > hitdeepest) {
hitdeepest = depth;
}
}
}
if (hitdeepest > faildeepest) {
return NG_MATCH;
} else if (faildeepest) {
return NG_SKIP;
} else {
return NG_DEFAULT;
}
}
/*
** Match a pattern against a group by looking at each word of pattern in turn.
**
** On a match, return the depth (roughly, ordinal number * k) of the rightmost
** word that matches. If group runs out first, the match fails; if pattern
** runs out first, it succeeds. On a failure, return zero.
*/
int
onepatmatch(patp, grp)
char *patp, *grp;
{
register char *rpatwd; /* used by word match (inner loop) */
register char *patdot, *grdot; /* point at dots after words */
register char *patwd, *grwd; /* point at current words */
register int depth = 0;
for (patwd = patp, grwd = grp;
patwd != Nullch && grwd != Nullch;
patwd = patdot, grwd = grdot
) {
register bool match = FALSE;
register int incr = 20;
/* null-terminate words */
patdot = index(patwd, NGDELIM);
if (patdot != Nullch) {
*patdot = '\0'; /* will be restored below */
}
grdot = index(grwd, NGDELIM);
if (grdot != Nullch) {
*grdot = '\0'; /* will be restored below */
}
/*
* Match one word of pattern with one word of group.
* A pattern word of "all" matches any group word,
* but isn't worth as much.
*/
#ifdef FAST_STRCMP
match = STREQ(patwd, grwd);
if (!match && STREQ(patwd, ALL)) {
match = TRUE;
--incr;
}
#else
for (rpatwd = patwd; *rpatwd == *grwd++;) {
if (*rpatwd++ == '\0') {
match = TRUE; /* literal match */
break;
}
}
if (!match) {
/* ugly special case match for "all" */
rpatwd = patwd;
if (*rpatwd++ == 'a' && *rpatwd++ == 'l'
&& *rpatwd++ == 'l' && *rpatwd == '\0') {
match = TRUE;
--incr;
}
}
#endif /* FAST_STRCMP */
if (patdot != Nullch) {
*patdot++ = NGDELIM; /* point after the dot */
}
if (grdot != Nullch) {
*grdot++ = NGDELIM;
}
if (!match) {
depth = 0; /* words differed - mismatch */
break;
}
depth += incr;
}
/* if group name ran out before pattern, then match fails */
if (grwd == Nullch && patwd != Nullch) {
depth = 0;
}
return depth;
}
/* Put our startup options into the log file.
*/
void
log_startup()
{
char tmpbuf[256];
strcpy(tmpbuf, "Started mthreads");
if (cron_locking) {
strcat(tmpbuf, " -c");
}
if (debug) {
strcat(tmpbuf, " -D");
}
if (force_flag) {
strcat(tmpbuf, " -f");
}
if (daemon_delay) {
sprintf(tmpbuf + strlen(tmpbuf), " -d%d", daemon_delay / 60);
if (expire_time) {
struct tm *ts;
ts = localtime(&expire_time);
sprintf(tmpbuf + strlen(tmpbuf), " -e%02d%02d",ts->tm_hour,ts->tm_min);
}
} else if (expire_time) {
strcat(tmpbuf, " -e");
}
if (slow_down) {
sprintf(tmpbuf + strlen(tmpbuf), " -s%d", slow_down);
}
if (no_processing) {
strcat(tmpbuf, " -n");
}
if (log_verbosity) {
sprintf(tmpbuf + strlen(tmpbuf), " -v%d", log_verbosity);
}
if (zap_thread) {
strcat(tmpbuf, " -z");
}
if (add_new) {
if (hierarchy_list) {
sprintf(tmpbuf + strlen(tmpbuf), " -a %s", hierarchy_list);
} else {
strcat(tmpbuf, " -a all");
}
} else if (hierarchy_list) {
sprintf(tmpbuf + strlen(tmpbuf), " %s (only)", hierarchy_list);
}
log_entry("%s\n", tmpbuf);
}
/* Put our statistics into the log file.
*/
void
log_stats()
{
sprintf(line, "Processed %d group%s: added %d article%s, expired %d.\n",
processed_groups, processed_groups == 1 ? nullstr : "s",
added_articles, added_articles == 1 ? nullstr : "s",
expired_articles);
log_entry(line);
if (!daemon_delay) {
putchar('\n');
fputs(line, stdout);
}
if (added_groups) {
sprintf(line, "Turned %d group%s on.\n", added_groups,
added_groups == 1 ? nullstr : "s");
log_entry(line);
if (!daemon_delay) {
fputs(line, stdout);
}
}
if (removed_groups) {
sprintf(line, "Turned %d group%s off.\n", removed_groups,
removed_groups == 1 ? nullstr : "s");
log_entry(line);
if (!daemon_delay) {
fputs(line, stdout);
}
}
}
/* Generate a log entry with timestamp.
*/
/*VARARGS1*/
void
log_entry(fmt, arg1, arg2, arg3)
char *fmt;
long arg1, arg2, arg3;
{
#ifndef USESYSLOG
time_t now;
char *ctime();
#endif
if (initializing) {
fprintf(stderr, fmt, arg1, arg2, arg3);
return;
}
#ifndef USESYSLOG
(void) time(&now);
fprintf(fp_log, "%.12s%c", ctime(&now)+4, daemon_delay ? ' ' : '+');
fprintf(fp_log, fmt, arg1, arg2, arg3);
fflush(fp_log);
#else
syslog(LOG_INFO, fmt, arg1, arg2, arg3);
#endif
}
/* Generate a log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
** and newsgroup name.
*/
/*VARARGS1*/
void
log_error(fmt, arg1, arg2, arg3)
char *fmt;
long arg1;
long arg2;
long arg3;
{
char fmtbuf[256];
sprintf(fmtbuf, "%s: %s", line, fmt);
#ifndef USESYSLOG
log_entry(fmtbuf, arg1, arg2, arg3);
#else
syslog(LOG_NOTICE, fmtbuf, arg1, arg2, arg3);
#endif
if (*fmt == '*') {
grevious_error = TRUE;
if (!daemon_delay) {
putchar('E');
}
}
else {
if (!daemon_delay) {
putchar('e');
}
}
}
#ifdef MYCHSIZE
/* code courtesy of William Kucharski */
int
chsize(fd, length)
int fd; /* file descriptor */
off_t length; /* length to set file to */
{
extern long lseek();
struct flock fl;
if (fstat(fd, &filestat) < 0) {
return -1;
}
if (filestat.st_size < length) { /* extend file length */
/* Write a 0 byte at the end. */
if (lseek(fd, length - 1, 0) < 0
|| write(fd, "", 1) != 1) {
return -1;
}
} else {
/* Truncate file at length. */
fl.l_whence = 0;
fl.l_len = 0;
fl.l_start = length;
fl.l_type = F_WRLCK; /* write lock on file space */
/*
** This relies on the UNDOCUMENTED F_FREESP argument to
** fcntl(2), which truncates the file so that it ends at the
** position indicated by fl.l_start.
**
** Will minor miracles never cease?
*/
if (fcntl(fd, F_FREESP, &fl) < 0) {
return -1;
}
}
return 0;
}
#endif
#ifdef MVTRUNC
int
mvtrunc(filename, truncate_len)
char *filename;
long truncate_len;
{
FILE *fp_in, *fp_out;
sprintf(line, "%s.new", filename);
if ((fp_out = fos2open(line, "w")) == Nullfp) {
log_entry("Tried to create active2.new.\n");
return -1;
}
if ((fp_in = fos2open(filename, "r")) == Nullfp) {
fclose(fp_out);
unlink(line);
log_entry("Tried to re-open the active2 file.\n");
return -1;
}
while (ftell(fp_out) < truncate_len) {
if (!fgets(buf, sizeof buf, fp_in)) {
break;
}
fputs(buf, fp_out);
}
sprintf(buf, "%s.old", filename);
/*** OS2: rename fails if destination already exists, so delete it before ***/
unlink(buf);
rename(filename, buf);
/*** OS2: rename fails if destination already exists, so delete it before ***/
unlink(filename);
rename(line, filename);
fclose(fp_in);
fclose(fp_out);
return 0;
}
#endif
#ifndef RENAME
int
rename(old, new)
char *old, *new;
{
struct stat st;
if (stat(old, &st) == -1) {
return -1;
}
if (unlink(new) == -1 && errno != ENOENT) {
return -1;
}
if (link(old, new) == -1) {
return -1;
}
if (unlink(old) == -1) {
int e = errno;
(void) unlink(new);
errno = e;
return -1;
}
return 0;
}
#endif /*RENAME*/
#ifdef CHECKLOAD
#define FREQCHECK 300
#define SLPLOAD 300
#define UPTIME "/usr/ucb/uptime"
#define TOOHIGH 5
checkload()
{
static long lastcheck = 0;
long time(), i;
FILE *pp;
char buf[BUFSIZ];
register char *cp;
char *strrchr();
i = time(Null(time_t*));
if ((i - lastcheck) < FREQCHECK) {
return;
}
lastcheck = i;
again:
if ((pp = popen(UPTIME, "r")) == NULL) {
return;
}
if (fgets(buf, BUFSIZ, pp) == NULL) {
pclose(pp);
return;
}
pclose(pp);
if ((cp = strrchr(buf, ':')) == NULL) {
return;
}
if (atoi(cp + 2) >= TOOHIGH) {
sleep(SLPLOAD);
goto again;
} else {
return;
}
}
#endif