home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-11-20 | 83.9 KB | 3,191 lines |
- Newsgroups: comp.sources.unix
- From: davison@borland.com (Wayne Davison)
- Subject: v27i091: mthreads - netnews database generator/manager, V3.1, Part02/04
- References: <1.753873779.13611@gw.home.vix.com>
- Sender: unix-sources-moderator@gw.home.vix.com
- Approved: vixie@gw.home.vix.com
-
- Submitted-By: davison@borland.com (Wayne Davison)
- Posting-Number: Volume 27, Issue 91
- Archive-Name: mthreads/part02
-
- #! /bin/sh
- # This is a shell archive. Remove anything before this line, then unpack
- # it by saving it into a file and typing "sh file". To overwrite existing
- # files, type "sh file -c". You can also feed this as standard input via
- # unshar, or by typing "sh <file", e.g.. If this archive is complete, you
- # will see the following message at the end:
- # "End of archive 2 (of 4)."
- # Contents: mt-read.c mthreads.8 mthreads.c parsedate.y
- # Wrapped by vixie@gw.home.vix.com on Sun Nov 21 01:12:01 1993
- PATH=/bin:/usr/bin:/usr/ucb ; export PATH
- if test -f 'mt-read.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'mt-read.c'\"
- else
- echo shar: Extracting \"'mt-read.c'\" \(15314 characters\)
- sed "s/^X//" >'mt-read.c' <<'END_OF_FILE'
- X/* $Id: mt-read.c,v 3.0 1992/10/01 00:14:04 davison Trn $
- X*/
- X/* The authors make no claims as to the fitness or correctness of this software
- X * for any use whatsoever, and it is provided as is. Any use of this software
- X * is at the user's own risk.
- X */
- X
- X#include "EXTERN.h"
- X#include "common.h"
- X#include "thread.h"
- X#include "mthreads.h"
- X
- Xextern long first;
- X
- Xstatic FILE *fp_in;
- X
- Xint read_authors _((void));
- Xint read_subjects _((void));
- Xint read_roots _((void));
- Xint read_articles _((void));
- Xint read_ids _((void));
- Xint read_item _((char **, MEM_SIZE));
- Xvoid tweak_roots _((void));
- X
- X/* Attempt to open the thread file. If it's there, only grab the totals
- X** from the start of the file. This should give them enough information
- X** to decide if they need to read the whole thing into memory.
- X*/
- Xint
- Xinit_data(filename)
- Xchar *filename;
- X{
- X root_root = Null(ROOT*);
- X author_root = Null(AUTHOR*);
- X unk_domain.ids = Nullart;
- X unk_domain.link = Null(DOMAIN*);
- X
- X if (filename && (fp_in = fopen(filename, FOPEN_RB)) != Nullfp) {
- X#ifdef SETBUFFER
- X setbuffer(fp_in, rwbuf, RWBUFSIZ);
- X#else
- X# ifdef SETVBUF
- X setvbuf(fp_in, rwbuf, _IOFBF, RWBUFSIZ);
- X# endif
- X#endif
- X if (fread((char*)&total,1,sizeof (TOTAL),fp_in) == sizeof (TOTAL)) {
- X /* If the data looks ok, return success. */
- X if (total.last - total.first >= 0 && total.author > 0
- X && total.article > 0 && total.subject > 0 && total.domain > 0
- X && total.subject <= total.article && total.author <= total.article
- X && total.root <= total.article && total.domain <= total.article
- X && total.string1 > 0 && total.string2 > 0) {
- X return 1;
- X }
- X }
- X bzero(&total, sizeof (TOTAL));
- X total.first = first;
- X total.last = first - 1;
- X return 1;
- X }
- X bzero(&total, sizeof (TOTAL));
- X return 0;
- X}
- X
- X/* They want everything. Read in the packed information and transform it
- X** into a set of linked structures that is easily manipulated.
- X*/
- Xint
- Xread_data()
- X{
- X /* If this is an empty thread file, simply return success. */
- X if (!total.root) {
- X fclose(fp_in);
- X return 1;
- X }
- X
- X if (read_authors()
- X && read_subjects()
- X && read_roots()
- X && read_articles()
- X && read_ids())
- X {
- X tweak_roots();
- X fclose(fp_in);
- X return 1;
- X }
- X /* Something failed. Safefree takes care of checking if some items
- X ** were already freed. Any partially-allocated structures were freed
- X ** before we got here. All other structures are cleaned up.
- X */
- X if (root_root) {
- X register ROOT *root, *next_root;
- X register SUBJECT *subj, *next_subj;
- X
- X for (root = root_root; root; root = next_root) {
- X for (subj = root->subjects; subj; subj = next_subj) {
- X next_subj = subj->link;
- X free(subj->str);
- X free(subj);
- X }
- X next_root = root->link;
- X free(root);
- X }
- X root_root = Null(ROOT*);
- X }
- X if (author_array) {
- X register int count;
- X
- X for (count = total.author; count--;) {
- X free(author_array[count]->name);
- X free(author_array[count]);
- X }
- X safefree(&author_array);
- X author_root = Null(AUTHOR*);
- X }
- X if (article_array) {
- X register int count;
- X
- X for (count = total.article; count--;) {
- X free(article_array[count]);
- X }
- X safefree(&article_array);
- X }
- X safefree(&strings);
- X safefree(&subject_cnts);
- X safefree(&subject_array);
- X safefree(&author_cnts);
- X safefree(&root_array);
- X safefree(&ids);
- X unk_domain.ids = Nullart;
- X unk_domain.link = Null(DOMAIN*);
- X fclose(fp_in);
- X return 0;
- X}
- X
- X/* They don't want to read the data. Close the file if we opened it.
- X*/
- Xvoid
- Xdont_read_data(open_flag)
- Xint open_flag; /* 0 == not opened, 1 == open failed, 2 == open */
- X{
- X if (open_flag == 2) {
- X fclose(fp_in);
- X bzero(&total, sizeof (TOTAL));
- X }
- X}
- X
- X#define give_string_to(dest) /* Comment for makedepend to \
- X ** ignore the backslash above */ \
- X{\
- X register MEM_SIZE len = strlen(string_ptr) + 1;\
- X dest = safemalloc(len);\
- X bcopy(string_ptr, dest, (int)len);\
- X string_ptr += len;\
- X}
- X
- Xchar *subject_strings, *string_end;
- X
- X/* The author information is an array of use-counts, followed by all the
- X** null-terminated strings crammed together. The subject strings are read
- X** in at the same time, since they are appended to the end of the author
- X** strings.
- X*/
- Xint
- Xread_authors()
- X{
- X register int count, author_tally;
- X register char *string_ptr;
- X register WORD *authp;
- X register AUTHOR *author, *last_author, **author_ptr;
- X
- X if (!read_item((char **)&author_cnts,
- X (MEM_SIZE)(total.author * sizeof (WORD)))
- X || !read_item((char **)&strings, total.string1)) {
- X /* (Error already logged.) */
- X return 0;
- X }
- X
- X string_ptr = strings;
- X string_end = string_ptr + total.string1;
- X if (string_end[-1] != '\0') {
- X log_error("first string table is invalid.\n");
- X return 0;
- X }
- X
- X /* We'll use this array to point each article at its proper author
- X ** (packed values are saved as indexes).
- X */
- X author_array = (AUTHOR**)safemalloc(total.author * sizeof (AUTHOR*));
- X author_ptr = author_array;
- X
- X authp = author_cnts;
- X
- X author_tally = 0;
- X#ifndef lint
- X last_author = (AUTHOR*)&author_root;
- X#else
- X last_author = Null(AUTHOR*);
- X#endif
- X for (count = total.author; count; count--) {
- X if (string_ptr >= string_end) {
- X break;
- X }
- X *author_ptr++ = author = (AUTHOR*)safemalloc(sizeof (AUTHOR));
- X last_author->link = author;
- X give_string_to(author->name);
- X author_tally += *authp;
- X author->count = *authp++;
- X last_author = author;
- X }
- X last_author->link = Null(AUTHOR*);
- X
- X subject_strings = string_ptr;
- X
- X safefree(&author_cnts);
- X
- X if (count || author_tally > total.article) {
- X log_error("author unpacking failed.\n");
- X for (; count < total.author; count++) {
- X free((*--author_ptr)->name);
- X free(*author_ptr);
- X }
- X safefree(&author_array);
- X return 0;
- X }
- X return 1;
- X}
- X
- X/* The subject values consist of the crammed-together null-terminated strings
- X** (already read in above) and the use-count array. They were saved in the
- X** order that the roots require while being unpacked.
- X*/
- Xint
- Xread_subjects()
- X{
- X if (!read_item((char **)&subject_cnts,
- X (MEM_SIZE)(total.subject * sizeof (WORD)))) {
- X /* (Error already logged.) */
- X return 0;
- X }
- X return 1;
- X}
- X
- X/* Read in the packed root structures and recreate the linked list versions,
- X** processing each root's subjects as we go. Defer interpretation of article
- X** offsets until we unpack the article structures.
- X*/
- Xint
- Xread_roots()
- X{
- X register int count, subj_tally;
- X register char *string_ptr;
- X register WORD *subjp;
- X ROOT *root, *last_root, **root_ptr;
- X SUBJECT *subject, *last_subject, **subj_ptr;
- X int ret;
- X
- X /* Use this array when unpacking the article's subject offset. */
- X subject_array = (SUBJECT**)safemalloc(total.subject * sizeof (SUBJECT*));
- X subj_ptr = subject_array;
- X /* And this array points the article's root offset at the right spot. */
- X root_array = (ROOT**)safemalloc(total.root * sizeof (ROOT*));
- X root_ptr = root_array;
- X
- X subjp = subject_cnts;
- X string_ptr = subject_strings; /* string_end is already set */
- X
- X subj_tally = 0;
- X#ifndef lint
- X last_root = (ROOT*)&root_root;
- X#else
- X last_root = Null(ROOT*);
- X#endif
- X for (count = total.root; count--;) {
- X ret = fread((char*)&p_root, 1, sizeof (PACKED_ROOT), fp_in);
- X if (ret != sizeof (PACKED_ROOT)) {
- X log_error("failed root read -- %d bytes instead of %d.\n",
- X ret, sizeof (PACKED_ROOT));
- X return 0;
- X }
- X if (p_root.articles < 0 || p_root.articles >= total.article
- X || subj_ptr - subject_array + p_root.subject_cnt > total.subject) {
- X log_error("root has invalid values.\n");
- X return 0;
- X }
- X *root_ptr++ = root = (ROOT*)safemalloc(sizeof (ROOT));
- X root->link = Null(ROOT*);
- X root->articles = Nullart;
- X root->seq = p_root.articles;
- X root->root_num = p_root.root_num;
- X root->thread_cnt = p_root.thread_cnt;
- X root->subject_cnt = p_root.subject_cnt;
- X last_root->link = root;
- X last_root = root;
- X
- X#ifndef lint
- X last_subject = (SUBJECT*)&root->subjects;
- X#else
- X last_subject = Null(SUBJECT*);
- X#endif
- X while (p_root.subject_cnt--) {
- X if (string_ptr >= string_end) {
- X log_error("error unpacking subject strings.\n");
- X last_subject->link = Null(SUBJECT*);
- X return 0;
- X }
- X *subj_ptr++ = subject = (SUBJECT*)safemalloc(sizeof (SUBJECT));
- X last_subject->link = subject;
- X give_string_to(subject->str);
- X subj_tally += *subjp;
- X subject->count = *subjp++;
- X last_subject = subject;
- X }
- X last_subject->link = Null(SUBJECT*);
- X }
- X if (subj_ptr != subject_array + total.subject
- X || subj_tally > total.article
- X || string_ptr != string_end) {
- X log_error("subject data is invalid.\n");
- X return 0;
- X }
- X safefree(&subject_cnts);
- X safefree(&strings);
- X
- X return 1;
- X}
- X
- Xbool invalid_data;
- X
- X/* A simple routine that checks the validity of the article's subject value.
- X** A -1 means that it is NULL, otherwise it should be an offset into the
- X** subject array we just unpacked.
- X*/
- XSUBJECT *
- Xvalid_subject(num, art_num)
- XWORD num;
- Xlong art_num;
- X{
- X if (num == -1) {
- X return Null(SUBJECT*);
- X }
- X if (num < 0 || num >= total.subject) {
- X log_error("invalid subject in thread file: %d [%ld]\n", num, art_num);
- X invalid_data = TRUE;
- X return Null(SUBJECT*);
- X }
- X return subject_array[num];
- X}
- X
- X/* Ditto for author checking. */
- XAUTHOR *
- Xvalid_author(num, art_num)
- XWORD num;
- Xlong art_num;
- X{
- X if (num == -1) {
- X return Null(AUTHOR*);
- X }
- X if (num < 0 || num >= total.author) {
- X log_error("invalid author in thread file: %d [%ld]\n", num, art_num);
- X invalid_data = TRUE;
- X return Null(AUTHOR*);
- X }
- X return author_array[num];
- X}
- X
- X/* Our parent/sibling information is a relative offset in the article array.
- X** zero for none. Child values are always found in the very next array
- X** element if child_cnt is non-zero.
- X*/
- XARTICLE *
- Xvalid_node(relative_offset, num)
- XWORD relative_offset;
- Xint num;
- X{
- X if (!relative_offset) {
- X return Nullart;
- X }
- X num += relative_offset;
- X if (num < 0 || num >= total.article) {
- X log_error("invalid node offset in thread file.\n");
- X invalid_data = TRUE;
- X return Nullart;
- X }
- X return article_array[num];
- X}
- X
- X/* Read the articles into their linked lists. Point everything everywhere. */
- Xint
- Xread_articles()
- X{
- X register int count;
- X register ARTICLE *article, **article_ptr;
- X int ret;
- X
- X /* Build an array to interpret interlinkages of articles. */
- X article_array = (ARTICLE**)safemalloc(total.article * sizeof (ARTICLE*));
- X article_ptr = article_array;
- X
- X /* Allocate all the structures up-front so that we can point to unread
- X ** siblings as we go.
- X */
- X for (count = total.article; count--;) {
- X *article_ptr++ = (ARTICLE*)safemalloc(sizeof (ARTICLE));
- X }
- X invalid_data = FALSE;
- X article_ptr = article_array;
- X for (count = 0; count < total.article; count++) {
- X ret = fread((char*)&p_article, 1, sizeof (PACKED_ARTICLE), fp_in);
- X if (ret != sizeof (PACKED_ARTICLE)) {
- X log_error("failed article read -- %d bytes instead of %d.\n", ret, sizeof (PACKED_ARTICLE));
- X return 0;
- X }
- X
- X article = *article_ptr++;
- X article->num = p_article.num;
- X article->date = p_article.date;
- X article->subject = valid_subject(p_article.subject, p_article.num);
- X article->author = valid_author(p_article.author, p_article.num);
- X article->flags = p_article.flags;
- X article->child_cnt = p_article.child_cnt;
- X article->parent = valid_node(p_article.parent, count);
- X article->children = valid_node(article->child_cnt ? 1 : 0, count);
- X article->siblings = valid_node(p_article.siblings, count);
- X if (p_article.root < 0 || p_article.root >= total.root) {
- X log_error("invalid root offset in thread file.\n");
- X return 0;
- X }
- X article->root = root_array[p_article.root];
- X if (invalid_data) {
- X /* (Error already logged.) */
- X return 0;
- X }
- X }
- X
- X /* We're done with most of the pointer arrays. */
- X safefree(&root_array);
- X safefree(&subject_array);
- X
- X return 1;
- X}
- X
- X/* Read the message-id strings and attach them to each article. The data
- X** format consists of the mushed-together null-terminated strings (a domain
- X** name followed by all its unique-id prefixes) and then the article offsets
- X** to which they belong. The first domain name was omitted, as it is the
- X** ".unknown." domain for those truly weird message-id's without '@'s.
- X*/
- Xint
- Xread_ids()
- X{
- X register DOMAIN *domain, *last;
- X register ARTICLE *article;
- X register char *string_ptr;
- X register int i, count;
- X
- X if (!read_item(&strings, total.string2)
- X || !read_item((char **)&ids,
- X (MEM_SIZE)((total.article + total.domain + 1)
- X * sizeof (WORD)))) {
- X return 0;
- X }
- X
- X string_ptr = strings;
- X string_end = string_ptr + total.string2;
- X
- X if (string_end[-1] != '\0') {
- X log_error("second string table is invalid.\n");
- X return 0;
- X }
- X
- X last = &unk_domain;
- X for (i = 0, count = total.domain + 1; count--; i++) {
- X if (i) {
- X if (string_ptr >= string_end) {
- X log_error("error unpacking domain strings.\n");
- X free_partial:
- X last->link = Null(DOMAIN*);
- X article = unk_domain.ids;
- X while (article) {
- X safefree(&article->id);
- X article = article->id_link;
- X }
- X domain = unk_domain.link;
- X while (domain) {
- X free(domain->name);
- X article = domain->ids;
- X while (article) {
- X safefree(&article->id);
- X article = article->id_link;
- X }
- X last = domain;
- X domain = domain->link;
- X free(last);
- X }
- X return 0;
- X }
- X domain = (DOMAIN*)safemalloc(sizeof (DOMAIN));
- X give_string_to(domain->name);
- X last->link = domain;
- X } else {
- X domain = &unk_domain;
- X }
- X if (ids[i] == -1) {
- X domain->ids = Nullart;
- X } else {
- X if (ids[i] < 0 || ids[i] >= total.article) {
- X id_error:
- X log_error("error in id array.\n");
- X domain->ids = Nullart;
- X goto free_partial;
- X }
- X article = article_array[ids[i]];
- X domain->ids = article;
- X for (;;) {
- X if (string_ptr >= string_end) {
- X log_error("error unpacking domain strings.\n");
- X article->id = Nullch;
- X article->id_link = Nullart;
- X goto free_partial;
- X }
- X give_string_to(article->id);
- X article->domain = domain;
- X if (++i >= total.article + total.domain + !count) {
- X log_error("overran id array unpacking domains.\n");
- X article->id_link = Nullart;
- X goto free_partial;
- X }
- X if (ids[i] != -1) {
- X if (ids[i] < 0 || ids[i] >= total.article) {
- X goto id_error;
- X }
- X article = article->id_link = article_array[ids[i]];
- X } else {
- X article->id_link = Nullart;
- X break;
- X }
- X }
- X }
- X last = domain;
- X }
- X last->link = Null(DOMAIN*);
- X safefree(&ids);
- X safefree(&strings);
- X
- X return 1;
- X}
- X
- X/* And finally, point all the roots at their root articles and get rid
- X** of anything left over that was used to aid our unpacking.
- X*/
- Xvoid
- Xtweak_roots()
- X{
- X register ROOT *root;
- X
- X for (root = root_root; root; root = root->link) {
- X root->articles = article_array[root->seq];
- X }
- X safefree(&author_array);
- X safefree(&article_array);
- X}
- X
- X/* A shorthand for reading a chunk of the file into a malloc'ed array.
- X*/
- Xint
- Xread_item(dest, len)
- Xchar **dest;
- XMEM_SIZE len;
- X{
- X int ret;
- X
- X *dest = safemalloc(len);
- X ret = fread(*dest, 1, (int)len, fp_in);
- X if (ret != len) {
- X log_error("only read %ld bytes instead of %ld.\n",
- X (long)ret, (long)len);
- X free(*dest);
- X *dest = Nullch;
- X return 0;
- X }
- X return 1;
- X}
- END_OF_FILE
- if test 15314 -ne `wc -c <'mt-read.c'`; then
- echo shar: \"'mt-read.c'\" unpacked with wrong size!
- fi
- # end of 'mt-read.c'
- fi
- if test -f 'mthreads.8' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'mthreads.8'\"
- else
- echo shar: Extracting \"'mthreads.8'\" \(10153 characters\)
- sed "s/^X//" >'mthreads.8' <<'END_OF_FILE'
- X.\" $Id: mthreads.8,v 3.0 1993/09/14 00:14:11 davison Trn $
- X.\"
- X.de Sh
- X.br
- X.ne 5
- X.PP
- X\fB\\$1\fR
- X.PP
- X..
- X.de Sp
- X.if t .sp .5v
- X.if n .sp
- X..
- X.\" unbreakable dash.
- X.tr \(*W-|\(bv\*(Tr
- X.ie n \{\
- X.ds -- \(*W-
- X.if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
- X.if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
- X.ds L" ""
- X.ds R" ""
- X.ds L' '
- X.ds R' '
- X'br\}
- X.el\{\
- X.ds -- \(em\|
- X.tr \*(Tr
- X.ds L" ``
- X.ds R" ''
- X.ds L' `
- X.ds R' '
- X'br\}
- X.TH MTHREADS 8 LOCAL
- X.UC 6
- X.SH NAME
- Xmthreads - threaded database manager for trn
- X.SH SYNOPSIS
- X.B mthreads [-d[MM]] [-e[HHMM]] [-acDfknstvz] [hierarchy_list]
- X.SH DESCRIPTION
- X.I Mthreads
- Xmanages the thread files that are used by the
- X.IR trn (1)
- Xnewsreader.
- X\*(L"Thread files\*(R" are used to store information about the news
- Xarticles and how they are all related to one another.
- X.PP
- X.I Mthreads
- Xshould be run periodically to update the thread database as new news arrives.
- XIt can be run in single-pass mode (out of cron), in daemon mode, or in a
- Xcombination of the two.
- XA site that gets its news feed during the night may just want to run mthreads
- Xonce a day (trn will handle any local postings that show up between passes).
- XIf more processing is needed, either run mthreads
- Xmore often or run it in daemon mode.
- XIn daemon mode, a background process is forked off that wakes up every 10
- Xminutes (by default) to check if the active file has been updated.
- XWhen the mthreads daemon is sleeping between passes, it is possible
- Xto run an mthreads single pass.
- XThis is often useful if you wish to run an enhanced expire pass more than
- Xonce a day (see the \-c and \-e options).
- X.SH INSTALLATION
- X.I Mthreads
- Xis installed in the PRIVLIB directory chosen during configuration.
- XWhen it is run for the first time, it will automatically create a file called
- X.I active2
- Xin the same directory.
- XThis file is essentially a copy of the active file that keeps the newsgroup
- Xtotals from the last run in one place.
- XIt is also used to choose which groups are to be processed into thread files.
- XAll groups start out as \*(L"unthreaded\*(R" unless they are turned on with
- Xa command like:
- X.IP
- Xmthreads all
- X.PP
- Xwhich would create thread file for all the groups.
- XFor testing purposes it is a good idea to start out small with a command
- Xlike:
- X.IP
- Xmthreads news
- X.PP
- Xwhich would thread only the news hierarchy.
- XThread processing can be turned on or off for individual groups or entire
- Xhierarchies by specifying the groups in a syntax very similar to that used
- Xin the sys file.
- XFor example, to turn on all of soc and talk except for talk.politics, and
- Xto turn off news.lists, use the following command once:
- X.IP
- Xmthreads soc,talk,!talk.politics,!news.lists
- X.PP
- XIf mthreads complains that another mthreads process is already running,
- Xyou can use the \-c option to tell it to continue trying to lock instead
- Xof giving up.
- X.PP
- XOnce all the desired groups are turned on, the hierarchy list should be
- Xomitted to allow mthreads to process all enabled groups.
- XIt can be used, however, in conjunction with the \-a option to customize
- Xwhich new groups get turned on as they are created.
- X.SH LOGGING
- XAs mthreads executes some status information (including error messages)
- Xis placed in
- Xthe file mt.log in the PRIVLIB directory, unless you chose to use SYSLOG.
- XThis file will grow without bounds, and should be scanned periodically for
- Xerrors, and trimmed in size when it grows too large.
- XSee the shell script
- X.I mt.check
- Xfor an mt.log maintainer that will send mail if it finds database errors.
- X.SH OPTIONS
- X.TP 5
- X.B \-a
- Xis used to automatically turn on thread processing for new news groups as
- Xthey are created.
- XWhen this option is specified, the hierarchy list is used to limit
- Xwhich new groups get enabled (omitting the hierarchy list is the same
- Xas specifying \*(L"all\*(R").
- XThe default without \-a is to leave new groups unthreaded.
- X.TP 5
- X.B \-c
- Xwill continue trying to lock the mthreads database for a single pass
- Xinstead of giving up.
- XThis is useful for running special commands out of cron while an mthreads
- Xdaemon is active.
- X.TP 5
- X.B \-D
- Xspecifies debug processing.
- XAny errors encountered reading a thread file will rename the offending
- Xfile to \*(L"bad.read\*(R".
- XAny errors detected while generating a new thread file will rename the
- Xfile to \*(L"bad.write\*(R".
- XIf more than one 'D' is specified, each group's name is output into
- Xthe log file before it is processed.
- X.TP 5
- X.B \-d
- Xis used to specify the daemon mode, where
- X.I mthreads
- Xforks a background task that periodically wakes up and checks for an updated
- Xactive file.
- XThe number of minutes to wait after the completion of the last pass can
- Xbe specified after the '-d' option (e.g. -d20), otherwise it will default to
- X10 minutes.
- X.TP 5
- X.B \-e
- Xtells
- X.I mthreads
- Xto run an enhanced expiration check on the database.
- XWithout this option, only articles below the minimum field in the active
- Xfile are expired.
- XWith this option, mthreads will periodically list all the article numbers
- Xto see which ones actually exist.
- XIn single-pass mode the
- X.B -e
- Xoption always affects the current pass \*(-- use it
- Xat lease once a day after expire has run.
- XIn daemon mode, the
- X.B -e
- Xoption will cause one pass a day to be the enhanced expire pass.
- XBy default, this is the first time mthreads wakes up after 12:30 am.
- XIf a different time is desired, it can be specified in the form HHMM
- X(e.g. -e2359).
- X.TP 5
- X.B -f
- Xis used to force
- X.I mthreads
- Xto open each and every thread file to see which ones really need to be
- Xupdated, not just the ones that differ in the active/active2 comparison.
- XIt will also remove any extraneous thread files from unthreaded groups
- X(which should only occur if you manually change the active2 file).
- XThis option should only be used when manipulating the thread files in
- Xunorthodox ways.
- X.TP 5
- X.B -k
- Xcan be used to terminate the currently running mthreads daemon, just as if it
- Xhad received a terminate signal.
- XWhen this option is specified, no other activity is performed.
- X.TP 5
- X.B -n
- Xtells
- X.I mthreads
- Xthat no actual processing of thread files is to be performed.
- XThis can be used to just adjust which groups are enabled, without
- Xactually doing any of the processing right away.
- X.TP 5
- X.B -s<usec>
- Xtells mthreads to sleep for <usec> microseconds before processing each
- Xarticle.
- XThis is useful if your NNTP server cannot handle mthreads running at
- Xfull speed.
- XUsing
- X.B -s
- Xby itself will sleep for an entire second to be compatible with older
- Xversions of mthreads.
- X.TP 5
- X.B -t
- Xis used to make mthreads update the active.times file (as specified
- Xduring configuration) with new directory names as they are encountered.
- XDon't use this option if your news software maintains this file for
- Xyou (as C news and INN do).
- X.TP 5
- X.B -v
- Xselects additional levels of verbosity in the log file.
- XThe default (without -v) is to log mthread's startup, the totals for each
- Xpass, and major database errors.
- XAdd one
- X.B -v
- Xto get extra reference line problems logged into the file.
- XAdd a second and a third for even more (useless?) information.
- XA fourth will cause mthreads to output each group's name into the log file
- Xbefore it is processed.
- X.TP 5
- X.B -V
- Xdisplays mthreads' version number and exits.
- X.TP 5
- X.B -z
- Xtells mthreads to 'zap' any thread file it believes to be corrupt.
- XThis will allow the file to be regenerated from scratch on the next pass.
- X.TP 5
- X.B hierarchy_list
- XThe hierarchy list is used to turn thread processing on or off for the listed
- Xgroups while limiting itself to updating only the listed groups.
- XIf specified with the \-a option, however, it only limits which new groups
- Xget enabled.
- XThe groups are specified in a manner very similar to the news software's
- Xsys file: \*(L"news\*(R" matches all groups in news; \*(L"!news\*(R" excludes
- Xall groups in news; \*(L"comp.all.ibm.pc,!comp.all.ibm.pc.all\*(L" matches both
- Xcomp.sys.ibm.pc and comp.binaries.ibm.pc, but not comp.binaries.ibm.pc.d.
- X.SH OUTPUT
- XWhen
- X.I mthreads
- Xis run in single-pass mode it generates a stream a status characters on
- Xstdout that present a visual display of what is happening. If
- Xsingle-pass mode is used for regular processing, this output can be
- Xredirected to /dev/null.
- X.Sp
- XThe output definitions:
- X.br
- X \&'.' = group's entry is up to date
- X.br
- X \&':' = group processed \*(-- no change
- X.br
- X \&'#' = group processed
- X.br
- X \&'-' = group processed \*(-- is now empty
- X.br
- X \&'x' = group excluded in active
- X.br
- X \&'X' = group excluded in active2
- X.br
- X \&'*' = unable to access a group
- X.br
- X \&'!' = write failed (bad news)
- X.br
- X \&'e' = informational error
- X.br
- X \&'E' = database-affecting error
- X.SH CONFIGURATION
- XDuring the configuration of
- X.IR mthreads ,
- Xa choice was made about where to place the thread data files.
- XThey either exist as a .thread file in each group's spool directory, or they
- Xare each a group.th file in a one-off directory structure on another drive.
- XSee the THREAD_DIR definition in config.h to review or change this definition.
- X.SH REBUILDING
- XIf the thread files are ever removed, also remove the file db.init in
- Xthe THREAD_DIR.
- XThis file contains the byte-order of the machine that generated the database,
- Xand needs to be removed to truly start from scratch.
- XAn easy way to get
- X.I mthreads
- Xto remove all the files except for db.init is to specify the command:
- X.IP
- Xmthreads !all
- X.PP
- XThis also turns off thread processing for all groups.
- X.SH "ERROR HANDLING"
- XIf the active2 file is removed or corrupted, it will
- Xbe automatically rebuilt in the normal course of operation.
- XThe record of which groups should be threaded will be lost, however.
- XMissing/corrupted thread files are automatically re-built.
- X.SH EXAMPLES
- XRecommended commands to run on a regular basis are:
- X.IP
- Xmthreads -dave0630
- X.PP
- Xto start up an mthreads daemon in verbose logging mode that automatically
- Xthreads new groups and performs an extended expire at 6:30 am, or:
- X.IP
- Xmthreads -e >/dev/null
- X.PP
- Xto run an mthreads single-pass with extended expire that leaves new groups
- Xunthreaded.
- X.SH FILES
- XNEWSLIB/active
- X.br
- XPRIVLIB/active2
- X.br
- XPRIVLIB/mt.log
- X.br
- XTHREAD_DIR/db.init
- X.br
- XPRIVLIB/LOCKmthreads
- X.br
- XPRIVLIB/LOCKmtdaemon
- X.br
- XLots of thread data files.
- X.SH AUTHOR
- XWayne Davison <davison@borland.com>
- END_OF_FILE
- if test 10153 -ne `wc -c <'mthreads.8'`; then
- echo shar: \"'mthreads.8'\" unpacked with wrong size!
- fi
- # end of 'mthreads.8'
- fi
- if test -f 'mthreads.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'mthreads.c'\"
- else
- echo shar: Extracting \"'mthreads.c'\" \(33313 characters\)
- sed "s/^X//" >'mthreads.c' <<'END_OF_FILE'
- X/* mthreads.c -- for making and updating a discussion-thread database
- X**
- X** We use the active file to read the high/low counts for each newsgroup
- X** and compare them with the corresponding values in a file called active2
- X** (which we created to keep the database high/lows in one place). If they
- X** don't match, we read/update/write the group's thread file and move on.
- X** If the active2 file is missing or corrupted, it will be repaired in the
- X** normal course of operation.
- X**
- X** Usage: mthreads [-d[MM]] [-e[HHMM]] [-s[hsec]] [-aDfknv] [hierarchy_list]
- X*/
- X/* The authors make no claims as to the fitness or correctness of this software
- X * for any use whatsoever, and it is provided as is. Any use of this software
- X * is at the user's own risk.
- X */
- X
- X#include "patchlevel.h"
- Xstatic char mtid[] = "@(#)$Id: mthreads.c,v 3.0 1993/10/01 00:14:06 davison Trn $";
- Xstatic char patchlevel[] = PATCHLEVEL;
- X
- X#include "EXTERN.h"
- X#include "common.h"
- X#include "thread.h"
- X#include "nntpclient.h"
- X#include "INTERN.h"
- X#include "mthreads.h"
- X
- X#if !defined(HAS_FTRUNCATE) && !defined(HAS_CHSIZE)
- X# ifdef F_FREESP
- X# define MYCHSIZE
- X# else
- X# define MVTRUNC
- X# endif
- X#endif
- X
- X#ifdef USE_SYSLOG
- X#include <syslog.h>
- X#else
- XFILE *fp_log;
- X#endif
- X
- XFILE *fp_tmp, *fp_active, *fp_active2, *fp_active2w = Nullfp;
- Xbool eof_active = FALSE, eof_active2 = FALSE;
- Xlong first, last, first2, last2;
- Xchar ch, ch2;
- X
- Xstruct stat filestat;
- X
- Xchar buf[LBUFLEN+1];
- X
- Xchar line[256];
- Xstatic char line2[256];
- X
- Xchar fmt_active2[] = "%s %010ld %07ld %c\n";
- X
- Xchar *filename;
- X
- Xtypedef struct _active_line {
- X struct _active_line *link;
- X char *name;
- X long last;
- X long first;
- X char type;
- X} ACTIVE_LINE;
- X
- X#define Nullact Null(ACTIVE_LINE*)
- X
- XACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
- X
- Xbool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
- Xbool add_new = FALSE, rebuild = FALSE, zap_thread = FALSE;
- Xbool acttimes_flag = FALSE, grevious_error;
- Xbool initializing = TRUE;
- Xint daemon_delay = 0, log_verbosity = 0, debug = 0, slow_down = 0;
- Xlong expire_time = 0;
- Xchar *hierarchy_list = NULL;
- Xlong truncate_len = -1;
- X
- Xchar nullstr[1] = "";
- X
- Xextern int locked, cron_locking;
- X
- X#define TIMER_FIRST 1
- X#define TIMER_DEFAULT (10 * 60)
- X
- Xint processed_groups = 0, added_groups = 0, removed_groups = 0;
- Xint action;
- X
- X#define NG_DEFAULT 0
- X#define NG_MATCH 1
- X#define NG_SKIP 2
- X
- X#ifndef USE_NNTP
- Xtime_t last_modified = 0;
- X#endif
- X
- XSignal_t alarm_handler(), int_handler();
- Xbool makethreads _((void));
- Xvoid log_startup _((void));
- Xvoid log_stats _((void));
- X
- Xint
- Xmain(argc, argv)
- Xint argc;
- Xchar *argv[];
- X{
- X#ifdef TIOCNOTTY
- X int fd;
- X#endif
- X long pid;
- X
- X while (--argc) {
- X if (**++argv == '-') {
- X while (*++*argv) {
- X switch (**argv) {
- X case 'a': /* automatically thread new groups */
- X add_new = TRUE;
- X break;
- X case 'c': /* continue trying to lock */
- X cron_locking = TRUE;
- X break;
- X case 'D': /* run in debug mode */
- X debug++;
- X break;
- X case 'd': /* run in daemon mode */
- X if (*++*argv <= '9' && **argv >= '0') {
- X daemon_delay = atoi(*argv) * 60;
- X while (*++*argv <= '9' && **argv >= '0') {
- X ;
- X }
- X } else {
- X daemon_delay = TIMER_DEFAULT;
- X }
- X --*argv;
- X break;
- X case 'e': { /* enhanced expire processing */
- X struct tm *ts;
- X long desired;
- X
- X (void) time(&expire_time);
- X ts = localtime(&expire_time);
- X
- X if (*++*argv <= '9' && **argv >= '0') {
- X desired = atol(*argv);
- X if (desired/100 > 23 || desired%100 > 59) {
- X fprintf(stderr, "Illegal expire time: '%04ld'\n",
- X desired);
- X exit(1);
- X }
- X desired = (desired/100)*60 + desired%100;
- X while (*++*argv <= '9' && **argv >= '0') {
- X ;
- X }
- X } else {
- X desired = 30; /* 0030 = 12:30am */
- X }
- X --*argv;
- X desired -= ts->tm_hour * 60 + ts->tm_min;
- X if (desired < 0) {
- X desired += 24 * 60;
- X }
- X expire_time += desired * 60 - ts->tm_sec;
- X break;
- X }
- X case 'f': /* force each group to process */
- X force_flag = TRUE;
- X break;
- X case 'k': /* kill running mthreads */
- X kill_mthreads = TRUE;
- X break;
- X case 'n': /* don't process anything */
- X no_processing = TRUE;
- X break;
- X case 's': /* sleep between articles */
- X if (*++*argv <= '9' && **argv >= '0') {
- X slow_down = atoi(*argv);
- X while (*++*argv <= '9' && **argv >= '0') {
- X ;
- X }
- X } else {
- X slow_down = 1L * 1000 * 1000;
- X }
- X --*argv;
- X break;
- X case 't': /* maintain active.times file */
- X#ifdef ACTIVE_TIMES
- X if (strEQ(ACTIVE_TIMES, "nntp")) {
- X fprintf(stderr, "Ignoring the -t option.\n");
- X } else {
- X acttimes_flag = TRUE;
- X }
- X#else
- X fprintf(stderr, "Ignoring the -t option.\n");
- X#endif
- X break;
- X case 'v': /* get more verbose in the log file */
- X log_verbosity++;
- X break;
- X case 'V':
- X fprintf(stderr,"%s\n%s\n",mtid,patchlevel);
- X fprintf(stderr,"Send bug reports to davison@borland.com\n");
- X exit(0);
- X case 'z': /* destroy .thread on severe signal */
- X zap_thread = TRUE;
- X break;
- X default:
- X fprintf(stderr, "Unknown option: '%c'\n", **argv);
- X exit(1);
- X }
- X }
- X } else {
- X if (hierarchy_list) {
- X fprintf(stderr, "Specify the newsgroups in one comma-separated list.\n");
- X exit(1);
- X }
- X hierarchy_list = *argv;
- X }
- X }
- X
- X /* Initialize umask(), file_exp(), etc. */
- X mt_init();
- X
- X /* If this is a kill request, look for the daemon lock file. */
- X if (kill_mthreads) {
- X if (!mt_lock(DAEMON_LOCK, SIGTERM)) {
- X fprintf(stderr, "No mthreads daemon is running.\n");
- X wrap_it_up(1);
- X }
- X fprintf(stderr, "Killed mthreads daemon.\n");
- X wrap_it_up(0);
- X }
- X
- X /* See if an mthreads pass is already going. */
- X if (mt_lock(PASS_LOCK, 0) != 0 && !daemon_delay) {
- X fprintf(stderr, "mthreads is already running.\n");
- X wrap_it_up(1);
- X }
- X
- X#ifdef USE_SYSLOG
- X# ifdef LOG_DAEMON
- X openlog("mthreads", LOG_PID, SYSLOG_PRIORITY);
- X# else
- X openlog("mthreads", LOG_PID);
- X# endif
- X#else
- X /* Open our log file */
- X filename = file_exp(MTLOG);
- X if ((fp_log = fopen(filename, "a")) == Nullfp) {
- X fprintf(stderr, "Unable to open `%s'.\n", filename);
- X wrap_it_up(1);
- X }
- X#endif
- X
- X#ifdef SIGHUP
- X if (sigset(SIGHUP, SIG_IGN) != SIG_IGN) {
- X sigset(SIGHUP, int_handler);
- X }
- X#endif
- X if (sigset(SIGINT, SIG_IGN) != SIG_IGN) {
- X sigset(SIGINT, int_handler);
- X }
- X#ifdef SIGQUIT
- X if (sigset(SIGQUIT, SIG_IGN) != SIG_IGN) {
- X sigset(SIGQUIT, int_handler);
- X }
- X#endif
- X sigset(SIGTERM, int_handler);
- X#ifdef SIGBUS
- X sigset(SIGBUS, int_handler);
- X#endif
- X sigset(SIGSEGV, int_handler);
- X#ifdef SIGTTIN
- X sigset(SIGTTIN, SIG_IGN);
- X sigset(SIGTTOU, SIG_IGN);
- X#endif
- X sigset(SIGALRM, SIG_IGN);
- X#ifdef USE_NNTP
- X sigset(SIGPIPE, int_handler);
- X#endif
- X
- X /* Ensure this machine has the right byte-order for the database */
- X filename = file_exp(DBINIT);
- X if ((fp_tmp = fopen(filename, FOPEN_RB)) == Nullfp
- X || fread((char*)&mt_bmap,1,sizeof (BMAP), fp_tmp) < sizeof (BMAP)-1) {
- X if (fp_tmp != Nullfp) {
- X fclose(fp_tmp);
- X }
- X write_db_init:
- X mybytemap(&mt_bmap);
- X if ((fp_tmp = fopen(filename, FOPEN_WB)) == Nullfp) {
- X log_entry("Unable to create file: `%s'.\n", filename);
- X wrap_it_up(1);
- X }
- X mt_bmap.version = DB_VERSION;
- X fwrite((char*)&mt_bmap, 1, sizeof (BMAP), fp_tmp);
- X fclose(fp_tmp);
- X } else {
- X int i;
- X
- X fclose(fp_tmp);
- X if (mt_bmap.version != DB_VERSION) {
- X if (mt_bmap.version == DB_VERSION-1) {
- X rebuild = TRUE;
- X log_entry("Upgrading database to version %d.\n", DB_VERSION);
- X goto write_db_init;
- X }
- X log_entry("** Database is not the right version (%d instead of %d) **\n",
- X mt_bmap.version, DB_VERSION);
- X wrap_it_up(1);
- X }
- X mybytemap(&my_bmap);
- X for (i = 0; i < sizeof (LONG); i++) {
- X if (my_bmap.l[i] != mt_bmap.l[i]
- X || (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i])) {
- X log_entry("\
- X** Byte-order conflict -- re-run from a compatible machine **\n\
- X\t\tor remove the current thread files, including db.init **\n");
- X wrap_it_up(1);
- X }
- X }
- X }
- X
- X initializing = FALSE;
- X
- X /* If we're not in daemon mode, run through once and quit. */
- X if (!daemon_delay) {
- X log_startup();
- X setbuf(stdout, Nullch);
- X extra_expire = (expire_time != 0);
- X makethreads();
- X } else {
- X cron_locking = FALSE;
- X if (mt_lock(DAEMON_LOCK, 0) != 0) {
- X fprintf(stderr, "An mthreads daemon is already running.\n");
- X wrap_it_up(1);
- X }
- X /* For daemon mode, we cut ourself off from anything tty-related and
- X ** run in the background (involves forks, but no knives).
- X */
- X close(0);
- X if (open("/dev/null", 2) != 0) {
- X fprintf(stderr, "unable to open /dev/null!\n");
- X wrap_it_up(1);
- X }
- X close(1);
- X close(2);
- X dup(0);
- X dup(0);
- X while ((pid = fork()) < 0) {
- X sleep(2);
- X }
- X if (pid) {
- X exit(0);
- X }
- X#ifdef TIOCNOTTY
- X if ((fd = open("/dev/tty", 1)) >= 0) {
- X ioctl(fd, TIOCNOTTY, (int*)0);
- X close(fd);
- X }
- X#else
- X (void) setpgrp();
- X while ((pid = fork()) < 0) {
- X sleep(2);
- X }
- X if (pid) {
- X exit(0);
- X }
- X#endif
- X /* Put our pid in the lock file for death detection */
- X if ((fp_tmp = fopen(file_exp(MTDLOCK), "w")) != Nullfp) {
- X fprintf(fp_tmp, "%ld\n", (long)getpid());
- X fclose(fp_tmp);
- X }
- X log_startup();
- X
- X sigset(SIGALRM, alarm_handler);
- X
- X /* Start timer -- first interval is shorter than all others */
- X alarm(TIMER_FIRST);
- X for (;;) {
- X pause(); /* let alarm go off */
- X alarm(0);
- X
- X#ifndef USE_SYSLOG
- X /* Re-open our log file, if needed */
- X if (!fp_log && !(fp_log = fopen(file_exp(MTLOG), "a"))) {
- X wrap_it_up(1);
- X }
- X#endif
- X#ifndef USE_NNTP
- X if (stat(file_exp(ACTIVE), &filestat) < 0) {
- X log_entry("Unable to stat active file -- quitting.\n");
- X wrap_it_up(1);
- X }
- X#endif
- X if (expire_time && time(Null(time_t*)) > expire_time) {
- X expire_time += 24L * 60 * 60;
- X extra_expire = TRUE;
- X }
- X#ifdef USE_NNTP
- X makethreads(); /* NNTP version always compares files */
- X#else
- X if (extra_expire || filestat.st_mtime != last_modified) {
- X last_modified = filestat.st_mtime;
- X if (!makethreads()) {
- X last_modified--;
- X }
- X }
- X#endif
- X alarm(daemon_delay);
- X#ifndef USE_SYSLOG
- X fclose(fp_log); /* close the log file while we sleep */
- X fp_log = Nullfp;
- X#endif
- X } /* for */
- X }/* if */
- X
- X wrap_it_up(0);
- X return 0; /* NOTREACHED */
- X}
- X
- XSignal_t
- Xalarm_handler(dummy)
- Xint dummy;
- X{
- X sigset(SIGALRM, alarm_handler);
- X}
- X
- XSignal_t
- Xint_handler(sig)
- Xint sig;
- X{
- X static int visits = 0;
- X int ret = 0;
- X
- X if (++visits > 4) {
- X wrap_it_up(1);
- X }
- X
- X#ifndef USE_SYSLOG
- X /* Re-open our log file, if needed */
- X if (fp_log || (fp_log = fopen(file_exp(MTLOG), "a")))
- X#endif
- X {
- X switch (sig) {
- X case SIGTERM:
- X#ifdef SIGHUP
- X case SIGHUP:
- X#endif
- X#ifdef SIGQUIT
- X case SIGQUIT:
- X#endif
- X log_entry("halt requested.\n");
- X zap_thread = 0;
- X break;
- X#ifdef USE_NNTP
- X case SIGPIPE:
- X log_entry("broken pipe -- trying to continue.\n");
- X sigset(SIGPIPE, int_handler);
- X return;
- X#endif
- X#ifdef SIGBUS
- X case SIGBUS:
- X#endif
- X case SIGSEGV:
- X log_error("** Severe signal: %d **\n", sig);
- X /* Destroy offending thread file if requested to do so. */
- X if (zap_thread) {
- X unlink(thread_name(line));
- X log_entry("Destroyed thread file for %s\n", line);
- X }
- X break;
- X default:
- X log_entry("interrupt %d received.\n", sig);
- X zap_thread = 0;
- X ret = 1;
- X break;
- X }
- X }
- X if (!daemon_delay) {
- X printf("Interrupt %d!\n", sig);
- X if (zap_thread) {
- X printf("Destroyed thread file for %s\n", line);
- X }
- X }
- X
- X /* If we're in the middle of writing the new active2 file, finish it. */
- X if (fp_active2w) {
- X if (*line2) {
- X if (index(line2, ' ')) {
- X fputs(line2, fp_active2w);
- X } else {
- X fprintf(fp_active2w, fmt_active2, line2, last2, first2, ch2);
- X }
- X }
- X for (pline = line_root; pline; pline = pline->link) {
- X fprintf(fp_active2w, fmt_active2,
- X pline->name, pline->last, pline->first, pline->type);
- X }
- X if (!eof_active2) {
- X while (fgets(line2, sizeof line2, fp_active2)) {
- X fputs(line2, fp_active2w);
- X }
- X }
- X log_stats();
- X }
- X wrap_it_up(ret);
- X}
- X
- Xvoid
- Xwrap_it_up(ret)
- Xint ret;
- X{
- X mt_unlock(locked);
- X (void) chdir(PRIVLIB); /* for *mon.out files, etc. */
- X exit(ret);
- X}
- X
- X/* Process the active file, creating/modifying the active2 file and
- X** creating/modifying the thread data files.
- X*/
- Xbool
- Xmakethreads()
- X{
- X register char *cp, *cp2;
- X char data_file_open;
- X bool update_successful, old_groups, touch_thread;
- X#ifdef USE_NNTP
- X int server_failure = 0;
- X#endif
- X
- X /* See if an mthreads pass is already going. */
- X if (!(locked & PASS_LOCK) && mt_lock(PASS_LOCK, 0) != 0) {
- X log_entry("unable to get a lock for this pass.\n");
- X return FALSE;
- X }
- X#ifdef USE_NNTP
- X if (!nntp_connect()) {
- X return FALSE;
- X }
- X nntp_command("LIST"); /* ask server for the active file */
- X if (nntp_check(FALSE) != NNTP_CLASS_OK) {
- X log_entry("Unable to get active file from server.\n");
- X nntp_close();
- X return FALSE;
- X }
- X if ((fp_active = fopen(file_exp(ACTIVE1), "w+")) == Nullfp) {
- X log_entry("Unable to write the active1 file -- quitting.\n");
- X wrap_it_up(1);
- X }
- X while (1) {
- X if (nntp_gets(line, sizeof line) < 0) {
- X log_entry("Server failed to send entire active file.\n");
- X fclose(fp_active);
- X nntp_close();
- X return FALSE;
- X }
- X if (*line == '.') {
- X break;
- X }
- X fputs(line, fp_active);
- X putc('\n', fp_active);
- X }
- X if (ferror(fp_active)) {
- X log_entry("Error writing to active1 file.\n");
- X fclose(fp_active);
- X nntp_close();
- X return FALSE;
- X }
- X fseek(fp_active, 0L, 0); /* rewind for read */
- X#else /* !USE_NNTP */
- X if ((fp_active = fopen(file_exp(ACTIVE), "r")) == Nullfp) {
- X log_entry("Unable to open the active file.\n");
- X wrap_it_up(1);
- X }
- X#endif
- X filename = file_exp(ACTIVE2);
- X if ((fp_active2w = fopen(filename, "r+")) == Nullfp) {
- X if ((fp_active2w = fopen(filename, "w")) == Nullfp) {
- X log_entry("Unable to open the active2 file for update.\n");
- X wrap_it_up(1);
- X }
- X /* Add existing groups to active.times file with ancient date. */
- X old_groups = TRUE;
- X } else {
- X old_groups = FALSE;
- X }
- X if ((fp_active2 = fopen(filename, "r")) == Nullfp) {
- X log_entry("Unable to open the active2 file.\n");
- X wrap_it_up(1);
- X }
- X if (extra_expire && log_verbosity) {
- X log_entry("Using enhanced expiration for this pass.\n");
- X }
- X
- X eof_active = eof_active2 = FALSE;
- X fp_tmp = Nullfp;
- X
- X /* Loop through entire active file. */
- X for (;;) {
- X if (eof_active || !fgets(line, sizeof line, fp_active)) {
- X if (eof_active2 && !line_root) {
- X break;
- X }
- X eof_active = TRUE;
- X ch = 'x';
- X } else {
- X cp = line + strlen(line) - 1;
- X if (*cp == '\n') {
- X *cp = '\0';
- X }
- X if (!(cp = index(line, ' '))) {
- X log_entry("** line in 'active' has no space: %s **\n", line);
- X continue;
- X }
- X *cp = '\0';
- X if (sscanf(cp+1, "%ld %ld %c", &last, &first, &ch) != 3) {
- X log_entry("** digits corrupted in 'active': %s %s **\n",
- X line, cp+1);
- X continue;
- X }
- X if (last < first - 1) {
- X log_entry("** bogus group values in 'active': %s %s **\n",
- X line, cp+1);
- X continue;
- X }
- X }
- X if (debug > 1 || log_verbosity > 3) {
- X log_entry("Processing %s:\n", line);
- X }
- X data_file_open = 0;
- X /* If we've allocated some lines in memory while searching for
- X ** newsgroups (they've scrambled the active file on us), check
- X ** them first.
- X */
- X last_line = Nullact;
- X for (pline = line_root; pline; pline = pline->link) {
- X if (eof_active || strEQ(line, pline->name)) {
- X strcpy(line2, pline->name);
- X free(pline->name);
- X first2 = pline->first;
- X last2 = pline->last;
- X ch2 = pline->type;
- X if (last_line) {
- X last_line->link = pline->link;
- X } else {
- X line_root = pline->link;
- X }
- X free(pline);
- X break;
- X }
- X last_line = pline;
- X }/* for */
- X touch_thread = FALSE;
- X
- X /* If not found yet, check the active2 file. */
- X if (!pline) {
- X for (;;) {
- X if (eof_active2 || !fgets(line2, sizeof line2, fp_active2)) {
- X /* At end of file, check if the thread data file exists.
- X ** If so, use its high/low values. Else, default to
- X ** some initial values.
- X */
- X eof_active2 = TRUE;
- X if (eof_active) {
- X break;
- X }
- X strcpy(line2, line);
- X if ((data_file_open = init_data(thread_name(line)))) {
- X last2 = total.last;
- X first2 = total.first;
- X ch2 = 'y';
- X } else {
- X total.first = first2 = first;
- X if (add_new && (!hierarchy_list
- X || ngmatch(hierarchy_list, line) == NG_MATCH)) {
- X total.last = last2 = first - 1;
- X ch2 = (ch == '=' ? 'x' : ch);
- X touch_thread = TRUE;
- X added_groups += (ch2 != 'x');
- X } else {
- X total.last = last2 = last;
- X ch2 = (ch == '=' ? 'X' : toupper(ch));
- X }
- X }
- X data_file_open++; /* (1 == empty, 2 == open) */
- X#ifdef ACTIVE_TIMES
- X /* Found a new group -- see if we need to log it. */
- X if (acttimes_flag) {
- X if (!fp_tmp && !(fp_tmp = fopen(ACTIVE_TIMES, "a"))) {
- X log_entry("unable to append to %s.\n",ACTIVE_TIMES);
- X acttimes_flag = FALSE;
- X } else {
- X fprintf(fp_tmp, "%s %ld mthreads\n", line,
- X old_groups ? 30010440L : time(Null(time_t)));
- X }
- X }
- X#endif
- X break;
- X }
- X if (!(cp2 = index(line2, ' '))) {
- X log_entry("active2 line has no space: %s\n", line2);
- X continue;
- X }
- X *cp2 = '\0';
- X if (sscanf(cp2+1,"%ld %ld %c",&last2,&first2,&ch2) != 3) {
- X log_entry("active2 digits corrupted: %s %s\n",
- X line2, cp2+1);
- X continue;
- X }
- X /* Check if we're still in-sync */
- X if (eof_active || strEQ(line, line2)) {
- X break;
- X }
- X /* Nope, we've got to go looking for this line somewhere
- X ** down in the file. Save each non-matching line in memory
- X ** as we go.
- X */
- X pline = (ACTIVE_LINE*)safemalloc(sizeof (ACTIVE_LINE));
- X pline->name = savestr(line2);
- X pline->last = last2;
- X pline->first = first2;
- X pline->type = ch2;
- X pline->link = Nullact;
- X if (!last_line) {
- X line_root = pline;
- X } else {
- X last_line->link = pline;
- X }
- X *line2 = '\0';
- X last_line = pline;
- X }/* for */
- X if (eof_active && eof_active2) {
- X break;
- X }
- X }/* if !pline */
- X if (eof_active) {
- X strcpy(line, line2);
- X if (truncate_len < 0) {
- X truncate_len = ftell(fp_active2w);
- X }
- X }
- X if (rebuild) {
- X unlink(thread_name(line));
- X }
- X update_successful = FALSE;
- X if (hierarchy_list && !add_new) {
- X switch ((action = ngmatch(hierarchy_list, line))) {
- X case NG_MATCH: /* if unthreaded, add it */
- X if (ch2 < 'a' && ch != 'x' && ch != '=') {
- X total.last = last2 = first2 - 1;
- X touch_thread = TRUE;
- X added_groups++;
- X }
- X break;
- X case NG_SKIP: /* if threaded, remove it */
- X if (ch2 >= 'a') {
- X unlink(thread_name(line));
- X removed_groups++;
- X }
- X break;
- X }
- X } else {
- X action = (ch2 < 'a' ? NG_SKIP : NG_MATCH);
- X }
- X if (action == NG_DEFAULT || (debug && action == NG_SKIP)) {
- X dont_read_data(data_file_open); /* skip silently */
- X if (touch_thread) {
- X (void) write_data(thread_name(line));
- X }
- X } else if (ch == 'x' || ch == '=') {
- X if (!daemon_delay) { /* skip 'x'ed groups */
- X putchar('x');
- X }
- X ch = (action == NG_SKIP ? 'X' : 'x');
- X if ((ch2 >= 'a' && ch2 != 'x') || force_flag) {
- X /* Remove thread file if group is newly 'x'ed out */
- X unlink(thread_name(line));
- X }
- X update_successful = TRUE;
- X dont_read_data(data_file_open);
- X } else if (action == NG_SKIP) { /* skip excluded groups */
- X if (!daemon_delay) {
- X putchar('X');
- X }
- X ch = toupper(ch);
- X if (force_flag) {
- X unlink(thread_name(line));
- X }
- X update_successful = TRUE;
- X dont_read_data(data_file_open);
- X } else if (no_processing) {
- X if (!daemon_delay) {
- X putchar(',');
- X }
- X ch2 = ch;
- X dont_read_data(data_file_open);
- X if (touch_thread) {
- X (void) write_data(thread_name(line));
- X }
- X } else if (!force_flag && !extra_expire && !rebuild
- X && first == first2 && last == last2) {
- X /* We're up-to-date here. Skip it. */
- X if (!daemon_delay) {
- X putchar('.');
- X }
- X update_successful = TRUE;
- X dont_read_data(data_file_open);
- X if (touch_thread) {
- X (void) write_data(thread_name(line));
- X }
- X } else {
- X /* Looks like we need to process something. */
- X#ifdef USE_NNTP
- X if (!server_failure) {
- X sprintf(buf, "GROUP %s", line);
- X nntp_command(buf); /* go to next group */
- X if (nntp_check(FALSE) != NNTP_CLASS_OK) {
- X log_error("NNTP failure -- %s.\n", ser_line);
- X if (strnNE(ser_line, "400", 3)) {
- X nntp_close();
- X }
- X server_failure = 1;
- X }
- X }
- X if (server_failure) {
- X#else
- X strcpy(cp = buf, line2);
- X while ((cp = index(cp, '.'))) {
- X *cp = '/';
- X }
- X filename = file_exp(buf); /* relative to spool dir */
- X if (chdir(filename) < 0) {
- X if (errno != ENOENT) {
- X log_entry("Unable to chdir to `%s'.\n", filename);
- X }
- X#endif
- X if (!daemon_delay) {
- X putchar('*');
- X }
- X dont_read_data(data_file_open);
- X } else {
- X filename = thread_name(line);
- X /* Try to open the data file only if we didn't try it
- X ** in the name matching code above.
- X */
- X if (!data_file_open--) { /* (0 == haven't tried yet) */
- X if (!(data_file_open = init_data(filename))) {
- X total.last = first - 1;
- X total.first = first;
- X }
- X }
- X
- X if (data_file_open) { /* (0 == empty, 1 == open) */
- X if (!read_data()) { /* did read fail? */
- X if (debug) {
- X strcpy(buf, filename);
- X cp = rindex(buf, '/') + 1;
- X strcpy(cp, "bad.read");
- X rename(filename, buf);
- X }
- X data_file_open = init_data(Nullch);
- X total.last = first - 1;
- X total.first = first;
- X }
- X }
- X grevious_error = FALSE;
- X process_articles(first, last);
- X processed_groups++;
- X if (!added_count && !expired_count && !touch_thread
- X && last == last2) {
- X (void) write_data(Nullch);
- X if (!daemon_delay) {
- X putchar(':');
- X }
- X update_successful = TRUE;
- X } else {
- X int root_count = total.root;
- X strcpy(buf, filename);
- X cp = rindex(buf, '/') + 1;
- X strcpy(cp, NEW_THREAD); /* write data as .new */
- X if (write_data(buf) && !grevious_error) {
- X rename(buf, filename);
- X added_articles += added_count;
- X expired_articles += expired_count;
- X if (!daemon_delay) {
- X if (!root_count) {
- X putchar('-');
- X } else {
- X putchar('#');
- X }
- X }
- X update_successful = TRUE;
- X } else {
- X if (debug) {
- X cp = rindex(filename, '/') + 1;
- X strcpy(cp, "bad.write");
- X rename(buf, filename);
- X } else {
- X unlink(buf); /* blow away bad write */
- X if (grevious_error) {
- X unlink(filename); /* blow away the .thread, */
- X (void) init_data(Nullch); /* set totals */
- X total.last = first-1;
- X total.first = first;
- X (void) write_data(filename); /* write it null */
- X }
- X }
- X if (!daemon_delay) {
- X putchar('!');
- X }
- X }/* if */
- X }/* if */
- X }/* if */
- X }/* if */
- X /* Finally, update the active2 entry for this newsgroup. */
- X if (!eof_active) {
- X if (update_successful) {
- X fprintf(fp_active2w, fmt_active2, line, last, first, ch);
- X } else {
- X fprintf(fp_active2w, fmt_active2, line, last2, first2, ch2);
- X }
- X }
- X *line2 = '\0';
- X /* If we're not out of sync, keep active2 file flushed. */
- X if (!line_root) {
- X fflush(fp_active2w);
- X }
- X#ifdef CHECKLOAD
- X checkload();
- X#endif
- X }/* for */
- X
- X#ifdef USE_NNTP
- X if (!server_failure) {
- X nntp_close();
- X }
- X#endif
- X fclose(fp_active);
- X fclose(fp_active2);
- X
- X if (truncate_len >= 0) {
- X#ifdef HAS_FTRUNCATE
- X if (ftruncate(fileno(fp_active2w), truncate_len) == -1)
- X#else
- X#ifdef MVTRUNC
- X if (mvtrunc(file_exp(ACTIVE2), truncate_len) == -1)
- X#else
- X if (chsize(fileno(fp_active2w), truncate_len) == -1)
- X#endif
- X#endif
- X {
- X log_entry("Unable to truncate the active2 file.\n");
- X }
- X truncate_len = -1;
- X }
- X fclose(fp_active2w);
- X fp_active2w = Nullfp;
- X
- X if (fp_tmp) {
- X fclose(fp_tmp);
- X }
- X log_stats();
- X processed_groups = added_groups = removed_groups = 0;
- X added_articles = expired_articles = 0;
- X
- X extra_expire = FALSE;
- X rebuild = FALSE;
- X
- X mt_unlock(PASS_LOCK); /* remove single-pass lock */
- X
- X return TRUE;
- X}
- X
- X/*
- X** ngmatch - newsgroup name matching
- X**
- X** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
- X** and NG_DEFAULT if the group doesn't match at all.
- X**
- X** "all" in a pattern is a wildcard that matches exactly one word;
- X** it does not cross "." (NGDELIM) delimiters.
- X**
- X** This matching code was borrowed from C news.
- X*/
- X
- X#define ALL "all" /* word wildcard */
- X
- X#define NGNEG '!'
- X#define NGSEP ','
- X#define NGDELIM '.'
- X
- Xint
- Xngmatch(ngpat, grp)
- Xchar *ngpat, *grp;
- X{
- X register char *patp; /* point at current pattern */
- X register char *patcomma;
- X register int depth;
- X register int faildeepest = 0, hitdeepest = 0; /* in case no match */
- X register bool negation;
- X
- X for (patp = ngpat; patp != Nullch; patp = patcomma) {
- X negation = FALSE;
- X patcomma = index(patp, NGSEP);
- X if (patcomma != Nullch) {
- X *patcomma = '\0'; /* will be restored below */
- X }
- X if (*patp == NGNEG) {
- X ++patp;
- X negation = TRUE;
- X }
- X depth = onepatmatch(patp, grp); /* try 1 pattern, 1 group */
- X if (patcomma != Nullch) {
- X *patcomma++ = NGSEP; /* point after the comma */
- X }
- X if (depth == 0) { /* mis-match */
- X ; /* ignore it */
- X } else if (negation) {
- X /* record depth of deepest negated matched word */
- X if (depth > faildeepest) {
- X faildeepest = depth;
- X }
- X } else {
- X /* record depth of deepest plain matched word */
- X if (depth > hitdeepest) {
- X hitdeepest = depth;
- X }
- X }
- X }
- X if (hitdeepest > faildeepest) {
- X return NG_MATCH;
- X } else if (faildeepest) {
- X return NG_SKIP;
- X } else {
- X return NG_DEFAULT;
- X }
- X}
- X
- X/*
- X** Match a pattern against a group by looking at each word of pattern in turn.
- X**
- X** On a match, return the depth (roughly, ordinal number * k) of the rightmost
- X** word that matches. If group runs out first, the match fails; if pattern
- X** runs out first, it succeeds. On a failure, return zero.
- X*/
- Xint
- Xonepatmatch(patp, grp)
- Xchar *patp, *grp;
- X{
- X register char *rpatwd; /* used by word match (inner loop) */
- X register char *patdot, *grdot; /* point at dots after words */
- X register char *patwd, *grwd; /* point at current words */
- X register int depth = 0;
- X
- X for (patwd = patp, grwd = grp;
- X patwd != Nullch && grwd != Nullch;
- X patwd = patdot, grwd = grdot
- X ) {
- X register bool match = FALSE;
- X register int incr = 20;
- X
- X /* null-terminate words */
- X patdot = index(patwd, NGDELIM);
- X if (patdot != Nullch) {
- X *patdot = '\0'; /* will be restored below */
- X }
- X grdot = index(grwd, NGDELIM);
- X if (grdot != Nullch) {
- X *grdot = '\0'; /* will be restored below */
- X }
- X /*
- X * Match one word of pattern with one word of group.
- X * A pattern word of "all" matches any group word,
- X * but isn't worth as much.
- X */
- X#ifdef FAST_STRCMP
- X match = STREQ(patwd, grwd);
- X if (!match && STREQ(patwd, ALL)) {
- X match = TRUE;
- X --incr;
- X }
- X#else
- X for (rpatwd = patwd; *rpatwd == *grwd++;) {
- X if (*rpatwd++ == '\0') {
- X match = TRUE; /* literal match */
- X break;
- X }
- X }
- X if (!match) {
- X /* ugly special case match for "all" */
- X rpatwd = patwd;
- X if (*rpatwd++ == 'a' && *rpatwd++ == 'l'
- X && *rpatwd++ == 'l' && *rpatwd == '\0') {
- X match = TRUE;
- X --incr;
- X }
- X }
- X#endif /* FAST_STRCMP */
- X
- X if (patdot != Nullch) {
- X *patdot++ = NGDELIM; /* point after the dot */
- X }
- X if (grdot != Nullch) {
- X *grdot++ = NGDELIM;
- X }
- X if (!match) {
- X depth = 0; /* words differed - mismatch */
- X break;
- X }
- X depth += incr;
- X }
- X /* if group name ran out before pattern, then match fails */
- X if (grwd == Nullch && patwd != Nullch) {
- X depth = 0;
- X }
- X return depth;
- X}
- X
- X/* Put our startup options into the log file.
- X*/
- Xvoid
- Xlog_startup()
- X{
- X char tmpbuf[256];
- X
- X strcpy(tmpbuf, "Started mthreads");
- X if (cron_locking) {
- X strcat(tmpbuf, " -c");
- X }
- X if (debug) {
- X strcat(tmpbuf, " -D");
- X }
- X if (force_flag) {
- X strcat(tmpbuf, " -f");
- X }
- X if (daemon_delay) {
- X sprintf(tmpbuf + strlen(tmpbuf), " -d%d", daemon_delay / 60);
- X if (expire_time) {
- X struct tm *ts;
- X
- X ts = localtime(&expire_time);
- X sprintf(tmpbuf + strlen(tmpbuf), " -e%02d%02d",ts->tm_hour,ts->tm_min);
- X }
- X } else if (expire_time) {
- X strcat(tmpbuf, " -e");
- X }
- X if (slow_down) {
- X sprintf(tmpbuf + strlen(tmpbuf), " -s%d", slow_down);
- X }
- X if (no_processing) {
- X strcat(tmpbuf, " -n");
- X }
- X if (log_verbosity) {
- X sprintf(tmpbuf + strlen(tmpbuf), " -v%d", log_verbosity);
- X }
- X if (zap_thread) {
- X strcat(tmpbuf, " -z");
- X }
- X if (add_new) {
- X if (hierarchy_list) {
- X sprintf(tmpbuf + strlen(tmpbuf), " -a %s", hierarchy_list);
- X } else {
- X strcat(tmpbuf, " -a all");
- X }
- X } else if (hierarchy_list) {
- X sprintf(tmpbuf + strlen(tmpbuf), " %s (only)", hierarchy_list);
- X }
- X log_entry("%s\n", tmpbuf);
- X}
- X
- X/* Put our statistics into the log file.
- X*/
- Xvoid
- Xlog_stats()
- X{
- X sprintf(line, "Processed %d group%s: added %d article%s, expired %d.\n",
- X processed_groups, processed_groups == 1 ? nullstr : "s",
- X added_articles, added_articles == 1 ? nullstr : "s",
- X expired_articles);
- X
- X log_entry(line);
- X
- X if (!daemon_delay) {
- X putchar('\n');
- X fputs(line, stdout);
- X }
- X if (added_groups) {
- X sprintf(line, "Turned %d group%s on.\n", added_groups,
- X added_groups == 1 ? nullstr : "s");
- X log_entry(line);
- X if (!daemon_delay) {
- X fputs(line, stdout);
- X }
- X }
- X if (removed_groups) {
- X sprintf(line, "Turned %d group%s off.\n", removed_groups,
- X removed_groups == 1 ? nullstr : "s");
- X log_entry(line);
- X if (!daemon_delay) {
- X fputs(line, stdout);
- X }
- X }
- X}
- X/* Generate a log entry with timestamp.
- X*/
- X/*VARARGS1*/
- Xvoid
- Xlog_entry(fmt, arg1, arg2, arg3)
- Xchar *fmt;
- Xlong arg1, arg2, arg3;
- X{
- X#ifndef USE_SYSLOG
- X time_t now;
- X char *ctime();
- X#endif
- X
- X if (initializing) {
- X fprintf(stderr, fmt, arg1, arg2, arg3);
- X return;
- X }
- X
- X#ifndef USE_SYSLOG
- X (void) time(&now);
- X fprintf(fp_log, "%.12s%c", ctime(&now)+4, daemon_delay ? ' ' : '+');
- X fprintf(fp_log, fmt, arg1, arg2, arg3);
- X fflush(fp_log);
- X#else
- X syslog(LOG_INFO, fmt, arg1, arg2, arg3);
- X#endif
- X}
- X
- X/* Generate a log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
- X** and newsgroup name.
- X*/
- X/*VARARGS1*/
- Xvoid
- Xlog_error(fmt, arg1, arg2, arg3)
- Xchar *fmt;
- Xlong arg1;
- Xlong arg2;
- Xlong arg3;
- X{
- X char fmtbuf[256];
- X
- X sprintf(fmtbuf, "%s: %s", line, fmt);
- X#ifndef USE_SYSLOG
- X log_entry(fmtbuf, arg1, arg2, arg3);
- X#else
- X syslog(LOG_NOTICE, fmtbuf, arg1, arg2, arg3);
- X#endif
- X if (*fmt == '*') {
- X grevious_error = TRUE;
- X if (!daemon_delay) {
- X putchar('E');
- X }
- X }
- X else {
- X if (!daemon_delay) {
- X putchar('e');
- X }
- X }
- X}
- X
- X#ifdef MYCHSIZE
- X /* code courtesy of William Kucharski */
- X
- Xint
- Xchsize(fd, length)
- Xint fd; /* file descriptor */
- Xoff_t length; /* length to set file to */
- X{
- X extern long lseek();
- X struct flock fl;
- X
- X if (fstat(fd, &filestat) < 0) {
- X return -1;
- X }
- X if (filestat.st_size < length) { /* extend file length */
- X /* Write a 0 byte at the end. */
- X if (lseek(fd, length - 1, 0) < 0
- X || write(fd, "", 1) != 1) {
- X return -1;
- X }
- X } else {
- X /* Truncate file at length. */
- X fl.l_whence = 0;
- X fl.l_len = 0;
- X fl.l_start = length;
- X fl.l_type = F_WRLCK; /* write lock on file space */
- X
- X /*
- X ** This relies on the UNDOCUMENTED F_FREESP argument to
- X ** fcntl(2), which truncates the file so that it ends at the
- X ** position indicated by fl.l_start.
- X **
- X ** Will minor miracles never cease?
- X */
- X if (fcntl(fd, F_FREESP, &fl) < 0) {
- X return -1;
- X }
- X }
- X return 0;
- X}
- X#endif
- X
- X#ifdef MVTRUNC
- Xint
- Xmvtrunc(filename, truncate_len)
- Xchar *filename;
- Xlong truncate_len;
- X{
- X FILE *fp_in, *fp_out;
- X
- X sprintf(line, "%s.new", filename);
- X if ((fp_out = fopen(line, "w")) == Nullfp) {
- X log_entry("Tried to create active2.new.\n");
- X return -1;
- X }
- X if ((fp_in = fopen(filename, "r")) == Nullfp) {
- X fclose(fp_out);
- X unlink(line);
- X log_entry("Tried to re-open the active2 file.\n");
- X return -1;
- X }
- X while (ftell(fp_out) < truncate_len) {
- X if (!fgets(buf, sizeof buf, fp_in)) {
- X break;
- X }
- X fputs(buf, fp_out);
- X }
- X sprintf(buf, "%s.old", filename);
- X rename(filename, buf);
- X rename(line, filename);
- X fclose(fp_in);
- X fclose(fp_out);
- X
- X return 0;
- X}
- X#endif
- X
- X#ifndef HAS_RENAME
- Xint
- Xrename(old, new)
- Xchar *old, *new;
- X{
- X struct stat st;
- X
- X if (stat(old, &st) == -1) {
- X return -1;
- X }
- X if (unlink(new) == -1 && errno != ENOENT) {
- X return -1;
- X }
- X if (link(old, new) == -1) {
- X return -1;
- X }
- X if (unlink(old) == -1) {
- X int e = errno;
- X (void) unlink(new);
- X errno = e;
- X return -1;
- X }
- X return 0;
- X}
- X#endif /* HAS_RENAME */
- X
- X#ifdef CHECKLOAD
- X
- X#define FREQCHECK 300
- X#define SLPLOAD 300
- X#define UPTIME "/usr/ucb/uptime"
- X#define TOOHIGH 5
- X
- Xcheckload()
- X{
- X static long lastcheck = 0;
- X long time(), i;
- X FILE *pp;
- X char buf[BUFSIZ];
- X register char *cp;
- X char *strrchr();
- X
- X i = time(Null(time_t*));
- X if ((i - lastcheck) < FREQCHECK) {
- X return;
- X }
- X lastcheck = i;
- X
- Xagain:
- X if ((pp = popen(UPTIME, "r")) == NULL) {
- X return;
- X }
- X if (fgets(buf, BUFSIZ, pp) == NULL) {
- X pclose(pp);
- X return;
- X }
- X pclose(pp);
- X if ((cp = strrchr(buf, ':')) == NULL) {
- X return;
- X }
- X if (atoi(cp + 2) >= TOOHIGH) {
- X sleep(SLPLOAD);
- X goto again;
- X } else {
- X return;
- X }
- X}
- X#endif
- END_OF_FILE
- if test 33313 -ne `wc -c <'mthreads.c'`; then
- echo shar: \"'mthreads.c'\" unpacked with wrong size!
- fi
- # end of 'mthreads.c'
- fi
- if test -f 'parsedate.y' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'parsedate.y'\"
- else
- echo shar: Extracting \"'parsedate.y'\" \(21261 characters\)
- sed "s/^X//" >'parsedate.y' <<'END_OF_FILE'
- X%{
- X/* $Revision: 1.12 $
- X**
- X** Originally written by Steven M. Bellovin <smb@research.att.com> while
- X** at the University of North Carolina at Chapel Hill. Later tweaked by
- X** a couple of people on Usenet. Completely overhauled by Rich $alz
- X** <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
- X** Further revised (removed obsolete constructs and cleaned up timezone
- X** names) in August, 1991, by Rich. Paul Eggert <eggert@twinsun.com>
- X** helped in September, 1992.
- X**
- X** This grammar has six shift/reduce conflicts.
- X**
- X** This code is in the public domain and has no copyright.
- X*/
- X/* SUPPRESS 530 *//* Empty body for statement */
- X/* SUPPRESS 593 on yyerrlab *//* Label was not used */
- X/* SUPPRESS 593 on yynewstate *//* Label was not used */
- X/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
- X#include <stdio.h>
- X#include <sys/types.h>
- X#include <ctype.h>
- X#include "config.h"
- X#include <time.h>
- X
- X#define yyparse date_parse
- X#define yylex date_lex
- X#define yyerror date_error
- X
- X
- X /* See the LeapYears table in Convert. */
- X#define EPOCH 1970
- X#define END_OF_TIME 2038
- X /* Constants for general time calculations. */
- X#define DST_OFFSET 1
- X#define SECSPERDAY (24L * 60L * 60L)
- X /* Readability for TABLE stuff. */
- X#define HOUR(x) (x * 60)
- X
- X#define LPAREN '('
- X#define RPAREN ')'
- X#define IS7BIT(x) ((unsigned int)(x) < 0200)
- X
- X#define SIZEOF(array) ((int)(sizeof array / sizeof array[0]))
- X#define ENDOF(array) (&array[SIZEOF(array)])
- X
- X
- X/*
- X** An entry in the lexical lookup table.
- X*/
- Xtypedef struct _TABLE {
- X char *name;
- X int type;
- X time_t value;
- X} TABLE;
- X
- X/*
- X** Daylight-savings mode: on, off, or not yet known.
- X*/
- Xtypedef enum _DSTMODE {
- X DSTon, DSToff, DSTmaybe
- X} DSTMODE;
- X
- X/*
- X** Meridian: am, pm, or 24-hour style.
- X*/
- Xtypedef enum _MERIDIAN {
- X MERam, MERpm, MER24
- X} MERIDIAN;
- X
- X
- X/*
- X** Global variables. We could get rid of most of them by using a yacc
- X** union, but this is more efficient. (This routine predates the
- X** yacc %union construct.)
- X*/
- Xstatic char *yyInput;
- Xstatic DSTMODE yyDSTmode;
- Xstatic int yyHaveDate;
- Xstatic int yyHaveRel;
- Xstatic int yyHaveTime;
- Xstatic time_t yyTimezone;
- Xstatic time_t yyDay;
- Xstatic time_t yyHour;
- Xstatic time_t yyMinutes;
- Xstatic time_t yyMonth;
- Xstatic time_t yySeconds;
- Xstatic time_t yyYear;
- Xstatic MERIDIAN yyMeridian;
- Xstatic time_t yyRelMonth;
- Xstatic time_t yyRelSeconds;
- X
- X
- Xextern struct tm *localtime();
- X
- Xstatic void date_error();
- X%}
- X
- X%union {
- X time_t Number;
- X enum _MERIDIAN Meridian;
- X}
- X
- X%token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
- X%token tUNUMBER tZONE
- X
- X%type <Number> tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
- X%type <Number> tSNUMBER tUNUMBER tZONE numzone zone
- X%type <Meridian> tMERIDIAN o_merid
- X
- X%%
- X
- Xspec : /* NULL */
- X | spec item
- X ;
- X
- Xitem : time {
- X yyHaveTime++;
- X#ifdef lint
- X /* I am compulsive about lint natterings... */
- X if (yyHaveTime == -1) {
- X YYERROR;
- X }
- X#endif /* lint */
- X }
- X | time zone {
- X yyHaveTime++;
- X yyTimezone = $2;
- X }
- X | date {
- X yyHaveDate++;
- X }
- X | rel {
- X yyHaveRel = 1;
- X }
- X ;
- X
- Xtime : tUNUMBER o_merid {
- X if ($1 < 100) {
- X yyHour = $1;
- X yyMinutes = 0;
- X }
- X else {
- X yyHour = $1 / 100;
- X yyMinutes = $1 % 100;
- X }
- X yySeconds = 0;
- X yyMeridian = $2;
- X }
- X | tUNUMBER ':' tUNUMBER o_merid {
- X yyHour = $1;
- X yyMinutes = $3;
- X yySeconds = 0;
- X yyMeridian = $4;
- X }
- X | tUNUMBER ':' tUNUMBER numzone {
- X yyHour = $1;
- X yyMinutes = $3;
- X yyTimezone = $4;
- X yyMeridian = MER24;
- X yyDSTmode = DSToff;
- X }
- X | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
- X yyHour = $1;
- X yyMinutes = $3;
- X yySeconds = $5;
- X yyMeridian = $6;
- X }
- X | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
- X yyHour = $1;
- X yyMinutes = $3;
- X yySeconds = $5;
- X yyTimezone = $6;
- X yyMeridian = MER24;
- X yyDSTmode = DSToff;
- X }
- X ;
- X
- Xzone : tZONE {
- X $$ = $1;
- X yyDSTmode = DSToff;
- X }
- X | tDAYZONE {
- X $$ = $1;
- X yyDSTmode = DSTon;
- X }
- X | tZONE numzone {
- X /* Only allow "GMT+300" and "GMT-0800" */
- X if ($1 != 0) {
- X YYABORT;
- X }
- X $$ = $2;
- X yyDSTmode = DSToff;
- X }
- X | numzone {
- X $$ = $1;
- X yyDSTmode = DSToff;
- X }
- X ;
- X
- Xnumzone : tSNUMBER {
- X int i;
- X
- X /* Unix and GMT and numeric timezones -- a little confusing. */
- X if ($1 < 0) {
- X /* Don't work with negative modulus. */
- X $1 = -$1;
- X if ($1 > 9999 || (i = $1 % 100) >= 60) {
- X YYABORT;
- X }
- X $$ = ($1 / 100) * 60 + i;
- X }
- X else {
- X if ($1 > 9999 || (i = $1 % 100) >= 60) {
- X YYABORT;
- X }
- X $$ = -(($1 / 100) * 60 + i);
- X }
- X }
- X ;
- X
- Xdate : tUNUMBER '/' tUNUMBER {
- X yyMonth = $1;
- X yyDay = $3;
- X }
- X | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
- X if ($1 > 100) {
- X yyYear = $1;
- X yyMonth = $3;
- X yyDay = $5;
- X }
- X else {
- X yyMonth = $1;
- X yyDay = $3;
- X yyYear = $5;
- X }
- X }
- X | tMONTH tUNUMBER {
- X yyMonth = $1;
- X yyDay = $2;
- X }
- X | tMONTH tUNUMBER ',' tUNUMBER {
- X yyMonth = $1;
- X yyDay = $2;
- X yyYear = $4;
- X }
- X | tUNUMBER tMONTH {
- X yyDay = $1;
- X yyMonth = $2;
- X }
- X | tUNUMBER tMONTH tUNUMBER {
- X yyDay = $1;
- X yyMonth = $2;
- X yyYear = $3;
- X }
- X | tDAY ',' tUNUMBER tMONTH tUNUMBER {
- X yyDay = $3;
- X yyMonth = $4;
- X yyYear = $5;
- X }
- X ;
- X
- Xrel : tSNUMBER tSEC_UNIT {
- X yyRelSeconds += $1 * $2;
- X }
- X | tUNUMBER tSEC_UNIT {
- X yyRelSeconds += $1 * $2;
- X }
- X | tSNUMBER tMONTH_UNIT {
- X yyRelMonth += $1 * $2;
- X }
- X | tUNUMBER tMONTH_UNIT {
- X yyRelMonth += $1 * $2;
- X }
- X ;
- X
- Xo_merid : /* NULL */ {
- X $$ = MER24;
- X }
- X | tMERIDIAN {
- X $$ = $1;
- X }
- X ;
- X
- X%%
- X
- X/* Month and day table. */
- Xstatic TABLE MonthDayTable[] = {
- X { "january", tMONTH, 1 },
- X { "february", tMONTH, 2 },
- X { "march", tMONTH, 3 },
- X { "april", tMONTH, 4 },
- X { "may", tMONTH, 5 },
- X { "june", tMONTH, 6 },
- X { "july", tMONTH, 7 },
- X { "august", tMONTH, 8 },
- X { "september", tMONTH, 9 },
- X { "october", tMONTH, 10 },
- X { "november", tMONTH, 11 },
- X { "december", tMONTH, 12 },
- X /* The value of the day isn't used... */
- X { "sunday", tDAY, 0 },
- X { "monday", tDAY, 0 },
- X { "tuesday", tDAY, 0 },
- X { "wednesday", tDAY, 0 },
- X { "thursday", tDAY, 0 },
- X { "friday", tDAY, 0 },
- X { "saturday", tDAY, 0 },
- X};
- X
- X/* Time units table. */
- Xstatic TABLE UnitsTable[] = {
- X { "year", tMONTH_UNIT, 12 },
- X { "month", tMONTH_UNIT, 1 },
- X { "week", tSEC_UNIT, 7L * 24 * 60 * 60 },
- X { "day", tSEC_UNIT, 1L * 24 * 60 * 60 },
- X { "hour", tSEC_UNIT, 60 * 60 },
- X { "minute", tSEC_UNIT, 60 },
- X { "min", tSEC_UNIT, 60 },
- X { "second", tSEC_UNIT, 1 },
- X { "sec", tSEC_UNIT, 1 },
- X};
- X
- X/* Timezone table. */
- Xstatic TABLE TimezoneTable[] = {
- X { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
- X { "ut", tZONE, HOUR( 0) }, /* Universal */
- X { "utc", tZONE, HOUR( 0) }, /* Universal Coordinated */
- X { "cut", tZONE, HOUR( 0) }, /* Coordinated Universal */
- X { "z", tZONE, HOUR( 0) }, /* Greenwich Mean */
- X { "wet", tZONE, HOUR( 0) }, /* Western European */
- X { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
- X { "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */
- X { "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */
- X { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
- X { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
- X { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
- X { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
- X { "cst", tZONE, HOUR( 6) }, /* Central Standard */
- X { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
- X { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
- X { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
- X { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
- X { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
- X { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
- X { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
- X { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */
- X { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */
- X { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
- X { "hast", tZONE, HOUR(10) }, /* Hawaii-Aleutian Standard */
- X { "hadt", tDAYZONE, HOUR(10) }, /* Hawaii-Aleutian Daylight */
- X { "ces", tDAYZONE, -HOUR(1) }, /* Central European Summer */
- X { "cest", tDAYZONE, -HOUR(1) }, /* Central European Summer */
- X { "mez", tZONE, -HOUR(1) }, /* Middle European */
- X { "mezt", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
- X { "cet", tZONE, -HOUR(1) }, /* Central European */
- X { "met", tZONE, -HOUR(1) }, /* Middle European */
- X { "eet", tZONE, -HOUR(2) }, /* Eastern Europe */
- X { "msk", tZONE, -HOUR(3) }, /* Moscow Winter */
- X { "msd", tDAYZONE, -HOUR(3) }, /* Moscow Summer */
- X { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
- X { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
- X { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
- X { "cct", tZONE, -HOUR(8) }, /* China Coast */
- X { "jst", tZONE, -HOUR(9) }, /* Japan Standard */
- X { "kst", tZONE, -HOUR(9) }, /* Korean Standard */
- X { "kdt", tZONE, -HOUR(9) }, /* Korean Daylight */
- X { "cast", tZONE, -(HOUR(9)+30) }, /* Central Australian Standard */
- X { "cadt", tDAYZONE, -(HOUR(9)+30) }, /* Central Australian Daylight */
- X { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
- X { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
- X { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
- X { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
- X
- X /* For completeness we include the following entries. */
- X#if 0
- X
- X /* Duplicate names. Either they conflict with a zone listed above
- X * (which is either more likely to be seen or just been in circulation
- X * longer), or they conflict with another zone in this section and
- X * we could not reasonably choose one over the other. */
- X { "fst", tZONE, HOUR( 2) }, /* Fernando De Noronha Standard */
- X { "fdt", tDAYZONE, HOUR( 2) }, /* Fernando De Noronha Daylight */
- X { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
- X { "est", tZONE, HOUR( 3) }, /* Eastern Standard (Brazil) */
- X { "edt", tDAYZONE, HOUR( 3) }, /* Eastern Daylight (Brazil) */
- X { "wst", tZONE, HOUR( 4) }, /* Western Standard (Brazil) */
- X { "wdt", tDAYZONE, HOUR( 4) }, /* Western Daylight (Brazil) */
- X { "cst", tZONE, HOUR( 5) }, /* Chile Standard */
- X { "cdt", tDAYZONE, HOUR( 5) }, /* Chile Daylight */
- X { "ast", tZONE, HOUR( 5) }, /* Acre Standard */
- X { "adt", tDAYZONE, HOUR( 5) }, /* Acre Daylight */
- X { "cst", tZONE, HOUR( 5) }, /* Cuba Standard */
- X { "cdt", tDAYZONE, HOUR( 5) }, /* Cuba Daylight */
- X { "est", tZONE, HOUR( 6) }, /* Easter Island Standard */
- X { "edt", tDAYZONE, HOUR( 6) }, /* Easter Island Daylight */
- X { "sst", tZONE, HOUR(11) }, /* Samoa Standard */
- X { "ist", tZONE, -HOUR(2) }, /* Israel Standard */
- X { "idt", tDAYZONE, -HOUR(2) }, /* Israel Daylight */
- X { "idt", tDAYZONE, -(HOUR(3)+30) }, /* Iran Daylight */
- X { "ist", tZONE, -(HOUR(3)+30) }, /* Iran Standard */
- X { "cst", tZONE, -HOUR(8) }, /* China Standard */
- X { "cdt", tDAYZONE, -HOUR(8) }, /* China Daylight */
- X { "sst", tZONE, -HOUR(8) }, /* Singapore Standard */
- X
- X /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
- X { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
- X { "wat", tZONE, -HOUR(1) }, /* West Africa */
- X { "at", tZONE, HOUR( 2) }, /* Azores */
- X { "gst", tZONE, -HOUR(10) }, /* Guam Standard */
- X { "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */
- X { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
- X { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
- X { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
- X { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
- X { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
- X { "fwt", tZONE, -HOUR(1) }, /* French Winter */
- X { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
- X { "bt", tZONE, -HOUR(3) }, /* Baghdad */
- X { "it", tZONE, -(HOUR(3)+30) }, /* Iran */
- X { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
- X { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
- X { "ist", tZONE, -(HOUR(5)+30) }, /* Indian Standard */
- X { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
- X { "nst", tZONE, -HOUR(7) }, /* North Sumatra */
- X { "sst", tZONE, -HOUR(7) }, /* South Sumatra */
- X { "jt", tZONE, -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
- X { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
- X { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
- X { "cat", tZONE, HOUR(10) }, /* -- expired 1967 */
- X { "nt", tZONE, HOUR(11) }, /* -- expired 1967 */
- X { "ahst", tZONE, HOUR(10) }, /* -- expired 1983 */
- X { "hdt", tDAYZONE, HOUR(10) }, /* -- expired 1986 */
- X#endif /* 0 */
- X};
- X
- X
- X/* ARGSUSED */
- Xstatic void
- Xdate_error(s)
- X char *s;
- X{
- X /* NOTREACHED */
- X}
- X
- X
- Xstatic time_t
- XToSeconds(Hours, Minutes, Seconds, Meridian)
- X time_t Hours;
- X time_t Minutes;
- X time_t Seconds;
- X MERIDIAN Meridian;
- X{
- X if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
- X return -1;
- X if (Meridian == MER24) {
- X if (Hours < 0 || Hours > 23)
- X return -1;
- X }
- X else {
- X if (Hours < 1 || Hours > 12)
- X return -1;
- X if (Hours == 12)
- X Hours = 0;
- X if (Meridian == MERpm)
- X Hours += 12;
- X }
- X return (Hours * 60L + Minutes) * 60L + Seconds;
- X}
- X
- X
- Xstatic time_t
- XConvert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, dst)
- X time_t Month;
- X time_t Day;
- X time_t Year;
- X time_t Hours;
- X time_t Minutes;
- X time_t Seconds;
- X MERIDIAN Meridian;
- X DSTMODE dst;
- X{
- X static int DaysNormal[13] = {
- X 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
- X };
- X static int DaysLeap[13] = {
- X 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
- X };
- X static int LeapYears[] = {
- X 1972, 1976, 1980, 1984, 1988, 1992, 1996,
- X 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
- X };
- X register int *yp;
- X register int *mp;
- X register time_t Julian;
- X register int i;
- X time_t tod;
- X
- X if (Year < 0)
- X Year = -Year;
- X if (Year < 100)
- X Year += 1900;
- X if (Year < EPOCH)
- X Year += 100;
- X for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
- X if (Year == *yp) {
- X mp = DaysLeap;
- X break;
- X }
- X if (Year < EPOCH || Year > END_OF_TIME
- X || Month < 1 || Month > 12
- X /* NOSTRICT *//* conversion from long may lose accuracy */
- X || Day < 1 || Day > mp[(int)Month])
- X return -1;
- X
- X Julian = Day - 1 + (Year - EPOCH) * 365;
- X for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
- X if (Year <= *yp)
- X break;
- X for (i = 1; i < Month; i++)
- X Julian += *++mp;
- X Julian *= SECSPERDAY;
- X Julian += yyTimezone * 60L;
- X if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
- X return -1;
- X Julian += tod;
- X tod = Julian;
- X if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
- X Julian -= DST_OFFSET * 60L * 60L;
- X return Julian;
- X}
- X
- X
- Xstatic time_t
- XDSTcorrect(Start, Future)
- X time_t Start;
- X time_t Future;
- X{
- X time_t StartDay;
- X time_t FutureDay;
- X
- X StartDay = (localtime(&Start)->tm_hour + 1) % 24;
- X FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
- X return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
- X}
- X
- X
- Xstatic time_t
- XRelativeMonth(Start, RelMonth)
- X time_t Start;
- X time_t RelMonth;
- X{
- X struct tm *tm;
- X time_t Month;
- X time_t Year;
- X
- X tm = localtime(&Start);
- X Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
- X Year = Month / 12;
- X Month = Month % 12 + 1;
- X return DSTcorrect(Start,
- X Convert(Month, (time_t)tm->tm_mday, Year,
- X (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
- X MER24, DSTmaybe));
- X}
- X
- X
- Xstatic int
- XLookupWord(buff, length)
- X char *buff;
- X register int length;
- X{
- X register char *p;
- X register char *q;
- X register TABLE *tp;
- X register int c;
- X
- X p = buff;
- X c = p[0];
- X
- X /* See if we have an abbreviation for a month. */
- X if (length == 3 || (length == 4 && p[3] == '.'))
- X for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
- X q = tp->name;
- X if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
- X yylval.Number = tp->value;
- X return tp->type;
- X }
- X }
- X else
- X for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
- X if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- X yylval.Number = tp->value;
- X return tp->type;
- X }
- X
- X /* Try for a timezone. */
- X for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
- X if (c == tp->name[0] && p[1] == tp->name[1]
- X && strcmp(p, tp->name) == 0) {
- X yylval.Number = tp->value;
- X return tp->type;
- X }
- X
- X /* Try the units table. */
- X for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
- X if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- X yylval.Number = tp->value;
- X return tp->type;
- X }
- X
- X /* Strip off any plural and try the units table again. */
- X if (--length > 0 && p[length] == 's') {
- X p[length] = '\0';
- X for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
- X if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
- X p[length] = 's';
- X yylval.Number = tp->value;
- X return tp->type;
- X }
- X p[length] = 's';
- X }
- X length++;
- X
- X /* Drop out any periods. */
- X for (p = buff, q = (char*)buff; *q; q++)
- X if (*q != '.')
- X *p++ = *q;
- X *p = '\0';
- X
- X /* Try the meridians. */
- X if (buff[1] == 'm' && buff[2] == '\0') {
- X if (buff[0] == 'a') {
- X yylval.Meridian = MERam;
- X return tMERIDIAN;
- X }
- X if (buff[0] == 'p') {
- X yylval.Meridian = MERpm;
- X return tMERIDIAN;
- X }
- X }
- X
- X /* If we saw any periods, try the timezones again. */
- X if (p - buff != length) {
- X c = buff[0];
- X for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
- X if (c == tp->name[0] && p[1] == tp->name[1]
- X && strcmp(p, tp->name) == 0) {
- X yylval.Number = tp->value;
- X return tp->type;
- X }
- X }
- X
- X /* Unknown word -- assume GMT timezone. */
- X yylval.Number = 0;
- X return tZONE;
- X}
- X
- X
- Xint
- Xdate_lex()
- X{
- X register char c;
- X register char *p;
- X char buff[20];
- X register int sign;
- X register int i;
- X register int nesting;
- X
- X for ( ; ; ) {
- X /* Get first character after the whitespace. */
- X for ( ; ; ) {
- X while (isspace(*yyInput))
- X yyInput++;
- X c = *yyInput;
- X
- X /* Ignore RFC 822 comments, typically time zone names. */
- X if (c != LPAREN)
- X break;
- X for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
- X if (c == LPAREN)
- X nesting++;
- X else if (!IS7BIT(c) || c == '\0' || c == '\r'
- X || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
- X /* Lexical error: bad comment. */
- X return '?';
- X yyInput++;
- X }
- X
- X /* A number? */
- X if (isdigit(c) || c == '-' || c == '+') {
- X if (c == '-' || c == '+') {
- X sign = c == '-' ? -1 : 1;
- X yyInput++;
- X if (!isdigit(*yyInput))
- X /* Skip the plus or minus sign. */
- X continue;
- X }
- X else
- X sign = 0;
- X for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
- X i = 10 * i + c - '0';
- X yyInput--;
- X yylval.Number = sign < 0 ? -i : i;
- X return sign ? tSNUMBER : tUNUMBER;
- X }
- X
- X /* A word? */
- X if (isalpha(c)) {
- X for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
- X if (p < &buff[sizeof buff - 1])
- X *p++ = isupper(c) ? tolower(c) : c;
- X *p = '\0';
- X yyInput--;
- X return LookupWord(buff, p - buff);
- X }
- X
- X return *yyInput++;
- X }
- X}
- X
- X
- Xtime_t
- Xparsedate(p)
- X char *p;
- X{
- X extern int date_parse();
- X time_t Start;
- X
- X yyInput = p;
- X
- X yyYear = 0;
- X yyMonth = 0;
- X yyDay = 0;
- X yyTimezone = 0;
- X yyDSTmode = DSTmaybe;
- X yyHour = 0;
- X yyMinutes = 0;
- X yySeconds = 0;
- X yyMeridian = MER24;
- X yyRelSeconds = 0;
- X yyRelMonth = 0;
- X yyHaveDate = 0;
- X yyHaveRel = 0;
- X yyHaveTime = 0;
- X
- X if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
- X return -1;
- X
- X if (yyHaveDate || yyHaveTime) {
- X Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
- X yyMeridian, yyDSTmode);
- X if (Start < 0)
- X return -1;
- X }
- X else
- X return -1;
- X
- X Start += yyRelSeconds;
- X if (yyRelMonth)
- X Start += RelativeMonth(Start, yyRelMonth);
- X
- X /* Have to do *something* with a legitimate -1 so it's distinguishable
- X * from the error return value. (Alternately could set errno on error.) */
- X return Start == -1 ? 0 : Start;
- X}
- X
- X
- X#ifdef TEST
- X
- X#if YYDEBUG
- Xextern int yydebug;
- X#endif /* YYDEBUG */
- X
- X/* ARGSUSED */
- Xint
- Xmain(ac, av)
- X int ac;
- X char *av[];
- X{
- X char buff[128];
- X time_t d;
- X
- X#if YYDEBUG
- X yydebug = 1;
- X#endif /* YYDEBUG */
- X
- X (void)printf("Enter date, or blank line to exit.\n\t> ");
- X for ( ; ; ) {
- X (void)printf("\t> ");
- X (void)fflush(stdout);
- X if (gets(buff) == NULL || buff[0] == '\n')
- X break;
- X#if YYDEBUG
- X if (strcmp(buff, "yydebug") == 0) {
- X yydebug = !yydebug;
- X printf("yydebug = %s\n", yydebug ? "on" : "off");
- X continue;
- X }
- X#endif /* YYDEBUG */
- X d = parsedate(buff, (TIMEINFO *)NULL);
- X if (d == -1)
- X (void)printf("Bad format - couldn't convert.\n");
- X else
- X (void)printf("%s", ctime(&d));
- X }
- X
- X exit(0);
- X /* NOTREACHED */
- X}
- X#endif /* TEST */
- END_OF_FILE
- if test 21261 -ne `wc -c <'parsedate.y'`; then
- echo shar: \"'parsedate.y'\" unpacked with wrong size!
- fi
- # end of 'parsedate.y'
- fi
- echo shar: End of archive 2 \(of 4\).
- cp /dev/null ark2isdone
- MISSING=""
- for I in 1 2 3 4 ; do
- if test ! -f ark${I}isdone ; then
- MISSING="${MISSING} ${I}"
- fi
- done
- if test "${MISSING}" = "" ; then
- echo You have unpacked all 4 archives.
- rm -f ark[1-9]isdone
- else
- echo You still need to unpack the following archives:
- echo " " ${MISSING}
- fi
- ## End of shell archive.
- exit 0
-