/* $Revision: 1.1.1.1 $ ** ** Expire news articles. */ #include "configdata.h" #include #include #include #include #if defined(DO_NEED_TIME) #include #endif /* defined(DO_NEED_TIME) */ #include #include #include "paths.h" #include "libinn.h" #include "clibrary.h" #include "inndcomm.h" #include "dbz.h" #include "qio.h" #include "macros.h" /* ** Stuff that more or less duplicates stuff in innd. */ #define NGH_HASH(Name, p, j) \ for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++ #define NGH_SIZE 128 #define NGH_BUCKET(j) &NGHtable[j & (NGH_SIZE - 1)] typedef struct _BUFFER { int Size; int Used; int Left; char *Data; } BUFFER; typedef struct _NEWSGROUP { char *Name; char *Rest; long Last; /* These fields are new. */ time_t Keep; time_t Default; time_t Purge; } NEWSGROUP; typedef struct _NGHASH { int Size; int Used; NEWSGROUP **Groups; } NGHASH; /* ** Expire-specific stuff. */ #define MAGIC_TIME 49710. typedef struct _BADGROUP { struct _BADGROUP *Next; char *Name; BOOL HasDirectory; } BADGROUP; STATIC BADGROUP *EXPbadgroups; STATIC BOOL EXPlinks; STATIC BOOL EXPquiet; STATIC BOOL EXPsizing; STATIC BOOL EXPtracing; STATIC BOOL EXPusepost; STATIC char EXPnewslib[] = _PATH_NEWSLIB; STATIC char ACTIVE[] = _PATH_ACTIVE; STATIC char SPOOL[] = _PATH_SPOOL; STATIC int nGroups; STATIC FILE *EXPunlinkfile; STATIC long EXPsaved; STATIC NEWSGROUP *Groups; STATIC NEWSGROUP EXPdefault; STATIC NGHASH NGHtable[NGH_SIZE]; STATIC STRING EXPreason; STATIC time_t EXPremember; STATIC time_t Now; /* Statistics; for -v flag. */ STATIC char *EXPgraph; STATIC int EXPverbose; STATIC long EXPprocessed; STATIC long EXPunlinked; STATIC long EXPhistdrop; STATIC long EXPhistremember; STATIC long EXPallgone; STATIC long EXPstillhere; STATIC int EXPsplit(); extern double atof(); /* ** Hash a newsgroup and see if we get it. */ STATIC NEWSGROUP * NGfind(Name) char *Name; { register char *p; register int i; unsigned int j; register NEWSGROUP **ngp; char c; NGHASH *htp; /* SUPPRESS 6 *//* Over/underflow from plus expression */ NGH_HASH(Name, p, j); htp = NGH_BUCKET(j); for (c = *Name, ngp = htp->Groups, i = htp->Used; --i >= 0; ngp++) if (c == ngp[0]->Name[0] && EQ(Name, ngp[0]->Name)) return ngp[0]; return NULL; } /* ** Sorting predicate to put newsgroups in rough order of their activity. */ STATIC int NGcompare(p1, p2) POINTER p1; POINTER p2; { NEWSGROUP **ng1; NEWSGROUP **ng2; ng1 = CAST(NEWSGROUP**, p1); ng2 = CAST(NEWSGROUP**, p2); return ng1[0]->Last - ng2[0]->Last; } /* ** Build the newsgroup structures from the active file. */ STATIC void BuildGroups(active) char *active; { register NGHASH *htp; register NEWSGROUP *ngp; register char *p; register char *q; register int i; register unsigned j; register int lines; int NGHbuckets; char *fields[5]; /* Count the number of groups. */ for (p = active, i = 0; (p = strchr(p, '\n')) != NULL; p++, i++) continue; nGroups = i; Groups = NEW(NEWSGROUP, i); /* Set up the default hash buckets. */ NGHbuckets = i / NGH_SIZE; if (NGHbuckets == 0) NGHbuckets = 1; for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) { htp->Size = NGHbuckets; htp->Groups = NEW(NEWSGROUP*, htp->Size); htp->Used = 0; } /* Fill in the array. */ lines = 0; for (p = active, ngp = Groups, i = nGroups; --i >= 0; ngp++, p = q + 1) { lines++; if ((q = strchr(p, '\n')) == NULL) { (void)fprintf(stderr, "%s: line %d missing newline\n", ACTIVE, lines); exit(1); } *q = '\0'; if (EXPsplit(p, ' ', fields, SIZEOF(fields)) != 4) { (void)fprintf(stderr, "%s: line %d wrong number of fields\n", ACTIVE, lines); exit(1); } ngp->Name = fields[0]; ngp->Last = atol(fields[1]); ngp->Rest = fields[3]; /* Find the right bucket for the group, make sure there is room. */ /* SUPPRESS 6 *//* Over/underflow from plus expression */ NGH_HASH(ngp->Name, p, j); htp = NGH_BUCKET(j); if (htp->Used >= htp->Size) { htp->Size += NGHbuckets; RENEW(htp->Groups, NEWSGROUP*, htp->Size); } htp->Groups[htp->Used++] = ngp; } /* Sort each hash bucket. */ for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) if (htp->Used > 1) qsort((POINTER)htp->Groups, (SIZE_T)htp->Used, sizeof htp->Groups[0], NGcompare); } /* ** Open a file or give up. */ STATIC FILE * EXPfopen(Remove, Name, Mode) BOOL Remove; STRING Name; char *Mode; { FILE *F; if (Remove && unlink(Name) < 0 && errno != ENOENT) (void)fprintf(stderr, "Warning, can't remove %s, %s\n", Name, strerror(errno)); if ((F = fopen(Name, Mode)) == NULL) { (void)fprintf(stderr, "Can't open %s in %s mode, %s\n", Name, Mode, strerror(errno)); exit(1); } return F; } /* ** Split a line at a specified field separator into a vector and return ** the number of fields found, or -1 on error. */ STATIC int EXPsplit(p, sep, argv, count) register char *p; register char sep; register char **argv; register int count; { register int i; for (i = 1, *argv++ = p; *p; ) if (*p++ == sep) { if (++i == count) /* Overflow. */ return -1; p[-1] = '\0'; for (*argv++ = p; *p == sep; p++) continue; } return i; } /* ** Parse a number field converting it into a "when did this start?". ** This makes the "keep it" tests fast, but inverts the logic of ** just about everything you expect. Print a message and return FALSE ** on error. */ STATIC BOOL EXPgetnum(line, word, v, name) int line; char *word; time_t *v; char *name; { register char *p; register BOOL SawDot; double d; if (caseEQ(word, "never")) { *v = (time_t)0; return TRUE; } /* Check the number. We don't have strtod yet. */ for (p = word; ISWHITE(*p); p++) continue; if (*p == '+' || *p == '-') p++; for (SawDot = FALSE; *p; p++) if (*p == '.') { if (SawDot) break; SawDot = TRUE; } else if (!CTYPE(isdigit, *p)) break; if (*p) { (void)fprintf(stderr, "Line %d, bad `%c' character in %s field\n", line, *p, name); return FALSE; } d = atof(word); if (d > MAGIC_TIME) *v = (time_t)0; else *v = Now - (time_t)(d * 86400.); return TRUE; } /* ** Set the expiration fields for all groups that match this pattern. */ STATIC void EXPmatch(p, v, mod) register char *p; register NEWSGROUP *v; register char mod; { register NEWSGROUP *ngp; register int i; register BOOL negate; negate = *p == '!'; if (negate) p++; for (ngp = Groups, i = nGroups; --i >= 0; ngp++) if (negate ? !wildmat(ngp->Name, p) : wildmat(ngp->Name, p)) if (mod == 'a' || (mod == 'm' && ngp->Rest[0] == NF_FLAG_MODERATED) || (mod == 'u' && ngp->Rest[0] != NF_FLAG_MODERATED)) { ngp->Keep = v->Keep; ngp->Default = v->Default; ngp->Purge = v->Purge; if (EXPverbose > 4) { (void)printf("%s", ngp->Name); (void)printf(" %13.13s", ctime(&v->Keep) + 3); (void)printf(" %13.13s", ctime(&v->Default) + 3); (void)printf(" %13.13s", ctime(&v->Purge) + 3); (void)printf(" (%s)\n", p); } } } /* ** Parse the expiration control file. Return TRUE if okay. */ STATIC BOOL EXPreadfile(F) register FILE *F; { register char *p; register int i; register int j; register int k; register char mod; NEWSGROUP v; BOOL SawDefault; char buff[BUFSIZ]; char *fields[6]; char **patterns; /* Scan all lines. */ EXPremember = -1; SawDefault = FALSE; patterns = NEW(char*, nGroups); for (i = 1; fgets(buff, sizeof buff, F) != NULL; i++) { if ((p = strchr(buff, '\n')) == NULL) { (void)fprintf(stderr, "Line %d too long\n", i); return FALSE; } *p = '\0'; if (buff[0] == '\0' || buff[0] == '#') continue; if ((j = EXPsplit(buff, ':', fields, SIZEOF(fields))) == -1) { (void)fprintf(stderr, "Line %d too many fields\n", i); return FALSE; } /* Expired-article remember line? */ if (EQ(fields[0], "/remember/")) { if (j != 2) { (void)fprintf(stderr, "Line %d bad format\n", i); return FALSE; } if (EXPremember != -1) { (void)fprintf(stderr, "Line %d duplicate /remember/\n", i); return FALSE; } if (!EXPgetnum(i, fields[1], &EXPremember, "remember")) return FALSE; continue; } /* Regular expiration line -- right number of fields? */ if (j != 5) { (void)fprintf(stderr, "Line %d bad format\n", i); return FALSE; } /* Parse the fields. */ if (strchr(fields[1], 'M') != NULL) mod = 'm'; else if (strchr(fields[1], 'U') != NULL) mod = 'u'; else if (strchr(fields[1], 'A') != NULL) mod = 'a'; else { (void)fprintf(stderr, "Line %d bad modflag\n", i); return FALSE; } if (!EXPgetnum(i, fields[2], &v.Keep, "keep") || !EXPgetnum(i, fields[3], &v.Default, "default") || !EXPgetnum(i, fields[4], &v.Purge, "purge")) return FALSE; /* These were turned into offsets, so the test is the opposie * of what you think it should be. If Purge isn't forever, * make sure it's greater then the other two fields. */ if (v.Purge) { /* Some value not forever; make sure other values are in range. */ if (v.Keep && v.Keep < v.Purge) { (void)fprintf(stderr, "Line %d keep>purge\n", i); return FALSE; } if (v.Default && v.Default < v.Purge) { (void)fprintf(stderr, "Line %d default>purge\n", i); return FALSE; } } /* Is this the default line? */ if (fields[0][0] == '*' && fields[0][1] == '\0' && mod == 'a') { if (SawDefault) { (void)fprintf(stderr, "Line %d duplicate default\n", i); break; } EXPdefault.Keep = v.Keep; EXPdefault.Default = v.Default; EXPdefault.Purge = v.Purge; SawDefault = TRUE; } /* Assign to all groups that match the pattern and flags. */ if ((j = EXPsplit(fields[0], ',', patterns, nGroups)) == -1) { (void)fprintf(stderr, "Line %d too many patterns\n", i); return FALSE; } for (k = 0; k < j; k++) EXPmatch(patterns[k], &v, mod); } DISPOSE(patterns); return TRUE; } /* ** Handle a newsgroup that isn't in the active file. */ STATIC NEWSGROUP * EXPnotfound(Entry) char *Entry; { static NEWSGROUP Removeit; register BADGROUP *bg; register char *p; struct stat Sb; char buff[SPOOLNAMEBUFF]; /* See if we already know about this group. */ for (bg = EXPbadgroups; bg; bg = bg->Next) if (EQ(Entry, bg->Name)) break; if (bg == NULL) { bg = NEW(BADGROUP, 1); bg->Name = COPY(Entry); (void)strcpy(buff, bg->Name); for (p = buff; *p; p++) if (*p == '.') *p = '/'; bg->HasDirectory = stat(buff, &Sb) >= 0 && S_ISDIR(Sb.st_mode); bg->Next = EXPbadgroups; EXPbadgroups = bg; if (!EXPquiet) { (void)fflush(stdout); (void)fprintf(stderr, "Group not matched (removed?) %s -- %s\n", Entry, bg->HasDirectory ? "Using default expiration" : "Purging all articles"); } } /* Directory still there; use default expiration. */ if (bg->HasDirectory) return &EXPdefault; /* No directory -- remove it all now. */ if (Removeit.Keep == 0) { Removeit.Keep = Now; Removeit.Default = Now; Removeit.Purge = Now; } return &Removeit; } /* ** Should we keep the specified article? */ STATIC BOOL EXPkeepit(Entry, when, Expires) char *Entry; time_t when; time_t Expires; { register char *p; register NEWSGROUP *ngp; if ((p = strchr(Entry, '/')) == NULL) { (void)fflush(stdout); (void)fprintf(stderr, "Bad entry, \"%s\"\n", Entry); return TRUE; } *p = '\0'; if ((ngp = NGfind(Entry)) == NULL) ngp = EXPnotfound(Entry); *p = '/'; if (EXPverbose > 2) { if (EXPverbose > 3) (void)printf("%s age = %0.2f\n", Entry, (Now - when) / 86400.); if (Expires == 0) { if (when < ngp->Default) (void)printf("%s too old (no exp)\n", Entry); } else { if (when < ngp->Purge) (void)printf("%s later than purge\n", Entry); if (when > ngp->Keep) (void)printf("%s earlier than min\n", Entry); if (Now > Expires) (void)printf("%s later than header\n", Entry); } } /* If no expiration, make sure it wasn't posted before the default. */ if (Expires == 0) return when >= ngp->Default; /* Make sure it's not posted before the purge cut-off and * that it's not due to expire. */ return when >= ngp->Purge && (Expires >= Now || when >= ngp->Keep); } /* ** An article can be removed. Either print a note, or actually remove it. ** Also fill in the article size. */ STATIC void EXPremove(p, size) char *p; long *size; { register char *q; struct stat Sb; /* Turn into a filename and get the size if we need it. */ for (q = p; *q; q++) if (*q == '.') *q = '/'; if (EXPsizing && *size < 0 && stat(p, &Sb) >= 0) *size = (int)(((long)Sb.st_size >> 10) + (((long)Sb.st_size >> 9) & 1)); if (EXPverbose > 1) (void)printf("\tunlink %s\n", p); EXPunlinked++; if (EXPtracing) { (void)printf("%s\n", p); return; } if (EXPunlinkfile) { (void)fprintf(EXPunlinkfile, "%s\n", p); if (!ferror(EXPunlinkfile)) return; (void)fprintf(stderr, "Can't write to -z file, %s\n", strerror(errno)); (void)fprintf(stderr, "(Will ignore it for rest of run.)\n"); (void)fclose(EXPunlinkfile); EXPunlinkfile = NULL; } if (unlink(p) < 0 && errno != ENOENT) (void)fprintf(stderr, "Can't unlink %s, %s\n", p, strerror(errno)); } /* ** Do the work of expiring one line. */ STATIC BOOL EXPdoline(out, line, length, arts) FILE *out; char *line; int length; char **arts; { static char IGNORING[] = "Ignoring bad line, \"%.20s...\"\n"; static long Offset; static BUFFER New; register char *p; register char *q; register char *first; register int i; int count; char *fields[4]; time_t Arrived; time_t Expires; time_t Posted; time_t when; long where; long size; datum key; datum value; char date[20]; /* Split up the major fields. */ i = EXPsplit(line, HIS_FIELDSEP, fields, SIZEOF(fields)); if (i != 2 && i != 3) { (void)fprintf(stderr, IGNORING, line); return TRUE; } /* Split up the time field, robustly. */ if ((p = strchr(fields[1], HIS_SUBFIELDSEP)) == NULL) { /* One sub-field: when the article arrived. */ Arrived = atol(fields[1]); Expires = 0; Posted = Arrived; } else { *p = '\0'; Arrived = atol(fields[1]); *p++ = HIS_SUBFIELDSEP; if ((q = strchr(p, HIS_SUBFIELDSEP)) == NULL) { /* Two sub-fields: arrival and expiration. */ Expires = EQ(p, HIS_NOEXP) ? 0 : atol(p); Posted = Arrived; } else { /* All three sub-fields: arrival, expiration, posted. */ *q = '\0'; Expires = EQ(p, HIS_NOEXP) ? 0 : atol(p); *q++ = HIS_SUBFIELDSEP; Posted = atol(q); } } if (i == 2) { /* History line for already-expired article. */ if (Arrived < EXPremember) { if (EXPverbose > 3) (void)printf("forget: %s\n", line); EXPhistdrop++; return TRUE; } /* Not time to forget about this one yet. */ if (out) { where = Offset; (void)fprintf(out, "%s%c%s\n", fields[0], HIS_FIELDSEP, fields[1]); Offset += strlen(fields[0]) + 1 + strlen(fields[1]) + 1; if (EXPverbose > 3) (void)printf("remember: %s\n", line); EXPhistremember++; } } else { /* Active article -- split up the file entries. */ count = EXPsplit(fields[2], ' ', arts, nGroups); if (count == -1) { (void)fprintf(stderr, IGNORING, line); return TRUE; } EXPprocessed++; when = EXPusepost ? Posted : Arrived; /* Get space to hold the remaining file name entries. */ if (New.Data == NULL) { New.Size = length; New.Data = NEW(char, New.Size); } else if (New.Size < length) { New.Size = length; RENEW(New.Data, char, New.Size); } /* The "first" variable tells us if we haven't saved the first * article yet. This only matters if we're doing link-saving. */ first = EXPlinks && count > 1 ? arts[0] : (char *)NULL; /* Loop over all file entries, see if we should keep each one. */ for (size = -1, q = New.Data, i = 0; i < count; i++) { p = arts[i]; if (*p == '\0') /* Shouldn't happen. */ continue; if (EXPkeepit(p, when, Expires)) { if (EXPverbose > 1) (void)printf("keep %s\n", p); if (first != NULL) { /* Keeping one and haven't kept the first; so save it. */ if (i > 0) q += strlen(strcpy(q, first)); first = NULL; } if (q > New.Data) *q++ = ' '; q += strlen(strcpy(q, p)); continue; } /* Don't delete the file if preserving symbolic links to it. */ if (EXPlinks && i == 0 && count > 1) continue; EXPremove(arts[i], &size); } /* If saving links and didn't have to save the leader, delete it. */ if (EXPlinks && first != NULL) EXPremove(first, &size); if (q == New.Data) { if (EXPsizing && size > 0) EXPsaved += size; if (EXPremember > 0 && out != NULL) { where = Offset; (void)sprintf(date, "%ld", (long)Arrived); (void)fprintf(out, "%s%c%s%c%s\n", fields[0], HIS_FIELDSEP, date, HIS_SUBFIELDSEP, HIS_NOEXP); Offset += strlen(fields[0]) + 1 + strlen(date) + 1 + STRLEN(HIS_NOEXP) + 1; if (EXPverbose > 3) (void)printf("remember history: %s%c%s%c%s\n", fields[0], HIS_FIELDSEP, date, HIS_SUBFIELDSEP, HIS_NOEXP); EXPallgone++; } } else if (out) { where = Offset; (void)fprintf(out, "%s%c%s%c%s\n", fields[0], HIS_FIELDSEP, fields[1], HIS_FIELDSEP, New.Data); Offset += strlen(fields[0]) + 1 + strlen(fields[1]) + 1 + strlen(New.Data) + 1; if (EXPverbose > 3) (void)printf("remember article: %s%c%s%c%s\n", fields[0], HIS_FIELDSEP, fields[1], HIS_FIELDSEP, New.Data); EXPstillhere++; } } if (out == NULL) return TRUE; if (ferror(out)) { (void)fprintf(stderr, "Can't write new history, %s\n", strerror(errno)); return FALSE; } /* Set up the DBZ data. We don't have to sanitize the Message-ID * since it had to have been clean to get in there. */ key.dptr = fields[0]; key.dsize = strlen(key.dptr) + 1; value.dptr = (char *)&where; value.dsize = sizeof where; if (EXPverbose > 4) (void)printf("\tdbz %s@%ld\n", key.dptr, where); if (dbzstore(key, value) < 0) { (void)fprintf(stderr, "Can't store key, %s\n", strerror(errno)); return FALSE; } return TRUE; } /* ** Clean up link with the server and exit. */ STATIC NORETURN CleanupAndExit(Server, Paused, x) BOOL Server; BOOL Paused; int x; { FILE *F; if (Server) (void)ICCreserve(""); if (Paused && ICCgo(EXPreason) != 0) { (void)fprintf(stderr, "Can't unpause server, %s\n", strerror(errno)); x = 1; } if (Server && ICCclose() < 0) { (void)fprintf(stderr, "Can't close communication link, %s\n", strerror(errno)); x = 1; } if (EXPunlinkfile && fclose(EXPunlinkfile) == EOF) { (void)fprintf(stderr, "Can't close -z file, %s\n", strerror(errno)); x = 1; } /* Report stats. */ if (EXPverbose) { (void)printf("Article lines processed %8ld\n", EXPprocessed); (void)printf("Articles retained %8ld\n", EXPstillhere); (void)printf("Entries expired %8ld\n", EXPallgone); (void)printf("Files unlinked %8ld\n", EXPunlinked); (void)printf("Old entries dropped %8ld\n", EXPhistdrop); (void)printf("Old entries retained %8ld\n", EXPhistremember); } /* Append statistics to a summary file */ if (EXPgraph) { F = EXPfopen(FALSE, EXPgraph, "a"); (void)fprintf(F, "%ld %ld %ld %ld %ld %ld %ld\n", (long)Now, EXPprocessed, EXPstillhere, EXPallgone, EXPunlinked, EXPhistdrop, EXPhistremember); (void)fclose(F); } exit(x); } /* ** Print a usage message and exit. */ STATIC NORETURN Usage() { (void)fprintf(stderr, "Usage: expire [flags] [expire.ctl]\n"); exit(1); } int main(ac, av) int ac; char *av[]; { static char CANTCD[] = "Can't cd to %s, %s\n"; register int i; register int line; register char *p; register QIOSTATE *qp; FILE *F; char *active; char **arts; STRING History; STRING HistoryText; STRING HistoryPath; STRING HistoryDB; char *Historydir; char *Historypag; char *NHistory; char *NHistorydir; char *NHistorypag; char buff[SMBUF]; register FILE *out; BOOL Server; BOOL Paused; BOOL Bad; BOOL IgnoreOld; BOOL Writing; BOOL UnlinkFile; time_t TimeWarp; /* Set defaults. */ Server = TRUE; IgnoreOld = FALSE; History = "history"; HistoryText = _PATH_HISTORY; HistoryPath = NULL; Writing = TRUE; TimeWarp = 0; UnlinkFile = FALSE; (void)umask(NEWSUMASK); /* Parse JCL. */ while ((i = getopt(ac, av, "f:h:d:g:ilnpqr:stv:w:xz:")) != EOF) switch (i) { default: Usage(); /* NOTREACHED */ case 'd': HistoryPath = optarg; break; case 'f': History = optarg; break; case 'g': EXPgraph = optarg; break; case 'h': HistoryText = optarg; break; case 'i': IgnoreOld = TRUE; break; case 'l': EXPlinks = TRUE; break; case 'n': Server = FALSE; break; case 'p': EXPusepost = TRUE; break; case 'q': EXPquiet = TRUE; break; case 'r': EXPreason = optarg; break; case 's': EXPsizing = TRUE; break; case 't': EXPtracing = TRUE; break; case 'v': EXPverbose = atoi(optarg); break; case 'w': TimeWarp = (time_t)(atof(optarg) * 86400.); break; case 'x': Writing = FALSE; break; case 'z': EXPunlinkfile = EXPfopen(TRUE, optarg, "a"); UnlinkFile = TRUE; break; } ac -= optind; av += optind; if (ac != 0 && ac != 1) Usage(); /* Get active file, parse it. */ if ((active = ReadInFile(ACTIVE, (struct stat *)NULL)) == NULL) { (void)fprintf(stderr, "Can't read %s, %s\n", ACTIVE, strerror(errno)); exit(1); } BuildGroups(active); (void)time(&Now); Now += TimeWarp; /* Parse the control file. */ if (av[0]) F = EQ(av[0], "-") ? stdin : EXPfopen(FALSE, av[0], "r"); else F = EXPfopen(FALSE, _PATH_EXPIRECTL, "r"); if (!EXPreadfile(F)) { (void)fclose(F); (void)fprintf(stderr, "Format error in control file\n"); exit(1); } (void)fclose(F); /* Set up the link, reserve the lock. */ if (EXPreason == NULL) { (void)sprintf(buff, "Expiring process %ld", (long)getpid()); EXPreason = COPY(buff); } if (Server) { /* If we fail, leave evidence behind. */ if (ICCopen() < 0) { (void)fprintf(stderr, "Can't open channel to server, %s\n", strerror(errno)); CleanupAndExit(FALSE, FALSE, 1); } if (ICCreserve(EXPreason) != 0) { (void)fprintf(stderr, "Can't reserve server\n"); CleanupAndExit(FALSE, FALSE, 1); } } /* Make the history filenames. */ HistoryDB = COPY(HistoryText); (void)sprintf(buff, "%s.dir", HistoryDB); Historydir = COPY(buff); (void)sprintf(buff, "%s.pag", HistoryDB); Historypag = COPY(buff); if (HistoryPath) (void)sprintf(buff, "%s/%s.n", HistoryPath, History); else (void)sprintf(buff, "%s.n", History); NHistory = COPY(buff); (void)sprintf(buff, "%s.dir", NHistory); NHistorydir = COPY(buff); (void)sprintf(buff, "%s.pag", NHistory); NHistorypag = COPY(buff); if (!Writing) out = NULL; else { /* Open new history files, relative to news lib. */ if (chdir(EXPnewslib) < 0) { (void)fprintf(stderr, CANTCD, EXPnewslib, strerror(errno)); exit(1); } out = EXPfopen(TRUE, NHistory, "w"); (void)fclose(EXPfopen(TRUE, NHistorydir, "w")); (void)fclose(EXPfopen(TRUE, NHistorypag, "w")); if (EXPverbose > 3) (void)printf("created: %s %s %s\n", NHistory, NHistorydir, NHistorypag); (void)dbzincore(1); if (IgnoreOld) { if (dbzfresh(NHistory, dbzsize(0L), '\t', 'C', 0L) < 0) { (void)fprintf(stderr, "Can't create database, %s\n", strerror(errno)); exit(1); } } else if (dbzagain(NHistory, HistoryDB) < 0) { (void)fprintf(stderr, "Can't dbzagain, %s\n", strerror(errno)); exit(1); } } if (chdir(SPOOL) < 0) { (void)fprintf(stderr, CANTCD, SPOOL, strerror(errno)); exit(1); } /* Main processing loop. */ arts = NEW(char*, nGroups); if ((qp = QIOopen(HistoryText, QIO_BUFFER)) == NULL) { (void)fprintf(stderr, "Can't open history file, %s\n", strerror(errno)); CleanupAndExit(Server, FALSE, 1); } for (Bad = FALSE, line = 1, Paused = FALSE; ; line++) { if ((p = QIOread(qp)) != NULL) { if (!EXPdoline(out, p, QIOlength(qp), arts)) { Bad = TRUE; if (errno == ENOSPC) { (void)unlink(NHistory); (void)unlink(NHistorydir); (void)unlink(NHistorypag); } break; } continue; } /* Read or line-format error? */ if (QIOerror(qp)) { (void)fprintf(stderr, "Can't read line %d, %s\n", line, strerror(errno)); QIOclose(qp); CleanupAndExit(Server, Paused, 1); } if (QIOtoolong(qp)) { (void)fprintf(stderr, "Line %d too long\n", line); QIOclose(qp); CleanupAndExit(Server, Paused, 1); } /* We hit EOF. */ if (Paused || !Server) /* Already paused or we don't want to pause -- we're done. */ break; if (ICCpause(EXPreason) != 0) { (void)fprintf(stderr, "Can't pause server, %s\n", strerror(errno)); QIOclose(qp); CleanupAndExit(Server, Paused, 1); } Paused = TRUE; } QIOclose(qp); DISPOSE(arts); if (Writing) { /* Close the output files. */ if (ferror(out) || fflush(out) == EOF || fclose(out) == EOF) { (void)fprintf(stderr, "Can't close %s, %s\n", NHistory, strerror(errno)); Bad = TRUE; } if (dbmclose() < 0) { (void)fprintf(stderr, "Can't close history, %s\n", strerror(errno)); Bad = TRUE; } if (UnlinkFile && EXPunlinkfile == NULL) /* Got -z but file was closed; oops. */ Bad = TRUE; /* If we're done okay, and we're not tracing, slip in the new files. */ if (EXPverbose) { if (Bad) (void)printf("Expire errors: history files not updated.\n"); if (EXPtracing) (void)printf("Expire tracing: history files not updated.\n"); } if (!Bad && !EXPtracing) { if (chdir(EXPnewslib) < 0) { (void)fprintf(stderr, CANTCD, EXPnewslib, strerror(errno)); CleanupAndExit(Server, Paused, 1); } /* If user used the -d flag, mark we're done and exit. */ if (HistoryPath != NULL) { (void)sprintf(buff, "%s.done", NHistory); (void)fclose(EXPfopen(FALSE, buff, "w")); CleanupAndExit(Server, FALSE, 0); } if (rename(NHistory, HistoryText) < 0 || rename(NHistorydir, Historydir) < 0 || rename(NHistorypag, Historypag) < 0) { (void)fprintf(stderr, "Can't replace history files, %s\n", strerror(errno)); /* Yes -- leave the server paused. */ CleanupAndExit(Server, FALSE, 1); } } } if (EXPsizing) (void)printf("%s approximately %ldk\n", EXPtracing ? "Would remove" : "Removed", EXPsaved); CleanupAndExit(Server, Paused, Bad ? 1 : 0); /* NOTREACHED */ }