home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / unix / volume27 / mthreads / part02 < prev    next >
Encoding:
Text File  |  1993-11-20  |  83.9 KB  |  3,191 lines

  1. Newsgroups: comp.sources.unix
  2. From: davison@borland.com (Wayne Davison)
  3. Subject: v27i091: mthreads - netnews database generator/manager, V3.1, Part02/04
  4. References: <1.753873779.13611@gw.home.vix.com>
  5. Sender: unix-sources-moderator@gw.home.vix.com
  6. Approved: vixie@gw.home.vix.com
  7.  
  8. Submitted-By: davison@borland.com (Wayne Davison)
  9. Posting-Number: Volume 27, Issue 91
  10. Archive-Name: mthreads/part02
  11.  
  12. #! /bin/sh
  13. # This is a shell archive.  Remove anything before this line, then unpack
  14. # it by saving it into a file and typing "sh file".  To overwrite existing
  15. # files, type "sh file -c".  You can also feed this as standard input via
  16. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  17. # will see the following message at the end:
  18. #        "End of archive 2 (of 4)."
  19. # Contents:  mt-read.c mthreads.8 mthreads.c parsedate.y
  20. # Wrapped by vixie@gw.home.vix.com on Sun Nov 21 01:12:01 1993
  21. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  22. if test -f 'mt-read.c' -a "${1}" != "-c" ; then 
  23.   echo shar: Will not clobber existing file \"'mt-read.c'\"
  24. else
  25. echo shar: Extracting \"'mt-read.c'\" \(15314 characters\)
  26. sed "s/^X//" >'mt-read.c' <<'END_OF_FILE'
  27. X/* $Id: mt-read.c,v 3.0 1992/10/01 00:14:04 davison Trn $
  28. X*/
  29. X/* The authors make no claims as to the fitness or correctness of this software
  30. X * for any use whatsoever, and it is provided as is. Any use of this software
  31. X * is at the user's own risk. 
  32. X */
  33. X
  34. X#include "EXTERN.h"
  35. X#include "common.h"
  36. X#include "thread.h"
  37. X#include "mthreads.h"
  38. X
  39. Xextern long first;
  40. X
  41. Xstatic FILE *fp_in;
  42. X
  43. Xint read_authors _((void));
  44. Xint read_subjects _((void));
  45. Xint read_roots _((void));
  46. Xint read_articles _((void));
  47. Xint read_ids _((void));
  48. Xint read_item _((char **, MEM_SIZE));
  49. Xvoid tweak_roots _((void));
  50. X
  51. X/* Attempt to open the thread file.  If it's there, only grab the totals
  52. X** from the start of the file.  This should give them enough information
  53. X** to decide if they need to read the whole thing into memory.
  54. X*/
  55. Xint
  56. Xinit_data(filename)
  57. Xchar *filename;
  58. X{
  59. X    root_root = Null(ROOT*);
  60. X    author_root = Null(AUTHOR*);
  61. X    unk_domain.ids = Nullart;
  62. X    unk_domain.link = Null(DOMAIN*);
  63. X
  64. X    if (filename && (fp_in = fopen(filename, FOPEN_RB)) != Nullfp) {
  65. X#ifdef SETBUFFER
  66. X    setbuffer(fp_in, rwbuf, RWBUFSIZ);
  67. X#else
  68. X# ifdef SETVBUF
  69. X    setvbuf(fp_in, rwbuf, _IOFBF, RWBUFSIZ);
  70. X# endif
  71. X#endif
  72. X    if (fread((char*)&total,1,sizeof (TOTAL),fp_in) == sizeof (TOTAL)) {
  73. X        /* If the data looks ok, return success. */
  74. X        if (total.last - total.first >= 0 && total.author > 0
  75. X         && total.article > 0 && total.subject > 0 && total.domain > 0
  76. X         && total.subject <= total.article && total.author <= total.article
  77. X         && total.root <= total.article && total.domain <= total.article
  78. X         && total.string1 > 0 && total.string2 > 0) {
  79. X        return 1;
  80. X        }
  81. X    }
  82. X    bzero(&total, sizeof (TOTAL));
  83. X    total.first = first;
  84. X    total.last = first - 1;
  85. X    return 1;
  86. X    }
  87. X    bzero(&total, sizeof (TOTAL));
  88. X    return 0;
  89. X}
  90. X
  91. X/* They want everything.  Read in the packed information and transform it
  92. X** into a set of linked structures that is easily manipulated.
  93. X*/
  94. Xint
  95. Xread_data()
  96. X{
  97. X    /* If this is an empty thread file, simply return success. */
  98. X    if (!total.root) {
  99. X    fclose(fp_in);
  100. X    return 1;
  101. X    }
  102. X
  103. X    if (read_authors()
  104. X     && read_subjects()
  105. X     && read_roots()
  106. X     && read_articles()
  107. X     && read_ids())
  108. X    {
  109. X    tweak_roots();
  110. X    fclose(fp_in);
  111. X    return 1;
  112. X    }
  113. X    /* Something failed.  Safefree takes care of checking if some items
  114. X    ** were already freed.  Any partially-allocated structures were freed
  115. X    ** before we got here.  All other structures are cleaned up.
  116. X    */
  117. X    if (root_root) {
  118. X    register ROOT *root, *next_root;
  119. X    register SUBJECT *subj, *next_subj;
  120. X
  121. X    for (root = root_root; root; root = next_root) {
  122. X        for (subj = root->subjects; subj; subj = next_subj) {
  123. X        next_subj = subj->link;
  124. X        free(subj->str);
  125. X        free(subj);
  126. X        }
  127. X        next_root = root->link;
  128. X        free(root);
  129. X    }
  130. X    root_root = Null(ROOT*);
  131. X    }
  132. X    if (author_array) {
  133. X    register int count;
  134. X
  135. X    for (count = total.author; count--;) {
  136. X        free(author_array[count]->name);
  137. X        free(author_array[count]);
  138. X    }
  139. X    safefree(&author_array);
  140. X    author_root = Null(AUTHOR*);
  141. X    }
  142. X    if (article_array) {
  143. X    register int count;
  144. X
  145. X    for (count = total.article; count--;) {
  146. X        free(article_array[count]);
  147. X    }
  148. X    safefree(&article_array);
  149. X    }
  150. X    safefree(&strings);
  151. X    safefree(&subject_cnts);
  152. X    safefree(&subject_array);
  153. X    safefree(&author_cnts);
  154. X    safefree(&root_array);
  155. X    safefree(&ids);
  156. X    unk_domain.ids = Nullart;
  157. X    unk_domain.link = Null(DOMAIN*);
  158. X    fclose(fp_in);
  159. X    return 0;
  160. X}
  161. X
  162. X/* They don't want to read the data.  Close the file if we opened it.
  163. X*/
  164. Xvoid
  165. Xdont_read_data(open_flag)
  166. Xint open_flag;        /* 0 == not opened, 1 == open failed, 2 == open */
  167. X{
  168. X    if (open_flag == 2) {
  169. X    fclose(fp_in);
  170. X    bzero(&total, sizeof (TOTAL));
  171. X    }
  172. X}
  173. X
  174. X#define give_string_to(dest)    /* Comment for makedepend to     \
  175. X                ** ignore the backslash above */ \
  176. X{\
  177. X    register MEM_SIZE len = strlen(string_ptr) + 1;\
  178. X    dest = safemalloc(len);\
  179. X    bcopy(string_ptr, dest, (int)len);\
  180. X    string_ptr += len;\
  181. X}
  182. X
  183. Xchar *subject_strings, *string_end;
  184. X
  185. X/* The author information is an array of use-counts, followed by all the
  186. X** null-terminated strings crammed together.  The subject strings are read
  187. X** in at the same time, since they are appended to the end of the author
  188. X** strings.
  189. X*/
  190. Xint
  191. Xread_authors()
  192. X{
  193. X    register int count, author_tally;
  194. X    register char *string_ptr;
  195. X    register WORD *authp;
  196. X    register AUTHOR *author, *last_author, **author_ptr;
  197. X
  198. X    if (!read_item((char **)&author_cnts,
  199. X           (MEM_SIZE)(total.author * sizeof (WORD)))
  200. X     || !read_item((char **)&strings, total.string1)) {
  201. X    /* (Error already logged.) */
  202. X    return 0;
  203. X    }
  204. X
  205. X    string_ptr = strings;
  206. X    string_end = string_ptr + total.string1;
  207. X    if (string_end[-1] != '\0') {
  208. X    log_error("first string table is invalid.\n");
  209. X    return 0;
  210. X    }
  211. X
  212. X    /* We'll use this array to point each article at its proper author
  213. X    ** (packed values are saved as indexes).
  214. X    */
  215. X    author_array = (AUTHOR**)safemalloc(total.author * sizeof (AUTHOR*));
  216. X    author_ptr = author_array;
  217. X
  218. X    authp = author_cnts;
  219. X
  220. X    author_tally = 0;
  221. X#ifndef lint
  222. X    last_author = (AUTHOR*)&author_root;
  223. X#else
  224. X    last_author = Null(AUTHOR*);
  225. X#endif
  226. X    for (count = total.author; count; count--) {
  227. X    if (string_ptr >= string_end) {
  228. X        break;
  229. X    }
  230. X    *author_ptr++ = author = (AUTHOR*)safemalloc(sizeof (AUTHOR));
  231. X    last_author->link = author;
  232. X    give_string_to(author->name);
  233. X    author_tally += *authp;
  234. X    author->count = *authp++;
  235. X    last_author = author;
  236. X    }
  237. X    last_author->link = Null(AUTHOR*);
  238. X
  239. X    subject_strings = string_ptr;
  240. X
  241. X    safefree(&author_cnts);
  242. X
  243. X    if (count || author_tally > total.article) {
  244. X    log_error("author unpacking failed.\n");
  245. X    for (; count < total.author; count++) {
  246. X        free((*--author_ptr)->name);
  247. X        free(*author_ptr);
  248. X    }
  249. X    safefree(&author_array);
  250. X    return 0;
  251. X    }
  252. X    return 1;
  253. X}
  254. X
  255. X/* The subject values consist of the crammed-together null-terminated strings
  256. X** (already read in above) and the use-count array.  They were saved in the
  257. X** order that the roots require while being unpacked.
  258. X*/
  259. Xint
  260. Xread_subjects()
  261. X{
  262. X    if (!read_item((char **)&subject_cnts,
  263. X           (MEM_SIZE)(total.subject * sizeof (WORD)))) {
  264. X    /* (Error already logged.) */
  265. X    return 0;
  266. X    }
  267. X    return 1;
  268. X}
  269. X
  270. X/* Read in the packed root structures and recreate the linked list versions,
  271. X** processing each root's subjects as we go.  Defer interpretation of article
  272. X** offsets until we unpack the article structures.
  273. X*/
  274. Xint
  275. Xread_roots()
  276. X{
  277. X    register int count, subj_tally;
  278. X    register char *string_ptr;
  279. X    register WORD *subjp;
  280. X    ROOT *root, *last_root, **root_ptr;
  281. X    SUBJECT *subject, *last_subject, **subj_ptr;
  282. X    int ret;
  283. X
  284. X    /* Use this array when unpacking the article's subject offset. */
  285. X    subject_array = (SUBJECT**)safemalloc(total.subject * sizeof (SUBJECT*));
  286. X    subj_ptr = subject_array;
  287. X    /* And this array points the article's root offset at the right spot. */
  288. X    root_array = (ROOT**)safemalloc(total.root * sizeof (ROOT*));
  289. X    root_ptr = root_array;
  290. X
  291. X    subjp = subject_cnts;
  292. X    string_ptr = subject_strings;    /* string_end is already set */
  293. X
  294. X    subj_tally = 0;
  295. X#ifndef lint
  296. X    last_root = (ROOT*)&root_root;
  297. X#else
  298. X    last_root = Null(ROOT*);
  299. X#endif
  300. X    for (count = total.root; count--;) {
  301. X    ret = fread((char*)&p_root, 1, sizeof (PACKED_ROOT), fp_in);
  302. X    if (ret != sizeof (PACKED_ROOT)) {
  303. X        log_error("failed root read -- %d bytes instead of %d.\n",
  304. X        ret, sizeof (PACKED_ROOT));
  305. X        return 0;
  306. X    }
  307. X    if (p_root.articles < 0 || p_root.articles >= total.article
  308. X     || subj_ptr - subject_array + p_root.subject_cnt > total.subject) {
  309. X        log_error("root has invalid values.\n");
  310. X        return 0;
  311. X    }
  312. X    *root_ptr++ = root = (ROOT*)safemalloc(sizeof (ROOT));
  313. X    root->link = Null(ROOT*);
  314. X    root->articles = Nullart;
  315. X    root->seq = p_root.articles;
  316. X    root->root_num = p_root.root_num;
  317. X    root->thread_cnt = p_root.thread_cnt;
  318. X    root->subject_cnt = p_root.subject_cnt;
  319. X    last_root->link = root;
  320. X    last_root = root;
  321. X
  322. X#ifndef lint
  323. X    last_subject = (SUBJECT*)&root->subjects;
  324. X#else
  325. X    last_subject = Null(SUBJECT*);
  326. X#endif
  327. X    while (p_root.subject_cnt--) {
  328. X        if (string_ptr >= string_end) {
  329. X        log_error("error unpacking subject strings.\n");
  330. X        last_subject->link = Null(SUBJECT*);
  331. X        return 0;
  332. X        }
  333. X        *subj_ptr++ = subject = (SUBJECT*)safemalloc(sizeof (SUBJECT));
  334. X        last_subject->link = subject;
  335. X        give_string_to(subject->str);
  336. X        subj_tally += *subjp;
  337. X        subject->count = *subjp++;
  338. X        last_subject = subject;
  339. X    }
  340. X    last_subject->link = Null(SUBJECT*);
  341. X    }
  342. X    if (subj_ptr != subject_array + total.subject
  343. X     || subj_tally > total.article
  344. X     || string_ptr != string_end) {
  345. X    log_error("subject data is invalid.\n");
  346. X    return 0;
  347. X    }
  348. X    safefree(&subject_cnts);
  349. X    safefree(&strings);
  350. X
  351. X    return 1;
  352. X}
  353. X
  354. Xbool invalid_data;
  355. X
  356. X/* A simple routine that checks the validity of the article's subject value.
  357. X** A -1 means that it is NULL, otherwise it should be an offset into the
  358. X** subject array we just unpacked.
  359. X*/
  360. XSUBJECT *
  361. Xvalid_subject(num, art_num)
  362. XWORD num;
  363. Xlong art_num;
  364. X{
  365. X    if (num == -1) {
  366. X    return Null(SUBJECT*);
  367. X    }
  368. X    if (num < 0 || num >= total.subject) {
  369. X    log_error("invalid subject in thread file: %d [%ld]\n", num, art_num);
  370. X    invalid_data = TRUE;
  371. X    return Null(SUBJECT*);
  372. X    }
  373. X    return subject_array[num];
  374. X}
  375. X
  376. X/* Ditto for author checking. */
  377. XAUTHOR *
  378. Xvalid_author(num, art_num)
  379. XWORD num;
  380. Xlong art_num;
  381. X{
  382. X    if (num == -1) {
  383. X    return Null(AUTHOR*);
  384. X    }
  385. X    if (num < 0 || num >= total.author) {
  386. X    log_error("invalid author in thread file: %d [%ld]\n", num, art_num);
  387. X    invalid_data = TRUE;
  388. X    return Null(AUTHOR*);
  389. X    }
  390. X    return author_array[num];
  391. X}
  392. X
  393. X/* Our parent/sibling information is a relative offset in the article array.
  394. X** zero for none.  Child values are always found in the very next array
  395. X** element if child_cnt is non-zero.
  396. X*/
  397. XARTICLE *
  398. Xvalid_node(relative_offset, num)
  399. XWORD relative_offset;
  400. Xint num;
  401. X{
  402. X    if (!relative_offset) {
  403. X    return Nullart;
  404. X    }
  405. X    num += relative_offset;
  406. X    if (num < 0 || num >= total.article) {
  407. X    log_error("invalid node offset in thread file.\n");
  408. X    invalid_data = TRUE;
  409. X    return Nullart;
  410. X    }
  411. X    return article_array[num];
  412. X}
  413. X
  414. X/* Read the articles into their linked lists.  Point everything everywhere. */
  415. Xint
  416. Xread_articles()
  417. X{
  418. X    register int count;
  419. X    register ARTICLE *article, **article_ptr;
  420. X    int ret;
  421. X
  422. X    /* Build an array to interpret interlinkages of articles. */
  423. X    article_array = (ARTICLE**)safemalloc(total.article * sizeof (ARTICLE*));
  424. X    article_ptr = article_array;
  425. X
  426. X    /* Allocate all the structures up-front so that we can point to unread
  427. X    ** siblings as we go.
  428. X    */
  429. X    for (count = total.article; count--;) {
  430. X    *article_ptr++ = (ARTICLE*)safemalloc(sizeof (ARTICLE));
  431. X    }
  432. X    invalid_data = FALSE;
  433. X    article_ptr = article_array;
  434. X    for (count = 0; count < total.article; count++) {
  435. X    ret = fread((char*)&p_article, 1, sizeof (PACKED_ARTICLE), fp_in);
  436. X    if (ret != sizeof (PACKED_ARTICLE)) {
  437. X        log_error("failed article read -- %d bytes instead of %d.\n", ret, sizeof (PACKED_ARTICLE));
  438. X        return 0;
  439. X    }
  440. X
  441. X    article = *article_ptr++;
  442. X    article->num = p_article.num;
  443. X    article->date = p_article.date;
  444. X    article->subject = valid_subject(p_article.subject, p_article.num);
  445. X    article->author = valid_author(p_article.author, p_article.num);
  446. X    article->flags = p_article.flags;
  447. X    article->child_cnt = p_article.child_cnt;
  448. X    article->parent = valid_node(p_article.parent, count);
  449. X    article->children = valid_node(article->child_cnt ? 1 : 0, count);
  450. X    article->siblings = valid_node(p_article.siblings, count);
  451. X    if (p_article.root < 0 || p_article.root >= total.root) {
  452. X        log_error("invalid root offset in thread file.\n");
  453. X        return 0;
  454. X    }
  455. X    article->root = root_array[p_article.root];
  456. X    if (invalid_data) {
  457. X        /* (Error already logged.) */
  458. X        return 0;
  459. X    }
  460. X    }
  461. X
  462. X    /* We're done with most of the pointer arrays. */
  463. X    safefree(&root_array);
  464. X    safefree(&subject_array);
  465. X
  466. X    return 1;
  467. X}
  468. X
  469. X/* Read the message-id strings and attach them to each article.  The data
  470. X** format consists of the mushed-together null-terminated strings (a domain
  471. X** name followed by all its unique-id prefixes) and then the article offsets
  472. X** to which they belong.  The first domain name was omitted, as it is the
  473. X** ".unknown." domain for those truly weird message-id's without '@'s.
  474. X*/
  475. Xint
  476. Xread_ids()
  477. X{
  478. X    register DOMAIN *domain, *last;
  479. X    register ARTICLE *article;
  480. X    register char *string_ptr;
  481. X    register int i, count;
  482. X
  483. X    if (!read_item(&strings, total.string2)
  484. X     || !read_item((char **)&ids,
  485. X           (MEM_SIZE)((total.article + total.domain + 1)
  486. X                  * sizeof (WORD)))) {
  487. X    return 0;
  488. X    }
  489. X
  490. X    string_ptr = strings;
  491. X    string_end = string_ptr + total.string2;
  492. X
  493. X    if (string_end[-1] != '\0') {
  494. X    log_error("second string table is invalid.\n");
  495. X    return 0;
  496. X    }
  497. X
  498. X    last = &unk_domain;
  499. X    for (i = 0, count = total.domain + 1; count--; i++) {
  500. X    if (i) {
  501. X        if (string_ptr >= string_end) {
  502. X        log_error("error unpacking domain strings.\n");
  503. X          free_partial:
  504. X        last->link = Null(DOMAIN*);
  505. X        article = unk_domain.ids;
  506. X        while (article) {
  507. X            safefree(&article->id);
  508. X            article = article->id_link;
  509. X        }
  510. X        domain = unk_domain.link;
  511. X        while (domain) {
  512. X            free(domain->name);
  513. X            article = domain->ids;
  514. X            while (article) {
  515. X            safefree(&article->id);
  516. X            article = article->id_link;
  517. X            }
  518. X            last = domain;
  519. X            domain = domain->link;
  520. X            free(last);
  521. X        }
  522. X        return 0;
  523. X        }
  524. X        domain = (DOMAIN*)safemalloc(sizeof (DOMAIN));
  525. X        give_string_to(domain->name);
  526. X        last->link = domain;
  527. X    } else {
  528. X        domain = &unk_domain;
  529. X    }
  530. X    if (ids[i] == -1) {
  531. X        domain->ids = Nullart;
  532. X    } else {
  533. X        if (ids[i] < 0 || ids[i] >= total.article) {
  534. X          id_error:
  535. X        log_error("error in id array.\n");
  536. X        domain->ids = Nullart;
  537. X        goto free_partial;
  538. X        }
  539. X        article = article_array[ids[i]];
  540. X        domain->ids = article;
  541. X        for (;;) {
  542. X        if (string_ptr >= string_end) {
  543. X            log_error("error unpacking domain strings.\n");
  544. X            article->id = Nullch;
  545. X            article->id_link = Nullart;
  546. X            goto free_partial;
  547. X        }
  548. X        give_string_to(article->id);
  549. X        article->domain = domain;
  550. X        if (++i >= total.article + total.domain + !count) {
  551. X            log_error("overran id array unpacking domains.\n");
  552. X            article->id_link = Nullart;
  553. X            goto free_partial;
  554. X        }
  555. X        if (ids[i] != -1) {
  556. X            if (ids[i] < 0 || ids[i] >= total.article) {
  557. X            goto id_error;
  558. X            }
  559. X            article = article->id_link = article_array[ids[i]];
  560. X        } else {
  561. X            article->id_link = Nullart;
  562. X            break;
  563. X        }
  564. X        }
  565. X    }
  566. X    last = domain;
  567. X    }
  568. X    last->link = Null(DOMAIN*);
  569. X    safefree(&ids);
  570. X    safefree(&strings);
  571. X
  572. X    return 1;
  573. X}
  574. X
  575. X/* And finally, point all the roots at their root articles and get rid
  576. X** of anything left over that was used to aid our unpacking.
  577. X*/
  578. Xvoid
  579. Xtweak_roots()
  580. X{
  581. X    register ROOT *root;
  582. X
  583. X    for (root = root_root; root; root = root->link) {
  584. X    root->articles = article_array[root->seq];
  585. X    }
  586. X    safefree(&author_array);
  587. X    safefree(&article_array);
  588. X}
  589. X
  590. X/* A shorthand for reading a chunk of the file into a malloc'ed array.
  591. X*/
  592. Xint
  593. Xread_item(dest, len)
  594. Xchar **dest;
  595. XMEM_SIZE len;
  596. X{
  597. X    int ret;
  598. X
  599. X    *dest = safemalloc(len);
  600. X    ret = fread(*dest, 1, (int)len, fp_in);
  601. X    if (ret != len) {
  602. X    log_error("only read %ld bytes instead of %ld.\n",
  603. X        (long)ret, (long)len);
  604. X    free(*dest);
  605. X    *dest = Nullch;
  606. X    return 0;
  607. X    }
  608. X    return 1;
  609. X}
  610. END_OF_FILE
  611. if test 15314 -ne `wc -c <'mt-read.c'`; then
  612.     echo shar: \"'mt-read.c'\" unpacked with wrong size!
  613. fi
  614. # end of 'mt-read.c'
  615. fi
  616. if test -f 'mthreads.8' -a "${1}" != "-c" ; then 
  617.   echo shar: Will not clobber existing file \"'mthreads.8'\"
  618. else
  619. echo shar: Extracting \"'mthreads.8'\" \(10153 characters\)
  620. sed "s/^X//" >'mthreads.8' <<'END_OF_FILE'
  621. X.\" $Id: mthreads.8,v 3.0 1993/09/14 00:14:11 davison Trn $
  622. X.\" 
  623. X.de Sh
  624. X.br
  625. X.ne 5
  626. X.PP
  627. X\fB\\$1\fR
  628. X.PP
  629. X..
  630. X.de Sp
  631. X.if t .sp .5v
  632. X.if n .sp
  633. X..
  634. X.\" unbreakable dash.
  635. X.tr \(*W-|\(bv\*(Tr
  636. X.ie n \{\
  637. X.ds -- \(*W-
  638. X.if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
  639. X.if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
  640. X.ds L" ""
  641. X.ds R" ""
  642. X.ds L' '
  643. X.ds R' '
  644. X'br\}
  645. X.el\{\
  646. X.ds -- \(em\|
  647. X.tr \*(Tr
  648. X.ds L" ``
  649. X.ds R" ''
  650. X.ds L' `
  651. X.ds R' '
  652. X'br\}
  653. X.TH MTHREADS 8 LOCAL
  654. X.UC 6
  655. X.SH NAME
  656. Xmthreads - threaded database manager for trn
  657. X.SH SYNOPSIS
  658. X.B mthreads [-d[MM]] [-e[HHMM]] [-acDfknstvz] [hierarchy_list]
  659. X.SH DESCRIPTION
  660. X.I Mthreads
  661. Xmanages the thread files that are used by the
  662. X.IR trn (1)
  663. Xnewsreader.
  664. X\*(L"Thread files\*(R" are used to store information about the news
  665. Xarticles and how they are all related to one another.
  666. X.PP
  667. X.I Mthreads
  668. Xshould be run periodically to update the thread database as new news arrives.
  669. XIt can be run in single-pass mode (out of cron), in daemon mode, or in a
  670. Xcombination of the two.
  671. XA site that gets its news feed during the night may just want to run mthreads
  672. Xonce a day (trn will handle any local postings that show up between passes).
  673. XIf more processing is needed, either run mthreads
  674. Xmore often or run it in daemon mode.
  675. XIn daemon mode, a background process is forked off that wakes up every 10
  676. Xminutes (by default) to check if the active file has been updated.
  677. XWhen the mthreads daemon is sleeping between passes, it is possible
  678. Xto run an mthreads single pass.
  679. XThis is often useful if you wish to run an enhanced expire pass more than
  680. Xonce a day (see the \-c and \-e options).
  681. X.SH INSTALLATION
  682. X.I Mthreads
  683. Xis installed in the PRIVLIB directory chosen during configuration.
  684. XWhen it is run for the first time, it will automatically create a file called
  685. X.I active2
  686. Xin the same directory.
  687. XThis file is essentially a copy of the active file that keeps the newsgroup
  688. Xtotals from the last run in one place.
  689. XIt is also used to choose which groups are to be processed into thread files.
  690. XAll groups start out as \*(L"unthreaded\*(R" unless they are turned on with
  691. Xa command like:
  692. X.IP
  693. Xmthreads all
  694. X.PP
  695. Xwhich would create thread file for all the groups.
  696. XFor testing purposes it is a good idea to start out small with a command
  697. Xlike:
  698. X.IP
  699. Xmthreads news
  700. X.PP
  701. Xwhich would thread only the news hierarchy.
  702. XThread processing can be turned on or off for individual groups or entire
  703. Xhierarchies by specifying the groups in a syntax very similar to that used
  704. Xin the sys file.
  705. XFor example, to turn on all of soc and talk except for talk.politics, and
  706. Xto turn off news.lists, use the following command once:
  707. X.IP
  708. Xmthreads soc,talk,!talk.politics,!news.lists
  709. X.PP
  710. XIf mthreads complains that another mthreads process is already running,
  711. Xyou can use the \-c option to tell it to continue trying to lock instead
  712. Xof giving up.
  713. X.PP
  714. XOnce all the desired groups are turned on, the hierarchy list should be
  715. Xomitted to allow mthreads to process all enabled groups.
  716. XIt can be used, however, in conjunction with the \-a option to customize
  717. Xwhich new groups get turned on as they are created.
  718. X.SH LOGGING
  719. XAs mthreads executes some status information (including error messages) 
  720. Xis placed in
  721. Xthe file mt.log in the PRIVLIB directory, unless you chose to use SYSLOG.
  722. XThis file will grow without bounds, and should be scanned periodically for
  723. Xerrors, and trimmed in size when it grows too large.
  724. XSee the shell script
  725. X.I mt.check
  726. Xfor an mt.log maintainer that will send mail if it finds database errors.
  727. X.SH OPTIONS
  728. X.TP 5
  729. X.B \-a
  730. Xis used to automatically turn on thread processing for new news groups as
  731. Xthey are created.
  732. XWhen this option is specified, the hierarchy list is used to limit
  733. Xwhich new groups get enabled (omitting the hierarchy list is the same
  734. Xas specifying \*(L"all\*(R").
  735. XThe default without \-a is to leave new groups unthreaded.
  736. X.TP 5
  737. X.B \-c
  738. Xwill continue trying to lock the mthreads database for a single pass
  739. Xinstead of giving up.
  740. XThis is useful for running special commands out of cron while an mthreads
  741. Xdaemon is active.
  742. X.TP 5
  743. X.B \-D
  744. Xspecifies debug processing.
  745. XAny errors encountered reading a thread file will rename the offending
  746. Xfile to \*(L"bad.read\*(R".
  747. XAny errors detected while generating a new thread file will rename the
  748. Xfile to \*(L"bad.write\*(R".
  749. XIf more than one 'D' is specified, each group's name is output into
  750. Xthe log file before it is processed.
  751. X.TP 5
  752. X.B \-d
  753. Xis used to specify the daemon mode, where
  754. X.I mthreads
  755. Xforks a background task that periodically wakes up and checks for an updated
  756. Xactive file.
  757. XThe number of minutes to wait after the completion of the last pass can
  758. Xbe specified after the '-d' option (e.g. -d20), otherwise it will default to
  759. X10 minutes.
  760. X.TP 5
  761. X.B \-e
  762. Xtells
  763. X.I mthreads
  764. Xto run an enhanced expiration check on the database.
  765. XWithout this option, only articles below the minimum field in the active
  766. Xfile are expired.
  767. XWith this option, mthreads will periodically list all the article numbers
  768. Xto see which ones actually exist.
  769. XIn single-pass mode the
  770. X.B -e
  771. Xoption always affects the current pass \*(-- use it
  772. Xat lease once a day after expire has run.
  773. XIn daemon mode, the
  774. X.B -e
  775. Xoption will cause one pass a day to be the enhanced expire pass.
  776. XBy default, this is the first time mthreads wakes up after 12:30 am.
  777. XIf a different time is desired, it can be specified in the form HHMM 
  778. X(e.g. -e2359).
  779. X.TP 5
  780. X.B -f
  781. Xis used to force
  782. X.I mthreads
  783. Xto open each and every thread file to see which ones really need to be
  784. Xupdated, not just the ones that differ in the active/active2 comparison.
  785. XIt will also remove any extraneous thread files from unthreaded groups
  786. X(which should only occur if you manually change the active2 file).
  787. XThis option should only be used when manipulating the thread files in
  788. Xunorthodox ways.
  789. X.TP 5
  790. X.B -k
  791. Xcan be used to terminate the currently running mthreads daemon, just as if it
  792. Xhad received a terminate signal.
  793. XWhen this option is specified, no other activity is performed.
  794. X.TP 5
  795. X.B -n
  796. Xtells
  797. X.I mthreads
  798. Xthat no actual processing of thread files is to be performed.
  799. XThis can be used to just adjust which groups are enabled, without
  800. Xactually doing any of the processing right away.
  801. X.TP 5
  802. X.B -s<usec>
  803. Xtells mthreads to sleep for <usec> microseconds before processing each
  804. Xarticle.
  805. XThis is useful if your NNTP server cannot handle mthreads running at
  806. Xfull speed.
  807. XUsing
  808. X.B -s
  809. Xby itself will sleep for an entire second to be compatible with older
  810. Xversions of mthreads.
  811. X.TP 5
  812. X.B -t
  813. Xis used to make mthreads update the active.times file (as specified
  814. Xduring configuration) with new directory names as they are encountered.
  815. XDon't use this option if your news software maintains this file for
  816. Xyou (as C news and INN do).
  817. X.TP 5
  818. X.B -v
  819. Xselects additional levels of verbosity in the log file.
  820. XThe default (without -v) is to log mthread's startup, the totals for each
  821. Xpass, and major database errors.
  822. XAdd one
  823. X.B -v
  824. Xto get extra reference line problems logged into the file.
  825. XAdd a second and a third for even more (useless?) information.
  826. XA fourth will cause mthreads to output each group's name into the log file
  827. Xbefore it is processed.
  828. X.TP 5
  829. X.B -V
  830. Xdisplays mthreads' version number and exits.
  831. X.TP 5
  832. X.B -z
  833. Xtells mthreads to 'zap' any thread file it believes to be corrupt.
  834. XThis will allow the file to be regenerated from scratch on the next pass.
  835. X.TP 5
  836. X.B hierarchy_list
  837. XThe hierarchy list is used to turn thread processing on or off for the listed
  838. Xgroups while limiting itself to updating only the listed groups.
  839. XIf specified with the \-a option, however, it only limits which new groups
  840. Xget enabled.
  841. XThe groups are specified in a manner very similar to the news software's
  842. Xsys file:  \*(L"news\*(R" matches all groups in news; \*(L"!news\*(R" excludes
  843. Xall groups in news; \*(L"comp.all.ibm.pc,!comp.all.ibm.pc.all\*(L" matches both
  844. Xcomp.sys.ibm.pc and comp.binaries.ibm.pc, but not comp.binaries.ibm.pc.d.
  845. X.SH OUTPUT
  846. XWhen
  847. X.I mthreads
  848. Xis run in single-pass mode it generates a stream a status characters on
  849. Xstdout that present a visual display of what is happening.  If 
  850. Xsingle-pass mode is used for regular processing, this output can be
  851. Xredirected to /dev/null.
  852. X.Sp
  853. XThe output definitions:
  854. X.br
  855. X    \&'.' = group's entry is up to date
  856. X.br
  857. X    \&':' = group processed \*(-- no change
  858. X.br
  859. X    \&'#' = group processed
  860. X.br
  861. X    \&'-' = group processed \*(-- is now empty
  862. X.br
  863. X    \&'x' = group excluded in active
  864. X.br
  865. X    \&'X' = group excluded in active2
  866. X.br
  867. X    \&'*' = unable to access a group
  868. X.br
  869. X    \&'!' = write failed (bad news)
  870. X.br
  871. X    \&'e' = informational error
  872. X.br
  873. X    \&'E' = database-affecting error
  874. X.SH CONFIGURATION
  875. XDuring the configuration of
  876. X.IR mthreads ,
  877. Xa choice was made about where to place the thread data files.
  878. XThey either exist as a .thread file in each group's spool directory, or they
  879. Xare each a group.th file in a one-off directory structure on another drive.
  880. XSee the THREAD_DIR definition in config.h to review or change this definition.
  881. X.SH REBUILDING
  882. XIf the thread files are ever removed, also remove the file db.init in
  883. Xthe THREAD_DIR.
  884. XThis file contains the byte-order of the machine that generated the database,
  885. Xand needs to be removed to truly start from scratch.
  886. XAn easy way to get
  887. X.I mthreads
  888. Xto remove all the files except for db.init is to specify the command:
  889. X.IP
  890. Xmthreads !all
  891. X.PP
  892. XThis also turns off thread processing for all groups.
  893. X.SH "ERROR HANDLING"
  894. XIf the active2 file is removed or corrupted, it will
  895. Xbe automatically rebuilt in the normal course of operation.
  896. XThe record of which groups should be threaded will be lost, however.
  897. XMissing/corrupted thread files are automatically re-built.
  898. X.SH EXAMPLES
  899. XRecommended commands to run on a regular basis are:
  900. X.IP
  901. Xmthreads -dave0630
  902. X.PP
  903. Xto start up an mthreads daemon in verbose logging mode that automatically
  904. Xthreads new groups and performs an extended expire at 6:30 am, or:
  905. X.IP
  906. Xmthreads -e >/dev/null
  907. X.PP
  908. Xto run an mthreads single-pass with extended expire that leaves new groups
  909. Xunthreaded.
  910. X.SH FILES
  911. XNEWSLIB/active
  912. X.br
  913. XPRIVLIB/active2
  914. X.br
  915. XPRIVLIB/mt.log
  916. X.br
  917. XTHREAD_DIR/db.init
  918. X.br
  919. XPRIVLIB/LOCKmthreads
  920. X.br
  921. XPRIVLIB/LOCKmtdaemon
  922. X.br
  923. XLots of thread data files.
  924. X.SH AUTHOR
  925. XWayne Davison <davison@borland.com>
  926. END_OF_FILE
  927. if test 10153 -ne `wc -c <'mthreads.8'`; then
  928.     echo shar: \"'mthreads.8'\" unpacked with wrong size!
  929. fi
  930. # end of 'mthreads.8'
  931. fi
  932. if test -f 'mthreads.c' -a "${1}" != "-c" ; then 
  933.   echo shar: Will not clobber existing file \"'mthreads.c'\"
  934. else
  935. echo shar: Extracting \"'mthreads.c'\" \(33313 characters\)
  936. sed "s/^X//" >'mthreads.c' <<'END_OF_FILE'
  937. X/* mthreads.c -- for making and updating a discussion-thread database
  938. X**
  939. X** We use the active file to read the high/low counts for each newsgroup
  940. X** and compare them with the corresponding values in a file called active2
  941. X** (which we created to keep the database high/lows in one place).  If they
  942. X** don't match, we read/update/write the group's thread file and move on.
  943. X** If the active2 file is missing or corrupted, it will be repaired in the
  944. X** normal course of operation.
  945. X**
  946. X** Usage:  mthreads [-d[MM]] [-e[HHMM]] [-s[hsec]] [-aDfknv] [hierarchy_list]
  947. X*/
  948. X/* The authors make no claims as to the fitness or correctness of this software
  949. X * for any use whatsoever, and it is provided as is. Any use of this software
  950. X * is at the user's own risk. 
  951. X */
  952. X
  953. X#include "patchlevel.h"
  954. Xstatic char mtid[] = "@(#)$Id: mthreads.c,v 3.0 1993/10/01 00:14:06 davison Trn $";
  955. Xstatic char patchlevel[] = PATCHLEVEL;
  956. X
  957. X#include "EXTERN.h"
  958. X#include "common.h"
  959. X#include "thread.h"
  960. X#include "nntpclient.h"
  961. X#include "INTERN.h"
  962. X#include "mthreads.h"
  963. X
  964. X#if !defined(HAS_FTRUNCATE) && !defined(HAS_CHSIZE)
  965. X# ifdef F_FREESP
  966. X#  define MYCHSIZE
  967. X# else
  968. X#  define MVTRUNC
  969. X# endif
  970. X#endif
  971. X
  972. X#ifdef USE_SYSLOG
  973. X#include <syslog.h>
  974. X#else
  975. XFILE *fp_log;
  976. X#endif
  977. X
  978. XFILE *fp_tmp, *fp_active, *fp_active2, *fp_active2w = Nullfp;
  979. Xbool eof_active = FALSE, eof_active2 = FALSE;
  980. Xlong first, last, first2, last2;
  981. Xchar ch, ch2;
  982. X
  983. Xstruct stat filestat;
  984. X
  985. Xchar buf[LBUFLEN+1];
  986. X
  987. Xchar line[256];
  988. Xstatic char line2[256];
  989. X
  990. Xchar fmt_active2[] = "%s %010ld %07ld %c\n";
  991. X
  992. Xchar *filename;
  993. X
  994. Xtypedef struct _active_line {
  995. X    struct _active_line *link;
  996. X    char *name;
  997. X    long last;
  998. X    long first;
  999. X    char type;
  1000. X} ACTIVE_LINE;
  1001. X
  1002. X#define Nullact Null(ACTIVE_LINE*)
  1003. X
  1004. XACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
  1005. X
  1006. Xbool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
  1007. Xbool add_new = FALSE, rebuild = FALSE, zap_thread = FALSE;
  1008. Xbool acttimes_flag = FALSE, grevious_error;
  1009. Xbool initializing = TRUE;
  1010. Xint daemon_delay = 0, log_verbosity = 0, debug = 0, slow_down = 0;
  1011. Xlong expire_time = 0;
  1012. Xchar *hierarchy_list = NULL;
  1013. Xlong truncate_len = -1;
  1014. X
  1015. Xchar nullstr[1] = "";
  1016. X
  1017. Xextern int locked, cron_locking;
  1018. X
  1019. X#define TIMER_FIRST 1
  1020. X#define TIMER_DEFAULT (10 * 60)
  1021. X
  1022. Xint processed_groups = 0, added_groups = 0, removed_groups = 0;
  1023. Xint action;
  1024. X
  1025. X#define NG_DEFAULT    0
  1026. X#define NG_MATCH    1
  1027. X#define NG_SKIP        2
  1028. X
  1029. X#ifndef USE_NNTP
  1030. Xtime_t last_modified = 0;
  1031. X#endif
  1032. X
  1033. XSignal_t alarm_handler(), int_handler();
  1034. Xbool makethreads _((void));
  1035. Xvoid log_startup _((void));
  1036. Xvoid log_stats _((void));
  1037. X
  1038. Xint
  1039. Xmain(argc, argv)
  1040. Xint  argc;
  1041. Xchar *argv[];
  1042. X{
  1043. X#ifdef TIOCNOTTY
  1044. X    int fd;
  1045. X#endif
  1046. X    long pid;
  1047. X
  1048. X    while (--argc) {
  1049. X    if (**++argv == '-') {
  1050. X        while (*++*argv) {
  1051. X        switch (**argv) {
  1052. X        case 'a':        /* automatically thread new groups */
  1053. X            add_new = TRUE;
  1054. X            break;
  1055. X        case 'c':        /* continue trying to lock */
  1056. X            cron_locking = TRUE;
  1057. X            break;
  1058. X        case 'D':        /* run in debug mode */
  1059. X            debug++;
  1060. X            break;
  1061. X        case 'd':        /* run in daemon mode */
  1062. X            if (*++*argv <= '9' && **argv >= '0') {
  1063. X            daemon_delay = atoi(*argv) * 60;
  1064. X            while (*++*argv <= '9' && **argv >= '0') {
  1065. X                ;
  1066. X            }
  1067. X            } else {
  1068. X            daemon_delay = TIMER_DEFAULT;
  1069. X            }
  1070. X            --*argv;
  1071. X            break;
  1072. X        case 'e': {        /* enhanced expire processing */
  1073. X            struct tm *ts;
  1074. X            long desired;
  1075. X
  1076. X            (void) time(&expire_time);
  1077. X            ts = localtime(&expire_time);
  1078. X
  1079. X            if (*++*argv <= '9' && **argv >= '0') {
  1080. X            desired = atol(*argv);
  1081. X            if (desired/100 > 23 || desired%100 > 59) {
  1082. X                fprintf(stderr, "Illegal expire time: '%04ld'\n",
  1083. X                desired);
  1084. X                exit(1);
  1085. X            }
  1086. X            desired = (desired/100)*60 + desired%100;
  1087. X            while (*++*argv <= '9' && **argv >= '0') {
  1088. X                ;
  1089. X            }
  1090. X            } else {
  1091. X            desired = 30;            /* 0030 = 12:30am */
  1092. X            }
  1093. X            --*argv;
  1094. X            desired -= ts->tm_hour * 60 + ts->tm_min;
  1095. X            if (desired < 0) {
  1096. X            desired += 24 * 60;
  1097. X            }
  1098. X            expire_time += desired * 60 - ts->tm_sec;
  1099. X            break;
  1100. X         }
  1101. X        case 'f':        /* force each group to process */
  1102. X            force_flag = TRUE;
  1103. X            break;
  1104. X        case 'k':        /* kill running mthreads */
  1105. X            kill_mthreads = TRUE;
  1106. X            break;
  1107. X        case 'n':        /* don't process anything */
  1108. X            no_processing = TRUE;
  1109. X            break;
  1110. X        case 's':        /* sleep between articles */
  1111. X            if (*++*argv <= '9' && **argv >= '0') {
  1112. X            slow_down = atoi(*argv);
  1113. X            while (*++*argv <= '9' && **argv >= '0') {
  1114. X                ;
  1115. X            }
  1116. X            } else {
  1117. X            slow_down = 1L * 1000 * 1000;
  1118. X            }
  1119. X            --*argv;
  1120. X            break;
  1121. X        case 't':        /* maintain active.times file */
  1122. X#ifdef ACTIVE_TIMES
  1123. X            if (strEQ(ACTIVE_TIMES, "nntp")) {
  1124. X            fprintf(stderr, "Ignoring the -t option.\n");
  1125. X            } else {
  1126. X            acttimes_flag = TRUE;
  1127. X            }
  1128. X#else
  1129. X            fprintf(stderr, "Ignoring the -t option.\n");
  1130. X#endif
  1131. X            break;
  1132. X        case 'v':        /* get more verbose in the log file */
  1133. X            log_verbosity++;
  1134. X            break;
  1135. X        case 'V':
  1136. X            fprintf(stderr,"%s\n%s\n",mtid,patchlevel);
  1137. X            fprintf(stderr,"Send bug reports to davison@borland.com\n");
  1138. X            exit(0);
  1139. X        case 'z':        /* destroy .thread on severe signal */
  1140. X            zap_thread = TRUE;
  1141. X            break;
  1142. X        default:
  1143. X            fprintf(stderr, "Unknown option: '%c'\n", **argv);
  1144. X            exit(1);
  1145. X        }
  1146. X        }
  1147. X    } else {
  1148. X        if (hierarchy_list) {
  1149. X        fprintf(stderr, "Specify the newsgroups in one comma-separated list.\n");
  1150. X        exit(1);
  1151. X        }
  1152. X        hierarchy_list = *argv;
  1153. X    }
  1154. X    }
  1155. X
  1156. X    /* Initialize umask(), file_exp(), etc. */
  1157. X    mt_init();
  1158. X
  1159. X    /* If this is a kill request, look for the daemon lock file. */
  1160. X    if (kill_mthreads) {
  1161. X    if (!mt_lock(DAEMON_LOCK, SIGTERM)) {
  1162. X        fprintf(stderr, "No mthreads daemon is running.\n");
  1163. X        wrap_it_up(1);
  1164. X    }
  1165. X    fprintf(stderr, "Killed mthreads daemon.\n");
  1166. X    wrap_it_up(0);
  1167. X    }
  1168. X
  1169. X    /* See if an mthreads pass is already going. */
  1170. X    if (mt_lock(PASS_LOCK, 0) != 0 && !daemon_delay) {
  1171. X    fprintf(stderr, "mthreads is already running.\n");
  1172. X    wrap_it_up(1);
  1173. X    }
  1174. X
  1175. X#ifdef USE_SYSLOG
  1176. X# ifdef LOG_DAEMON
  1177. X    openlog("mthreads", LOG_PID, SYSLOG_PRIORITY);
  1178. X# else
  1179. X    openlog("mthreads", LOG_PID);
  1180. X# endif
  1181. X#else
  1182. X    /* Open our log file */
  1183. X    filename = file_exp(MTLOG);
  1184. X    if ((fp_log = fopen(filename, "a")) == Nullfp) {
  1185. X    fprintf(stderr, "Unable to open `%s'.\n", filename);
  1186. X    wrap_it_up(1);
  1187. X    }
  1188. X#endif
  1189. X
  1190. X#ifdef SIGHUP
  1191. X    if (sigset(SIGHUP, SIG_IGN) != SIG_IGN) {
  1192. X    sigset(SIGHUP, int_handler);
  1193. X    }
  1194. X#endif
  1195. X    if (sigset(SIGINT, SIG_IGN) != SIG_IGN) {
  1196. X    sigset(SIGINT, int_handler);
  1197. X    }
  1198. X#ifdef SIGQUIT
  1199. X    if (sigset(SIGQUIT, SIG_IGN) != SIG_IGN) {
  1200. X    sigset(SIGQUIT, int_handler);
  1201. X    }
  1202. X#endif
  1203. X    sigset(SIGTERM, int_handler);
  1204. X#ifdef SIGBUS
  1205. X    sigset(SIGBUS, int_handler);
  1206. X#endif
  1207. X    sigset(SIGSEGV, int_handler);
  1208. X#ifdef SIGTTIN
  1209. X    sigset(SIGTTIN, SIG_IGN);
  1210. X    sigset(SIGTTOU, SIG_IGN);
  1211. X#endif
  1212. X    sigset(SIGALRM, SIG_IGN);
  1213. X#ifdef USE_NNTP
  1214. X    sigset(SIGPIPE, int_handler);
  1215. X#endif
  1216. X
  1217. X    /* Ensure this machine has the right byte-order for the database */
  1218. X    filename = file_exp(DBINIT);
  1219. X    if ((fp_tmp = fopen(filename, FOPEN_RB)) == Nullfp
  1220. X     || fread((char*)&mt_bmap,1,sizeof (BMAP), fp_tmp) < sizeof (BMAP)-1) {
  1221. X    if (fp_tmp != Nullfp) {
  1222. X        fclose(fp_tmp);
  1223. X    }
  1224. X     write_db_init:
  1225. X    mybytemap(&mt_bmap);
  1226. X    if ((fp_tmp = fopen(filename, FOPEN_WB)) == Nullfp) {
  1227. X        log_entry("Unable to create file: `%s'.\n", filename);
  1228. X        wrap_it_up(1);
  1229. X    }
  1230. X    mt_bmap.version = DB_VERSION;
  1231. X    fwrite((char*)&mt_bmap, 1, sizeof (BMAP), fp_tmp);
  1232. X    fclose(fp_tmp);
  1233. X    } else {
  1234. X    int i;
  1235. X
  1236. X    fclose(fp_tmp);
  1237. X    if (mt_bmap.version != DB_VERSION) {
  1238. X        if (mt_bmap.version == DB_VERSION-1) {
  1239. X        rebuild = TRUE;
  1240. X        log_entry("Upgrading database to version %d.\n", DB_VERSION);
  1241. X        goto write_db_init;
  1242. X        }
  1243. X        log_entry("** Database is not the right version (%d instead of %d) **\n",
  1244. X        mt_bmap.version, DB_VERSION);
  1245. X        wrap_it_up(1);
  1246. X    }
  1247. X    mybytemap(&my_bmap);
  1248. X    for (i = 0; i < sizeof (LONG); i++) {
  1249. X        if (my_bmap.l[i] != mt_bmap.l[i]
  1250. X         || (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i])) {
  1251. X        log_entry("\
  1252. X** Byte-order conflict -- re-run from a compatible machine **\n\
  1253. X\t\tor remove the current thread files, including db.init **\n");
  1254. X        wrap_it_up(1);
  1255. X        }
  1256. X    }
  1257. X    }
  1258. X
  1259. X    initializing = FALSE;
  1260. X
  1261. X    /* If we're not in daemon mode, run through once and quit. */
  1262. X    if (!daemon_delay) {
  1263. X    log_startup();
  1264. X    setbuf(stdout, Nullch);
  1265. X    extra_expire = (expire_time != 0);
  1266. X    makethreads();
  1267. X    } else {
  1268. X    cron_locking = FALSE;
  1269. X    if (mt_lock(DAEMON_LOCK, 0) != 0) {
  1270. X        fprintf(stderr, "An mthreads daemon is already running.\n");
  1271. X        wrap_it_up(1);
  1272. X    }
  1273. X    /* For daemon mode, we cut ourself off from anything tty-related and
  1274. X    ** run in the background (involves forks, but no knives).
  1275. X    */
  1276. X    close(0);
  1277. X    if (open("/dev/null", 2) != 0) {
  1278. X        fprintf(stderr, "unable to open /dev/null!\n");
  1279. X        wrap_it_up(1);
  1280. X    }
  1281. X    close(1);
  1282. X    close(2);
  1283. X    dup(0);
  1284. X    dup(0);
  1285. X    while ((pid = fork()) < 0) {
  1286. X        sleep(2);
  1287. X    }
  1288. X    if (pid) {
  1289. X        exit(0);
  1290. X    }
  1291. X#ifdef TIOCNOTTY
  1292. X    if ((fd = open("/dev/tty", 1)) >= 0) {
  1293. X        ioctl(fd, TIOCNOTTY, (int*)0);
  1294. X        close(fd);
  1295. X    }
  1296. X#else
  1297. X    (void) setpgrp();
  1298. X    while ((pid = fork()) < 0) {
  1299. X        sleep(2);
  1300. X    }
  1301. X    if (pid) {
  1302. X        exit(0);
  1303. X    }
  1304. X#endif
  1305. X    /* Put our pid in the lock file for death detection */
  1306. X    if ((fp_tmp = fopen(file_exp(MTDLOCK), "w")) != Nullfp) {
  1307. X        fprintf(fp_tmp, "%ld\n", (long)getpid());
  1308. X        fclose(fp_tmp);
  1309. X    }
  1310. X    log_startup();
  1311. X
  1312. X    sigset(SIGALRM, alarm_handler);
  1313. X
  1314. X    /* Start timer -- first interval is shorter than all others */
  1315. X    alarm(TIMER_FIRST);
  1316. X    for (;;) {
  1317. X        pause();        /* let alarm go off */
  1318. X        alarm(0);
  1319. X
  1320. X#ifndef USE_SYSLOG
  1321. X        /* Re-open our log file, if needed */
  1322. X        if (!fp_log && !(fp_log = fopen(file_exp(MTLOG), "a"))) {
  1323. X        wrap_it_up(1);
  1324. X        }
  1325. X#endif
  1326. X#ifndef USE_NNTP
  1327. X        if (stat(file_exp(ACTIVE), &filestat) < 0) {
  1328. X        log_entry("Unable to stat active file -- quitting.\n");
  1329. X        wrap_it_up(1);
  1330. X        }
  1331. X#endif
  1332. X        if (expire_time && time(Null(time_t*)) > expire_time) {
  1333. X        expire_time += 24L * 60 * 60;
  1334. X        extra_expire = TRUE;
  1335. X        }
  1336. X#ifdef USE_NNTP
  1337. X        makethreads();    /* NNTP version always compares files */
  1338. X#else
  1339. X        if (extra_expire || filestat.st_mtime != last_modified) {
  1340. X        last_modified = filestat.st_mtime;
  1341. X        if (!makethreads()) {
  1342. X            last_modified--;
  1343. X        }
  1344. X        }
  1345. X#endif
  1346. X        alarm(daemon_delay);
  1347. X#ifndef USE_SYSLOG
  1348. X        fclose(fp_log);    /* close the log file while we sleep */
  1349. X        fp_log = Nullfp;
  1350. X#endif
  1351. X    } /* for */
  1352. X    }/* if */
  1353. X
  1354. X    wrap_it_up(0);
  1355. X    return 0;                /* NOTREACHED */
  1356. X}
  1357. X
  1358. XSignal_t
  1359. Xalarm_handler(dummy)
  1360. Xint dummy;
  1361. X{
  1362. X    sigset(SIGALRM, alarm_handler);
  1363. X}
  1364. X
  1365. XSignal_t
  1366. Xint_handler(sig)
  1367. Xint sig;
  1368. X{
  1369. X    static int visits = 0;
  1370. X    int ret = 0;
  1371. X
  1372. X    if (++visits > 4) {
  1373. X    wrap_it_up(1);
  1374. X    }
  1375. X
  1376. X#ifndef USE_SYSLOG
  1377. X    /* Re-open our log file, if needed */
  1378. X    if (fp_log || (fp_log = fopen(file_exp(MTLOG), "a")))
  1379. X#endif
  1380. X    {
  1381. X    switch (sig) {
  1382. X    case SIGTERM:
  1383. X#ifdef SIGHUP
  1384. X    case SIGHUP:
  1385. X#endif
  1386. X#ifdef SIGQUIT
  1387. X    case SIGQUIT:
  1388. X#endif
  1389. X        log_entry("halt requested.\n");
  1390. X        zap_thread = 0;
  1391. X        break;
  1392. X#ifdef USE_NNTP
  1393. X    case SIGPIPE:
  1394. X        log_entry("broken pipe -- trying to continue.\n");
  1395. X        sigset(SIGPIPE, int_handler);
  1396. X        return;
  1397. X#endif
  1398. X#ifdef SIGBUS
  1399. X        case SIGBUS:
  1400. X#endif
  1401. X        case SIGSEGV:
  1402. X        log_error("** Severe signal: %d **\n", sig);
  1403. X        /* Destroy offending thread file if requested to do so. */
  1404. X        if (zap_thread) {
  1405. X            unlink(thread_name(line));
  1406. X            log_entry("Destroyed thread file for %s\n", line);
  1407. X        }
  1408. X        break;
  1409. X    default:
  1410. X        log_entry("interrupt %d received.\n", sig);
  1411. X        zap_thread = 0;
  1412. X        ret = 1;
  1413. X        break;
  1414. X    }
  1415. X    }
  1416. X    if (!daemon_delay) {
  1417. X    printf("Interrupt %d!\n", sig);
  1418. X    if (zap_thread) {
  1419. X        printf("Destroyed thread file for %s\n", line);
  1420. X    }
  1421. X    }
  1422. X
  1423. X    /* If we're in the middle of writing the new active2 file, finish it. */
  1424. X    if (fp_active2w) {
  1425. X    if (*line2) {
  1426. X        if (index(line2, ' ')) {
  1427. X        fputs(line2, fp_active2w);
  1428. X        } else {
  1429. X        fprintf(fp_active2w, fmt_active2, line2, last2, first2, ch2);
  1430. X        }
  1431. X    }
  1432. X    for (pline = line_root; pline; pline = pline->link) {
  1433. X        fprintf(fp_active2w, fmt_active2,
  1434. X            pline->name, pline->last, pline->first, pline->type);
  1435. X    }
  1436. X    if (!eof_active2) {
  1437. X        while (fgets(line2, sizeof line2, fp_active2)) {
  1438. X        fputs(line2, fp_active2w);
  1439. X        }
  1440. X    }
  1441. X    log_stats();
  1442. X    }
  1443. X    wrap_it_up(ret);
  1444. X}
  1445. X
  1446. Xvoid
  1447. Xwrap_it_up(ret)
  1448. Xint ret;
  1449. X{
  1450. X    mt_unlock(locked);
  1451. X    (void) chdir(PRIVLIB);        /* for *mon.out files, etc. */
  1452. X    exit(ret);
  1453. X}
  1454. X
  1455. X/* Process the active file, creating/modifying the active2 file and
  1456. X** creating/modifying the thread data files.
  1457. X*/
  1458. Xbool
  1459. Xmakethreads()
  1460. X{
  1461. X    register char *cp, *cp2;
  1462. X    char data_file_open;
  1463. X    bool update_successful, old_groups, touch_thread;
  1464. X#ifdef USE_NNTP
  1465. X    int server_failure = 0;
  1466. X#endif
  1467. X
  1468. X    /* See if an mthreads pass is already going. */
  1469. X    if (!(locked & PASS_LOCK) && mt_lock(PASS_LOCK, 0) != 0) {
  1470. X    log_entry("unable to get a lock for this pass.\n");
  1471. X    return FALSE;
  1472. X    }
  1473. X#ifdef USE_NNTP
  1474. X    if (!nntp_connect()) {
  1475. X    return FALSE;
  1476. X    }
  1477. X    nntp_command("LIST");    /* ask server for the active file */
  1478. X    if (nntp_check(FALSE) != NNTP_CLASS_OK) {
  1479. X    log_entry("Unable to get active file from server.\n");
  1480. X    nntp_close();
  1481. X    return FALSE;
  1482. X    }
  1483. X    if ((fp_active = fopen(file_exp(ACTIVE1), "w+")) == Nullfp) {
  1484. X    log_entry("Unable to write the active1 file -- quitting.\n");
  1485. X    wrap_it_up(1);
  1486. X    }
  1487. X    while (1) {
  1488. X    if (nntp_gets(line, sizeof line) < 0) {
  1489. X        log_entry("Server failed to send entire active file.\n");
  1490. X        fclose(fp_active);
  1491. X        nntp_close();
  1492. X        return FALSE;
  1493. X    }
  1494. X    if (*line == '.') {
  1495. X        break;
  1496. X    }
  1497. X    fputs(line, fp_active);
  1498. X    putc('\n', fp_active);
  1499. X    }
  1500. X    if (ferror(fp_active)) {
  1501. X    log_entry("Error writing to active1 file.\n");
  1502. X    fclose(fp_active);
  1503. X    nntp_close();
  1504. X    return FALSE;
  1505. X    }
  1506. X    fseek(fp_active, 0L, 0);        /* rewind for read */
  1507. X#else /* !USE_NNTP */
  1508. X    if ((fp_active = fopen(file_exp(ACTIVE), "r")) == Nullfp) {
  1509. X    log_entry("Unable to open the active file.\n");
  1510. X    wrap_it_up(1);
  1511. X    }
  1512. X#endif
  1513. X    filename = file_exp(ACTIVE2);
  1514. X    if ((fp_active2w = fopen(filename, "r+")) == Nullfp) {
  1515. X    if ((fp_active2w = fopen(filename, "w")) == Nullfp) {
  1516. X        log_entry("Unable to open the active2 file for update.\n");
  1517. X        wrap_it_up(1);
  1518. X    }
  1519. X    /* Add existing groups to active.times file with ancient date. */
  1520. X    old_groups = TRUE;
  1521. X    } else {
  1522. X    old_groups = FALSE;
  1523. X    }
  1524. X    if ((fp_active2 = fopen(filename, "r")) == Nullfp) {
  1525. X    log_entry("Unable to open the active2 file.\n");
  1526. X    wrap_it_up(1);
  1527. X    }
  1528. X    if (extra_expire && log_verbosity) {
  1529. X    log_entry("Using enhanced expiration for this pass.\n");
  1530. X    }
  1531. X
  1532. X    eof_active = eof_active2 = FALSE;
  1533. X    fp_tmp = Nullfp;
  1534. X
  1535. X    /* Loop through entire active file. */
  1536. X    for (;;) {
  1537. X    if (eof_active || !fgets(line, sizeof line, fp_active)) {
  1538. X        if (eof_active2 && !line_root) {
  1539. X        break;
  1540. X        }
  1541. X        eof_active = TRUE;
  1542. X        ch = 'x';
  1543. X    } else {
  1544. X        cp = line + strlen(line) - 1;
  1545. X        if (*cp == '\n') {
  1546. X        *cp = '\0';
  1547. X        }
  1548. X        if (!(cp = index(line, ' '))) {
  1549. X        log_entry("** line in 'active' has no space: %s **\n", line);
  1550. X        continue;
  1551. X        }
  1552. X        *cp = '\0';
  1553. X        if (sscanf(cp+1, "%ld %ld %c", &last, &first, &ch) != 3) {
  1554. X        log_entry("** digits corrupted in 'active': %s %s **\n",
  1555. X            line, cp+1);
  1556. X        continue;
  1557. X        }
  1558. X        if (last < first - 1) {
  1559. X        log_entry("** bogus group values in 'active': %s %s **\n",
  1560. X            line, cp+1);
  1561. X        continue;
  1562. X        }
  1563. X    }
  1564. X    if (debug > 1 || log_verbosity > 3) {
  1565. X        log_entry("Processing %s:\n", line);
  1566. X    }
  1567. X    data_file_open = 0;
  1568. X    /* If we've allocated some lines in memory while searching for
  1569. X    ** newsgroups (they've scrambled the active file on us), check
  1570. X    ** them first.
  1571. X    */
  1572. X    last_line = Nullact;
  1573. X    for (pline = line_root; pline; pline = pline->link) {
  1574. X        if (eof_active || strEQ(line, pline->name)) {
  1575. X        strcpy(line2, pline->name);
  1576. X        free(pline->name);
  1577. X        first2 = pline->first;
  1578. X        last2 = pline->last;
  1579. X        ch2 = pline->type;
  1580. X        if (last_line) {
  1581. X            last_line->link = pline->link;
  1582. X        } else {
  1583. X            line_root = pline->link;
  1584. X        }
  1585. X        free(pline);
  1586. X        break;
  1587. X        }
  1588. X        last_line = pline;
  1589. X    }/* for */
  1590. X    touch_thread = FALSE;
  1591. X
  1592. X    /* If not found yet, check the active2 file. */
  1593. X    if (!pline) {
  1594. X        for (;;) {
  1595. X        if (eof_active2 || !fgets(line2, sizeof line2, fp_active2)) {
  1596. X            /* At end of file, check if the thread data file exists.
  1597. X            ** If so, use its high/low values.  Else, default to
  1598. X            ** some initial values.
  1599. X            */
  1600. X            eof_active2 = TRUE;
  1601. X            if (eof_active) {
  1602. X            break;
  1603. X            }
  1604. X            strcpy(line2, line);
  1605. X            if ((data_file_open = init_data(thread_name(line)))) {
  1606. X            last2 = total.last;
  1607. X            first2 = total.first;
  1608. X            ch2 = 'y';
  1609. X            } else {
  1610. X            total.first = first2 = first;
  1611. X            if (add_new && (!hierarchy_list
  1612. X              || ngmatch(hierarchy_list, line) == NG_MATCH)) {
  1613. X                total.last = last2 = first - 1;
  1614. X                ch2 = (ch == '=' ? 'x' : ch);
  1615. X                touch_thread = TRUE;
  1616. X                added_groups += (ch2 != 'x');
  1617. X            } else {
  1618. X                total.last = last2 = last;
  1619. X                ch2 = (ch == '=' ? 'X' : toupper(ch));
  1620. X            }
  1621. X            }
  1622. X            data_file_open++;        /* (1 == empty, 2 == open) */
  1623. X#ifdef ACTIVE_TIMES
  1624. X            /* Found a new group -- see if we need to log it. */
  1625. X            if (acttimes_flag) {
  1626. X            if (!fp_tmp && !(fp_tmp = fopen(ACTIVE_TIMES, "a"))) {
  1627. X                log_entry("unable to append to %s.\n",ACTIVE_TIMES);
  1628. X                acttimes_flag = FALSE;
  1629. X            } else {
  1630. X                fprintf(fp_tmp, "%s %ld mthreads\n", line,
  1631. X                old_groups ? 30010440L : time(Null(time_t)));
  1632. X            }
  1633. X            }
  1634. X#endif
  1635. X            break;
  1636. X        }
  1637. X        if (!(cp2 = index(line2, ' '))) {
  1638. X            log_entry("active2 line has no space: %s\n", line2);
  1639. X            continue;
  1640. X        }
  1641. X        *cp2 = '\0';
  1642. X        if (sscanf(cp2+1,"%ld %ld %c",&last2,&first2,&ch2) != 3) {
  1643. X            log_entry("active2 digits corrupted: %s %s\n",
  1644. X            line2, cp2+1);
  1645. X            continue;
  1646. X        }
  1647. X        /* Check if we're still in-sync */
  1648. X        if (eof_active || strEQ(line, line2)) {
  1649. X            break;
  1650. X        }
  1651. X        /* Nope, we've got to go looking for this line somewhere
  1652. X        ** down in the file.  Save each non-matching line in memory
  1653. X        ** as we go.
  1654. X        */
  1655. X        pline = (ACTIVE_LINE*)safemalloc(sizeof (ACTIVE_LINE));
  1656. X        pline->name = savestr(line2);
  1657. X        pline->last = last2;
  1658. X        pline->first = first2;
  1659. X        pline->type = ch2;
  1660. X        pline->link = Nullact;
  1661. X        if (!last_line) {
  1662. X            line_root = pline;
  1663. X        } else {
  1664. X            last_line->link = pline;
  1665. X        }
  1666. X        *line2 = '\0';
  1667. X        last_line = pline;
  1668. X        }/* for */
  1669. X        if (eof_active && eof_active2) {
  1670. X        break;
  1671. X        }
  1672. X    }/* if !pline */
  1673. X    if (eof_active) {
  1674. X        strcpy(line, line2);
  1675. X        if (truncate_len < 0) {
  1676. X        truncate_len = ftell(fp_active2w);
  1677. X        }
  1678. X    }
  1679. X    if (rebuild) {
  1680. X        unlink(thread_name(line));
  1681. X    }
  1682. X    update_successful = FALSE;
  1683. X    if (hierarchy_list && !add_new) {
  1684. X        switch ((action = ngmatch(hierarchy_list, line))) {
  1685. X        case NG_MATCH:            /* if unthreaded, add it */
  1686. X        if (ch2 < 'a' && ch != 'x' && ch != '=') {
  1687. X            total.last = last2 = first2 - 1;
  1688. X            touch_thread = TRUE;
  1689. X            added_groups++;
  1690. X        }
  1691. X        break;
  1692. X        case NG_SKIP:            /* if threaded, remove it */
  1693. X        if (ch2 >= 'a') {
  1694. X            unlink(thread_name(line));
  1695. X            removed_groups++;
  1696. X        }
  1697. X        break;
  1698. X        }
  1699. X    } else {
  1700. X        action = (ch2 < 'a' ? NG_SKIP : NG_MATCH);
  1701. X    }
  1702. X    if (action == NG_DEFAULT || (debug && action == NG_SKIP)) {
  1703. X        dont_read_data(data_file_open);    /* skip silently */
  1704. X        if (touch_thread) {
  1705. X        (void) write_data(thread_name(line));
  1706. X        }
  1707. X    } else if (ch == 'x' || ch == '=') {
  1708. X        if (!daemon_delay) {        /* skip 'x'ed groups */
  1709. X        putchar('x');
  1710. X        }
  1711. X        ch = (action == NG_SKIP ? 'X' : 'x');
  1712. X        if ((ch2 >= 'a' && ch2 != 'x') || force_flag) {
  1713. X        /* Remove thread file if group is newly 'x'ed out */
  1714. X        unlink(thread_name(line));
  1715. X        }
  1716. X        update_successful = TRUE;
  1717. X        dont_read_data(data_file_open);
  1718. X    } else if (action == NG_SKIP) {    /* skip excluded groups */
  1719. X        if (!daemon_delay) {
  1720. X        putchar('X');
  1721. X        }
  1722. X        ch = toupper(ch);
  1723. X        if (force_flag) {
  1724. X        unlink(thread_name(line));
  1725. X        }
  1726. X        update_successful = TRUE;
  1727. X        dont_read_data(data_file_open);
  1728. X    } else if (no_processing) {
  1729. X        if (!daemon_delay) {
  1730. X        putchar(',');
  1731. X        }
  1732. X        ch2 = ch;
  1733. X        dont_read_data(data_file_open);
  1734. X        if (touch_thread) {
  1735. X        (void) write_data(thread_name(line));
  1736. X        }
  1737. X    } else if (!force_flag && !extra_expire && !rebuild
  1738. X     && first == first2 && last == last2) {
  1739. X        /* We're up-to-date here.  Skip it. */
  1740. X        if (!daemon_delay) {
  1741. X        putchar('.');
  1742. X        }
  1743. X        update_successful = TRUE;
  1744. X        dont_read_data(data_file_open);
  1745. X        if (touch_thread) {
  1746. X        (void) write_data(thread_name(line));
  1747. X        }
  1748. X    } else {
  1749. X        /* Looks like we need to process something. */
  1750. X#ifdef USE_NNTP
  1751. X        if (!server_failure) {
  1752. X        sprintf(buf, "GROUP %s", line);
  1753. X        nntp_command(buf);        /* go to next group */
  1754. X        if (nntp_check(FALSE) != NNTP_CLASS_OK) {
  1755. X            log_error("NNTP failure -- %s.\n", ser_line);
  1756. X            if (strnNE(ser_line, "400", 3)) {
  1757. X            nntp_close();
  1758. X            }
  1759. X            server_failure = 1;
  1760. X        }
  1761. X        }
  1762. X        if (server_failure) {
  1763. X#else
  1764. X        strcpy(cp = buf, line2);
  1765. X        while ((cp = index(cp, '.'))) {
  1766. X        *cp = '/';
  1767. X        }
  1768. X        filename = file_exp(buf);        /* relative to spool dir */
  1769. X        if (chdir(filename) < 0) {
  1770. X        if (errno != ENOENT) {
  1771. X            log_entry("Unable to chdir to `%s'.\n", filename);
  1772. X        }
  1773. X#endif
  1774. X        if (!daemon_delay) {
  1775. X            putchar('*');
  1776. X        }
  1777. X        dont_read_data(data_file_open);
  1778. X        } else {
  1779. X        filename = thread_name(line);
  1780. X        /* Try to open the data file only if we didn't try it
  1781. X        ** in the name matching code above.
  1782. X        */
  1783. X        if (!data_file_open--) {    /* (0 == haven't tried yet) */
  1784. X            if (!(data_file_open = init_data(filename))) {
  1785. X            total.last = first - 1;
  1786. X            total.first = first;
  1787. X            }
  1788. X        }
  1789. X
  1790. X        if (data_file_open) {        /* (0 == empty, 1 == open) */
  1791. X            if (!read_data()) {    /* did read fail? */
  1792. X            if (debug) {
  1793. X                strcpy(buf, filename);
  1794. X                cp = rindex(buf, '/') + 1;
  1795. X                strcpy(cp, "bad.read");
  1796. X                rename(filename, buf);
  1797. X            }
  1798. X            data_file_open = init_data(Nullch);
  1799. X            total.last = first - 1;
  1800. X            total.first = first;
  1801. X            }
  1802. X        }
  1803. X        grevious_error = FALSE;
  1804. X        process_articles(first, last);
  1805. X        processed_groups++;
  1806. X        if (!added_count && !expired_count && !touch_thread
  1807. X         && last == last2) {
  1808. X            (void) write_data(Nullch);
  1809. X            if (!daemon_delay) {
  1810. X            putchar(':');
  1811. X            }
  1812. X            update_successful = TRUE;
  1813. X        } else {
  1814. X            int root_count = total.root;
  1815. X            strcpy(buf, filename);
  1816. X            cp = rindex(buf, '/') + 1;
  1817. X            strcpy(cp, NEW_THREAD);    /* write data as .new */
  1818. X            if (write_data(buf) && !grevious_error) {
  1819. X            rename(buf, filename);
  1820. X            added_articles += added_count;
  1821. X            expired_articles += expired_count;
  1822. X            if (!daemon_delay) {
  1823. X                if (!root_count) {
  1824. X                putchar('-');
  1825. X                } else {
  1826. X                putchar('#');
  1827. X                }
  1828. X            }
  1829. X            update_successful = TRUE;
  1830. X            } else {
  1831. X            if (debug) {
  1832. X                cp = rindex(filename, '/') + 1;
  1833. X                strcpy(cp, "bad.write");
  1834. X                rename(buf, filename);
  1835. X            } else {
  1836. X                unlink(buf);    /* blow away bad write */
  1837. X                if (grevious_error) {
  1838. X                unlink(filename); /* blow away the .thread, */
  1839. X                (void) init_data(Nullch); /* set totals */
  1840. X                total.last = first-1;
  1841. X                total.first = first;
  1842. X                (void) write_data(filename); /* write it null */
  1843. X                }
  1844. X            }
  1845. X            if (!daemon_delay) {
  1846. X                putchar('!');
  1847. X            }
  1848. X            }/* if */
  1849. X        }/* if */
  1850. X        }/* if */
  1851. X    }/* if */
  1852. X    /* Finally, update the active2 entry for this newsgroup. */
  1853. X    if (!eof_active) {
  1854. X        if (update_successful) {
  1855. X        fprintf(fp_active2w, fmt_active2, line, last, first, ch);
  1856. X        } else {
  1857. X        fprintf(fp_active2w, fmt_active2, line, last2, first2, ch2);
  1858. X        }
  1859. X    }
  1860. X    *line2 = '\0';
  1861. X    /* If we're not out of sync, keep active2 file flushed. */
  1862. X    if (!line_root) {
  1863. X        fflush(fp_active2w);
  1864. X    }
  1865. X#ifdef CHECKLOAD
  1866. X    checkload();
  1867. X#endif
  1868. X    }/* for */
  1869. X
  1870. X#ifdef USE_NNTP
  1871. X    if (!server_failure) {
  1872. X    nntp_close();
  1873. X    }
  1874. X#endif
  1875. X    fclose(fp_active);
  1876. X    fclose(fp_active2);
  1877. X
  1878. X    if (truncate_len >= 0) {
  1879. X#ifdef HAS_FTRUNCATE
  1880. X    if (ftruncate(fileno(fp_active2w), truncate_len) == -1)
  1881. X#else
  1882. X#ifdef MVTRUNC
  1883. X    if (mvtrunc(file_exp(ACTIVE2), truncate_len) == -1)
  1884. X#else
  1885. X    if (chsize(fileno(fp_active2w), truncate_len) == -1)
  1886. X#endif
  1887. X#endif
  1888. X    {
  1889. X        log_entry("Unable to truncate the active2 file.\n");
  1890. X    }
  1891. X    truncate_len = -1;
  1892. X    }
  1893. X    fclose(fp_active2w);
  1894. X    fp_active2w = Nullfp;
  1895. X
  1896. X    if (fp_tmp) {
  1897. X    fclose(fp_tmp);
  1898. X    }
  1899. X    log_stats();
  1900. X    processed_groups = added_groups = removed_groups = 0;
  1901. X    added_articles = expired_articles = 0;
  1902. X
  1903. X    extra_expire = FALSE;
  1904. X    rebuild = FALSE;
  1905. X
  1906. X    mt_unlock(PASS_LOCK);        /* remove single-pass lock */
  1907. X
  1908. X    return TRUE;
  1909. X}
  1910. X
  1911. X/*
  1912. X** ngmatch - newsgroup name matching
  1913. X**
  1914. X** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
  1915. X** and NG_DEFAULT if the group doesn't match at all.
  1916. X**
  1917. X** "all" in a pattern is a wildcard that matches exactly one word;
  1918. X** it does not cross "." (NGDELIM) delimiters.
  1919. X**
  1920. X** This matching code was borrowed from C news.
  1921. X*/
  1922. X
  1923. X#define ALL "all"            /* word wildcard */
  1924. X
  1925. X#define NGNEG '!'
  1926. X#define NGSEP ','
  1927. X#define NGDELIM '.'
  1928. X
  1929. Xint
  1930. Xngmatch(ngpat, grp)
  1931. Xchar *ngpat, *grp;
  1932. X{
  1933. X    register char *patp;        /* point at current pattern */
  1934. X    register char *patcomma;
  1935. X    register int depth;
  1936. X    register int faildeepest = 0, hitdeepest = 0;    /* in case no match */
  1937. X    register bool negation;
  1938. X
  1939. X    for (patp = ngpat; patp != Nullch; patp = patcomma) {
  1940. X    negation = FALSE;
  1941. X    patcomma = index(patp, NGSEP);
  1942. X    if (patcomma != Nullch) {
  1943. X        *patcomma = '\0';        /* will be restored below */
  1944. X    }
  1945. X    if (*patp == NGNEG) {
  1946. X        ++patp;
  1947. X        negation = TRUE;
  1948. X    }
  1949. X    depth = onepatmatch(patp, grp); /* try 1 pattern, 1 group */
  1950. X    if (patcomma != Nullch) {
  1951. X        *patcomma++ = NGSEP;    /* point after the comma */
  1952. X    }
  1953. X    if (depth == 0) {        /* mis-match */
  1954. X        ;                /* ignore it */
  1955. X    } else if (negation) {
  1956. X        /* record depth of deepest negated matched word */
  1957. X        if (depth > faildeepest) {
  1958. X        faildeepest = depth;
  1959. X        }
  1960. X    } else {
  1961. X        /* record depth of deepest plain matched word */
  1962. X        if (depth > hitdeepest) {
  1963. X        hitdeepest = depth;
  1964. X        }
  1965. X    }
  1966. X    }
  1967. X    if (hitdeepest > faildeepest) {
  1968. X    return NG_MATCH;
  1969. X    } else if (faildeepest) {
  1970. X    return NG_SKIP;
  1971. X    } else {
  1972. X    return NG_DEFAULT;
  1973. X    }
  1974. X}
  1975. X
  1976. X/*
  1977. X** Match a pattern against a group by looking at each word of pattern in turn.
  1978. X**
  1979. X** On a match, return the depth (roughly, ordinal number * k) of the rightmost
  1980. X** word that matches.  If group runs out first, the match fails; if pattern
  1981. X** runs out first, it succeeds.  On a failure, return zero.
  1982. X*/
  1983. Xint
  1984. Xonepatmatch(patp, grp)
  1985. Xchar *patp, *grp;
  1986. X{
  1987. X    register char *rpatwd;        /* used by word match (inner loop) */
  1988. X    register char *patdot, *grdot;    /* point at dots after words */
  1989. X    register char *patwd, *grwd;    /* point at current words */
  1990. X    register int depth = 0;
  1991. X
  1992. X    for (patwd = patp, grwd = grp;
  1993. X     patwd != Nullch && grwd != Nullch;
  1994. X     patwd = patdot, grwd = grdot
  1995. X    ) {
  1996. X    register bool match = FALSE;
  1997. X    register int incr = 20;
  1998. X
  1999. X    /* null-terminate words */
  2000. X    patdot = index(patwd, NGDELIM);
  2001. X    if (patdot != Nullch) {
  2002. X        *patdot = '\0';        /* will be restored below */
  2003. X    }
  2004. X    grdot = index(grwd, NGDELIM);
  2005. X    if (grdot != Nullch) {
  2006. X        *grdot = '\0';        /* will be restored below */
  2007. X    }
  2008. X    /*
  2009. X     * Match one word of pattern with one word of group.
  2010. X     * A pattern word of "all" matches any group word,
  2011. X     * but isn't worth as much.
  2012. X     */
  2013. X#ifdef FAST_STRCMP
  2014. X    match = STREQ(patwd, grwd);
  2015. X    if (!match && STREQ(patwd, ALL)) {
  2016. X        match = TRUE;
  2017. X        --incr;
  2018. X    }
  2019. X#else
  2020. X    for (rpatwd = patwd; *rpatwd == *grwd++;) {
  2021. X        if (*rpatwd++ == '\0') {
  2022. X        match = TRUE;        /* literal match */
  2023. X        break;
  2024. X        }
  2025. X    }
  2026. X    if (!match) {
  2027. X        /* ugly special case match for "all" */
  2028. X        rpatwd = patwd;
  2029. X        if (*rpatwd++ == 'a' && *rpatwd++ == 'l'
  2030. X         && *rpatwd++ == 'l' && *rpatwd   == '\0') {
  2031. X        match = TRUE;
  2032. X         --incr;
  2033. X        }
  2034. X    }
  2035. X#endif                /* FAST_STRCMP */
  2036. X
  2037. X    if (patdot != Nullch) {
  2038. X        *patdot++ = NGDELIM;    /* point after the dot */
  2039. X    }
  2040. X    if (grdot != Nullch) {
  2041. X        *grdot++ = NGDELIM;
  2042. X    }
  2043. X    if (!match) {
  2044. X        depth = 0;        /* words differed - mismatch */
  2045. X        break;
  2046. X    }
  2047. X    depth += incr;
  2048. X    }
  2049. X    /* if group name ran out before pattern, then match fails */
  2050. X    if (grwd == Nullch && patwd != Nullch) {
  2051. X    depth = 0;
  2052. X    }
  2053. X    return depth;
  2054. X}
  2055. X
  2056. X/* Put our startup options into the log file.
  2057. X*/
  2058. Xvoid
  2059. Xlog_startup()
  2060. X{
  2061. X    char tmpbuf[256];
  2062. X
  2063. X    strcpy(tmpbuf, "Started mthreads");
  2064. X    if (cron_locking) {
  2065. X    strcat(tmpbuf, " -c");
  2066. X    }
  2067. X    if (debug) {
  2068. X    strcat(tmpbuf, " -D");
  2069. X    }
  2070. X    if (force_flag) {
  2071. X    strcat(tmpbuf, " -f");
  2072. X    }
  2073. X    if (daemon_delay) {
  2074. X    sprintf(tmpbuf + strlen(tmpbuf), " -d%d", daemon_delay / 60);
  2075. X    if (expire_time) {
  2076. X        struct tm *ts;
  2077. X
  2078. X        ts = localtime(&expire_time);
  2079. X        sprintf(tmpbuf + strlen(tmpbuf), " -e%02d%02d",ts->tm_hour,ts->tm_min);
  2080. X    }
  2081. X    } else if (expire_time) {
  2082. X    strcat(tmpbuf, " -e");
  2083. X    }
  2084. X    if (slow_down) {
  2085. X    sprintf(tmpbuf + strlen(tmpbuf), " -s%d", slow_down);
  2086. X    }
  2087. X    if (no_processing) {
  2088. X    strcat(tmpbuf, " -n");
  2089. X    }
  2090. X    if (log_verbosity) {
  2091. X    sprintf(tmpbuf + strlen(tmpbuf), " -v%d", log_verbosity);
  2092. X    }
  2093. X    if (zap_thread) {
  2094. X    strcat(tmpbuf, " -z");
  2095. X    }
  2096. X    if (add_new) {
  2097. X    if (hierarchy_list) {
  2098. X        sprintf(tmpbuf + strlen(tmpbuf), " -a %s", hierarchy_list);
  2099. X    } else {
  2100. X        strcat(tmpbuf, " -a all");
  2101. X    }
  2102. X    } else if (hierarchy_list) {
  2103. X    sprintf(tmpbuf + strlen(tmpbuf), " %s (only)", hierarchy_list);
  2104. X    }
  2105. X    log_entry("%s\n", tmpbuf);
  2106. X}
  2107. X
  2108. X/* Put our statistics into the log file.
  2109. X*/
  2110. Xvoid
  2111. Xlog_stats()
  2112. X{
  2113. X    sprintf(line, "Processed %d group%s:  added %d article%s, expired %d.\n",
  2114. X    processed_groups, processed_groups == 1 ? nullstr : "s",
  2115. X    added_articles, added_articles == 1 ? nullstr : "s",
  2116. X    expired_articles);
  2117. X
  2118. X    log_entry(line);
  2119. X
  2120. X    if (!daemon_delay) {
  2121. X    putchar('\n');
  2122. X    fputs(line, stdout);
  2123. X    }
  2124. X    if (added_groups) {
  2125. X    sprintf(line, "Turned %d group%s on.\n", added_groups,
  2126. X        added_groups == 1 ? nullstr : "s");
  2127. X    log_entry(line);
  2128. X    if (!daemon_delay) {
  2129. X        fputs(line, stdout);
  2130. X    }
  2131. X    }
  2132. X    if (removed_groups) {
  2133. X    sprintf(line, "Turned %d group%s off.\n", removed_groups,
  2134. X        removed_groups == 1 ? nullstr : "s");
  2135. X    log_entry(line);
  2136. X    if (!daemon_delay) {
  2137. X        fputs(line, stdout);
  2138. X    }
  2139. X    }
  2140. X}
  2141. X/* Generate a log entry with timestamp.
  2142. X*/
  2143. X/*VARARGS1*/
  2144. Xvoid
  2145. Xlog_entry(fmt, arg1, arg2, arg3)
  2146. Xchar *fmt;
  2147. Xlong arg1, arg2, arg3;
  2148. X{
  2149. X#ifndef USE_SYSLOG
  2150. X    time_t now;
  2151. X    char *ctime();
  2152. X#endif
  2153. X
  2154. X    if (initializing) {
  2155. X    fprintf(stderr, fmt, arg1, arg2, arg3);
  2156. X    return;
  2157. X    }
  2158. X
  2159. X#ifndef USE_SYSLOG
  2160. X    (void) time(&now);
  2161. X    fprintf(fp_log, "%.12s%c", ctime(&now)+4, daemon_delay ? ' ' : '+');
  2162. X    fprintf(fp_log, fmt, arg1, arg2, arg3);
  2163. X    fflush(fp_log);
  2164. X#else
  2165. X    syslog(LOG_INFO, fmt, arg1, arg2, arg3);
  2166. X#endif
  2167. X}
  2168. X
  2169. X/* Generate a log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
  2170. X** and newsgroup name.
  2171. X*/
  2172. X/*VARARGS1*/
  2173. Xvoid
  2174. Xlog_error(fmt, arg1, arg2, arg3)
  2175. Xchar *fmt;
  2176. Xlong arg1;
  2177. Xlong arg2;
  2178. Xlong arg3;
  2179. X{
  2180. X    char fmtbuf[256];
  2181. X
  2182. X    sprintf(fmtbuf, "%s: %s", line, fmt);
  2183. X#ifndef USE_SYSLOG
  2184. X    log_entry(fmtbuf, arg1, arg2, arg3);
  2185. X#else
  2186. X    syslog(LOG_NOTICE, fmtbuf, arg1, arg2, arg3);
  2187. X#endif
  2188. X    if (*fmt == '*') {
  2189. X    grevious_error = TRUE;
  2190. X    if (!daemon_delay) {
  2191. X        putchar('E');
  2192. X    }
  2193. X    }
  2194. X    else {
  2195. X    if (!daemon_delay) {
  2196. X        putchar('e');
  2197. X    }
  2198. X    }
  2199. X}
  2200. X
  2201. X#ifdef MYCHSIZE
  2202. X    /* code courtesy of William Kucharski */
  2203. X
  2204. Xint
  2205. Xchsize(fd, length)
  2206. Xint fd;            /* file descriptor */
  2207. Xoff_t length;        /* length to set file to */
  2208. X{
  2209. X    extern long lseek();
  2210. X    struct flock fl;
  2211. X
  2212. X    if (fstat(fd, &filestat) < 0) {
  2213. X    return -1;
  2214. X    }
  2215. X    if (filestat.st_size < length) {    /* extend file length */
  2216. X    /* Write a 0 byte at the end. */
  2217. X    if (lseek(fd, length - 1, 0) < 0
  2218. X     || write(fd, "", 1) != 1) {
  2219. X        return -1;
  2220. X    }
  2221. X    } else {
  2222. X    /* Truncate file at length. */
  2223. X    fl.l_whence = 0;
  2224. X    fl.l_len = 0;
  2225. X    fl.l_start = length;
  2226. X    fl.l_type = F_WRLCK;        /* write lock on file space */
  2227. X
  2228. X    /*
  2229. X    ** This relies on the UNDOCUMENTED F_FREESP argument to
  2230. X    ** fcntl(2), which truncates the file so that it ends at the
  2231. X    ** position indicated by fl.l_start.
  2232. X    **
  2233. X    ** Will minor miracles never cease?
  2234. X    */
  2235. X    if (fcntl(fd, F_FREESP, &fl) < 0) {
  2236. X        return -1;
  2237. X    }
  2238. X    }
  2239. X    return 0;
  2240. X}
  2241. X#endif
  2242. X
  2243. X#ifdef MVTRUNC
  2244. Xint
  2245. Xmvtrunc(filename, truncate_len)
  2246. Xchar *filename;
  2247. Xlong truncate_len;
  2248. X{
  2249. X    FILE *fp_in, *fp_out;
  2250. X
  2251. X    sprintf(line, "%s.new", filename);
  2252. X    if ((fp_out = fopen(line, "w")) == Nullfp) {
  2253. X    log_entry("Tried to create active2.new.\n");
  2254. X    return -1;
  2255. X    }
  2256. X    if ((fp_in = fopen(filename, "r")) == Nullfp) {
  2257. X    fclose(fp_out);
  2258. X    unlink(line);
  2259. X    log_entry("Tried to re-open the active2 file.\n");
  2260. X    return -1;
  2261. X    }
  2262. X    while (ftell(fp_out) < truncate_len) {
  2263. X    if (!fgets(buf, sizeof buf, fp_in)) {
  2264. X        break;
  2265. X    }
  2266. X    fputs(buf, fp_out);
  2267. X    }
  2268. X    sprintf(buf, "%s.old", filename);
  2269. X    rename(filename, buf);
  2270. X    rename(line, filename);
  2271. X    fclose(fp_in);
  2272. X    fclose(fp_out);
  2273. X
  2274. X    return 0;
  2275. X}
  2276. X#endif
  2277. X
  2278. X#ifndef HAS_RENAME
  2279. Xint
  2280. Xrename(old, new)
  2281. Xchar    *old, *new;
  2282. X{
  2283. X    struct stat st;
  2284. X
  2285. X    if (stat(old, &st) == -1) {
  2286. X    return -1;
  2287. X    }
  2288. X    if (unlink(new) == -1 && errno != ENOENT) {
  2289. X    return -1;
  2290. X    }
  2291. X    if (link(old, new) == -1) {
  2292. X    return -1;
  2293. X    }
  2294. X    if (unlink(old) == -1) {
  2295. X    int e = errno;
  2296. X    (void) unlink(new);
  2297. X    errno = e;
  2298. X    return -1;
  2299. X    }
  2300. X    return 0;
  2301. X}
  2302. X#endif /* HAS_RENAME */
  2303. X
  2304. X#ifdef CHECKLOAD
  2305. X
  2306. X#define FREQCHECK    300
  2307. X#define SLPLOAD        300
  2308. X#define UPTIME        "/usr/ucb/uptime"
  2309. X#define TOOHIGH        5
  2310. X
  2311. Xcheckload()
  2312. X{
  2313. X    static long lastcheck = 0;
  2314. X    long time(), i;
  2315. X    FILE *pp;
  2316. X    char buf[BUFSIZ];
  2317. X    register char *cp;
  2318. X    char *strrchr();
  2319. X
  2320. X    i = time(Null(time_t*));
  2321. X    if ((i - lastcheck) < FREQCHECK) {
  2322. X    return;
  2323. X    }
  2324. X    lastcheck = i;
  2325. X
  2326. Xagain:
  2327. X    if ((pp = popen(UPTIME, "r")) == NULL) {
  2328. X    return;
  2329. X    }
  2330. X    if (fgets(buf, BUFSIZ, pp) == NULL) {
  2331. X    pclose(pp);
  2332. X    return;
  2333. X    }
  2334. X    pclose(pp);
  2335. X    if ((cp = strrchr(buf, ':')) == NULL) {
  2336. X    return;
  2337. X    }
  2338. X    if (atoi(cp + 2) >= TOOHIGH) {
  2339. X    sleep(SLPLOAD);
  2340. X    goto again;
  2341. X    } else {
  2342. X    return;
  2343. X    }
  2344. X}
  2345. X#endif
  2346. END_OF_FILE
  2347. if test 33313 -ne `wc -c <'mthreads.c'`; then
  2348.     echo shar: \"'mthreads.c'\" unpacked with wrong size!
  2349. fi
  2350. # end of 'mthreads.c'
  2351. fi
  2352. if test -f 'parsedate.y' -a "${1}" != "-c" ; then 
  2353.   echo shar: Will not clobber existing file \"'parsedate.y'\"
  2354. else
  2355. echo shar: Extracting \"'parsedate.y'\" \(21261 characters\)
  2356. sed "s/^X//" >'parsedate.y' <<'END_OF_FILE'
  2357. X%{
  2358. X/* $Revision: 1.12 $
  2359. X**
  2360. X**  Originally written by Steven M. Bellovin <smb@research.att.com> while
  2361. X**  at the University of North Carolina at Chapel Hill.  Later tweaked by
  2362. X**  a couple of people on Usenet.  Completely overhauled by Rich $alz
  2363. X**  <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
  2364. X**  Further revised (removed obsolete constructs and cleaned up timezone
  2365. X**  names) in August, 1991, by Rich.  Paul Eggert <eggert@twinsun.com>
  2366. X**  helped in September, 1992.
  2367. X**
  2368. X**  This grammar has six shift/reduce conflicts.
  2369. X**
  2370. X**  This code is in the public domain and has no copyright.
  2371. X*/
  2372. X/* SUPPRESS 530 *//* Empty body for statement */
  2373. X/* SUPPRESS 593 on yyerrlab *//* Label was not used */
  2374. X/* SUPPRESS 593 on yynewstate *//* Label was not used */
  2375. X/* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
  2376. X#include <stdio.h>
  2377. X#include <sys/types.h>
  2378. X#include <ctype.h>
  2379. X#include "config.h"
  2380. X#include <time.h>
  2381. X
  2382. X#define yyparse        date_parse
  2383. X#define yylex        date_lex
  2384. X#define yyerror        date_error
  2385. X
  2386. X
  2387. X    /* See the LeapYears table in Convert. */
  2388. X#define EPOCH        1970
  2389. X#define END_OF_TIME    2038
  2390. X    /* Constants for general time calculations. */
  2391. X#define DST_OFFSET    1
  2392. X#define SECSPERDAY    (24L * 60L * 60L)
  2393. X    /* Readability for TABLE stuff. */
  2394. X#define HOUR(x)        (x * 60)
  2395. X
  2396. X#define LPAREN        '('
  2397. X#define RPAREN        ')'
  2398. X#define IS7BIT(x)    ((unsigned int)(x) < 0200)
  2399. X
  2400. X#define SIZEOF(array)    ((int)(sizeof array / sizeof array[0]))
  2401. X#define ENDOF(array)    (&array[SIZEOF(array)])
  2402. X
  2403. X
  2404. X/*
  2405. X**  An entry in the lexical lookup table.
  2406. X*/
  2407. Xtypedef struct _TABLE {
  2408. X    char    *name;
  2409. X    int        type;
  2410. X    time_t    value;
  2411. X} TABLE;
  2412. X
  2413. X/*
  2414. X**  Daylight-savings mode:  on, off, or not yet known.
  2415. X*/
  2416. Xtypedef enum _DSTMODE {
  2417. X    DSTon, DSToff, DSTmaybe
  2418. X} DSTMODE;
  2419. X
  2420. X/*
  2421. X**  Meridian:  am, pm, or 24-hour style.
  2422. X*/
  2423. Xtypedef enum _MERIDIAN {
  2424. X    MERam, MERpm, MER24
  2425. X} MERIDIAN;
  2426. X
  2427. X
  2428. X/*
  2429. X**  Global variables.  We could get rid of most of them by using a yacc
  2430. X**  union, but this is more efficient.  (This routine predates the
  2431. X**  yacc %union construct.)
  2432. X*/
  2433. Xstatic char    *yyInput;
  2434. Xstatic DSTMODE    yyDSTmode;
  2435. Xstatic int    yyHaveDate;
  2436. Xstatic int    yyHaveRel;
  2437. Xstatic int    yyHaveTime;
  2438. Xstatic time_t    yyTimezone;
  2439. Xstatic time_t    yyDay;
  2440. Xstatic time_t    yyHour;
  2441. Xstatic time_t    yyMinutes;
  2442. Xstatic time_t    yyMonth;
  2443. Xstatic time_t    yySeconds;
  2444. Xstatic time_t    yyYear;
  2445. Xstatic MERIDIAN    yyMeridian;
  2446. Xstatic time_t    yyRelMonth;
  2447. Xstatic time_t    yyRelSeconds;
  2448. X
  2449. X
  2450. Xextern struct tm    *localtime();
  2451. X
  2452. Xstatic void        date_error();
  2453. X%}
  2454. X
  2455. X%union {
  2456. X    time_t        Number;
  2457. X    enum _MERIDIAN    Meridian;
  2458. X}
  2459. X
  2460. X%token    tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
  2461. X%token    tUNUMBER tZONE
  2462. X
  2463. X%type    <Number>    tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
  2464. X%type    <Number>    tSNUMBER tUNUMBER tZONE numzone zone
  2465. X%type    <Meridian>    tMERIDIAN o_merid
  2466. X
  2467. X%%
  2468. X
  2469. Xspec    : /* NULL */
  2470. X    | spec item
  2471. X    ;
  2472. X
  2473. Xitem    : time {
  2474. X        yyHaveTime++;
  2475. X#ifdef lint
  2476. X        /* I am compulsive about lint natterings... */
  2477. X        if (yyHaveTime == -1) {
  2478. X        YYERROR;
  2479. X        }
  2480. X#endif /* lint */
  2481. X    }
  2482. X    | time zone {
  2483. X        yyHaveTime++;
  2484. X        yyTimezone = $2;
  2485. X    }
  2486. X    | date {
  2487. X        yyHaveDate++;
  2488. X    }
  2489. X    | rel {
  2490. X        yyHaveRel = 1;
  2491. X    }
  2492. X    ;
  2493. X
  2494. Xtime    : tUNUMBER o_merid {
  2495. X        if ($1 < 100) {
  2496. X        yyHour = $1;
  2497. X        yyMinutes = 0;
  2498. X        }
  2499. X        else {
  2500. X        yyHour = $1 / 100;
  2501. X        yyMinutes = $1 % 100;
  2502. X        }
  2503. X        yySeconds = 0;
  2504. X        yyMeridian = $2;
  2505. X    }
  2506. X    | tUNUMBER ':' tUNUMBER o_merid {
  2507. X        yyHour = $1;
  2508. X        yyMinutes = $3;
  2509. X        yySeconds = 0;
  2510. X        yyMeridian = $4;
  2511. X    }
  2512. X    | tUNUMBER ':' tUNUMBER numzone {
  2513. X        yyHour = $1;
  2514. X        yyMinutes = $3;
  2515. X        yyTimezone = $4;
  2516. X        yyMeridian = MER24;
  2517. X        yyDSTmode = DSToff;
  2518. X    }
  2519. X    | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
  2520. X        yyHour = $1;
  2521. X        yyMinutes = $3;
  2522. X        yySeconds = $5;
  2523. X        yyMeridian = $6;
  2524. X    }
  2525. X    | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
  2526. X        yyHour = $1;
  2527. X        yyMinutes = $3;
  2528. X        yySeconds = $5;
  2529. X        yyTimezone = $6;
  2530. X        yyMeridian = MER24;
  2531. X        yyDSTmode = DSToff;
  2532. X    }
  2533. X    ;
  2534. X
  2535. Xzone    : tZONE {
  2536. X        $$ = $1;
  2537. X        yyDSTmode = DSToff;
  2538. X    }
  2539. X    | tDAYZONE {
  2540. X        $$ = $1;
  2541. X        yyDSTmode = DSTon;
  2542. X    }
  2543. X    | tZONE numzone {
  2544. X        /* Only allow "GMT+300" and "GMT-0800" */
  2545. X        if ($1 != 0) {
  2546. X        YYABORT;
  2547. X        }
  2548. X        $$ = $2;
  2549. X        yyDSTmode = DSToff;
  2550. X    }
  2551. X    | numzone {
  2552. X        $$ = $1;
  2553. X        yyDSTmode = DSToff;
  2554. X    }
  2555. X    ;
  2556. X
  2557. Xnumzone    : tSNUMBER {
  2558. X        int        i;
  2559. X
  2560. X        /* Unix and GMT and numeric timezones -- a little confusing. */
  2561. X        if ($1 < 0) {
  2562. X        /* Don't work with negative modulus. */
  2563. X        $1 = -$1;
  2564. X        if ($1 > 9999 || (i = $1 % 100) >= 60) {
  2565. X            YYABORT;
  2566. X        }
  2567. X        $$ = ($1 / 100) * 60 + i;
  2568. X        }
  2569. X        else {
  2570. X        if ($1 > 9999 || (i = $1 % 100) >= 60) {
  2571. X            YYABORT;
  2572. X        }
  2573. X        $$ = -(($1 / 100) * 60 + i);
  2574. X        }
  2575. X    }
  2576. X    ;
  2577. X
  2578. Xdate    : tUNUMBER '/' tUNUMBER {
  2579. X        yyMonth = $1;
  2580. X        yyDay = $3;
  2581. X    }
  2582. X    | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
  2583. X        if ($1 > 100) {
  2584. X        yyYear = $1;
  2585. X        yyMonth = $3;
  2586. X        yyDay = $5;
  2587. X        }
  2588. X        else {
  2589. X        yyMonth = $1;
  2590. X        yyDay = $3;
  2591. X        yyYear = $5;
  2592. X        }
  2593. X    }
  2594. X    | tMONTH tUNUMBER {
  2595. X        yyMonth = $1;
  2596. X        yyDay = $2;
  2597. X    }
  2598. X    | tMONTH tUNUMBER ',' tUNUMBER {
  2599. X        yyMonth = $1;
  2600. X        yyDay = $2;
  2601. X        yyYear = $4;
  2602. X    }
  2603. X    | tUNUMBER tMONTH {
  2604. X        yyDay = $1;
  2605. X        yyMonth = $2;
  2606. X    }
  2607. X    | tUNUMBER tMONTH tUNUMBER {
  2608. X        yyDay = $1;
  2609. X        yyMonth = $2;
  2610. X        yyYear = $3;
  2611. X    }
  2612. X    | tDAY ',' tUNUMBER tMONTH tUNUMBER {
  2613. X        yyDay = $3;
  2614. X        yyMonth = $4;
  2615. X        yyYear = $5;
  2616. X    }
  2617. X    ;
  2618. X
  2619. Xrel    : tSNUMBER tSEC_UNIT {
  2620. X        yyRelSeconds += $1 * $2;
  2621. X    }
  2622. X    | tUNUMBER tSEC_UNIT {
  2623. X        yyRelSeconds += $1 * $2;
  2624. X    }
  2625. X    | tSNUMBER tMONTH_UNIT {
  2626. X        yyRelMonth += $1 * $2;
  2627. X    }
  2628. X    | tUNUMBER tMONTH_UNIT {
  2629. X        yyRelMonth += $1 * $2;
  2630. X    }
  2631. X    ;
  2632. X
  2633. Xo_merid    : /* NULL */ {
  2634. X        $$ = MER24;
  2635. X    }
  2636. X    | tMERIDIAN {
  2637. X        $$ = $1;
  2638. X    }
  2639. X    ;
  2640. X
  2641. X%%
  2642. X
  2643. X/* Month and day table. */
  2644. Xstatic TABLE    MonthDayTable[] = {
  2645. X    { "january",    tMONTH,  1 },
  2646. X    { "february",    tMONTH,  2 },
  2647. X    { "march",        tMONTH,  3 },
  2648. X    { "april",        tMONTH,  4 },
  2649. X    { "may",        tMONTH,  5 },
  2650. X    { "june",        tMONTH,  6 },
  2651. X    { "july",        tMONTH,  7 },
  2652. X    { "august",        tMONTH,  8 },
  2653. X    { "september",    tMONTH,  9 },
  2654. X    { "october",    tMONTH, 10 },
  2655. X    { "november",    tMONTH, 11 },
  2656. X    { "december",    tMONTH, 12 },
  2657. X    /* The value of the day isn't used... */
  2658. X    { "sunday",        tDAY, 0 },
  2659. X    { "monday",        tDAY, 0 },
  2660. X    { "tuesday",    tDAY, 0 },
  2661. X    { "wednesday",    tDAY, 0 },
  2662. X    { "thursday",    tDAY, 0 },
  2663. X    { "friday",        tDAY, 0 },
  2664. X    { "saturday",    tDAY, 0 },
  2665. X};
  2666. X
  2667. X/* Time units table. */
  2668. Xstatic TABLE    UnitsTable[] = {
  2669. X    { "year",        tMONTH_UNIT,    12 },
  2670. X    { "month",        tMONTH_UNIT,    1 },
  2671. X    { "week",        tSEC_UNIT,    7L * 24 * 60 * 60 },
  2672. X    { "day",        tSEC_UNIT,    1L * 24 * 60 * 60 },
  2673. X    { "hour",        tSEC_UNIT,    60 * 60 },
  2674. X    { "minute",        tSEC_UNIT,    60 },
  2675. X    { "min",        tSEC_UNIT,    60 },
  2676. X    { "second",        tSEC_UNIT,    1 },
  2677. X    { "sec",        tSEC_UNIT,    1 },
  2678. X};
  2679. X
  2680. X/* Timezone table. */
  2681. Xstatic TABLE    TimezoneTable[] = {
  2682. X    { "gmt",    tZONE,     HOUR( 0) },    /* Greenwich Mean */
  2683. X    { "ut",    tZONE,     HOUR( 0) },    /* Universal */
  2684. X    { "utc",    tZONE,     HOUR( 0) },    /* Universal Coordinated */
  2685. X    { "cut",    tZONE,     HOUR( 0) },    /* Coordinated Universal */
  2686. X    { "z",    tZONE,     HOUR( 0) },    /* Greenwich Mean */
  2687. X    { "wet",    tZONE,     HOUR( 0) },    /* Western European */
  2688. X    { "bst",    tDAYZONE,  HOUR( 0) },    /* British Summer */
  2689. X    { "nst",    tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
  2690. X    { "ndt",    tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
  2691. X    { "ast",    tZONE,     HOUR( 4) },    /* Atlantic Standard */
  2692. X    { "adt",    tDAYZONE,  HOUR( 4) },    /* Atlantic Daylight */
  2693. X    { "est",    tZONE,     HOUR( 5) },    /* Eastern Standard */
  2694. X    { "edt",    tDAYZONE,  HOUR( 5) },    /* Eastern Daylight */
  2695. X    { "cst",    tZONE,     HOUR( 6) },    /* Central Standard */
  2696. X    { "cdt",    tDAYZONE,  HOUR( 6) },    /* Central Daylight */
  2697. X    { "mst",    tZONE,     HOUR( 7) },    /* Mountain Standard */
  2698. X    { "mdt",    tDAYZONE,  HOUR( 7) },    /* Mountain Daylight */
  2699. X    { "pst",    tZONE,     HOUR( 8) },    /* Pacific Standard */
  2700. X    { "pdt",    tDAYZONE,  HOUR( 8) },    /* Pacific Daylight */
  2701. X    { "yst",    tZONE,     HOUR( 9) },    /* Yukon Standard */
  2702. X    { "ydt",    tDAYZONE,  HOUR( 9) },    /* Yukon Daylight */
  2703. X    { "akst",    tZONE,     HOUR( 9) },    /* Alaska Standard */
  2704. X    { "akdt",    tDAYZONE,  HOUR( 9) },    /* Alaska Daylight */
  2705. X    { "hst",    tZONE,     HOUR(10) },    /* Hawaii Standard */
  2706. X    { "hast",    tZONE,     HOUR(10) },    /* Hawaii-Aleutian Standard */
  2707. X    { "hadt",    tDAYZONE,  HOUR(10) },    /* Hawaii-Aleutian Daylight */
  2708. X    { "ces",    tDAYZONE,  -HOUR(1) },    /* Central European Summer */
  2709. X    { "cest",    tDAYZONE,  -HOUR(1) },    /* Central European Summer */
  2710. X    { "mez",    tZONE,     -HOUR(1) },    /* Middle European */
  2711. X    { "mezt",    tDAYZONE,  -HOUR(1) },    /* Middle European Summer */
  2712. X    { "cet",    tZONE,     -HOUR(1) },    /* Central European */
  2713. X    { "met",    tZONE,     -HOUR(1) },    /* Middle European */
  2714. X    { "eet",    tZONE,     -HOUR(2) },    /* Eastern Europe */
  2715. X    { "msk",    tZONE,     -HOUR(3) },    /* Moscow Winter */
  2716. X    { "msd",    tDAYZONE,  -HOUR(3) },    /* Moscow Summer */
  2717. X    { "wast",    tZONE,     -HOUR(8) },    /* West Australian Standard */
  2718. X    { "wadt",    tDAYZONE,  -HOUR(8) },    /* West Australian Daylight */
  2719. X    { "hkt",    tZONE,     -HOUR(8) },    /* Hong Kong */
  2720. X    { "cct",    tZONE,     -HOUR(8) },    /* China Coast */
  2721. X    { "jst",    tZONE,     -HOUR(9) },    /* Japan Standard */
  2722. X    { "kst",    tZONE,     -HOUR(9) },    /* Korean Standard */
  2723. X    { "kdt",    tZONE,     -HOUR(9) },    /* Korean Daylight */
  2724. X    { "cast",    tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
  2725. X    { "cadt",    tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
  2726. X    { "east",    tZONE,     -HOUR(10) },    /* Eastern Australian Standard */
  2727. X    { "eadt",    tDAYZONE,  -HOUR(10) },    /* Eastern Australian Daylight */
  2728. X    { "nzst",    tZONE,     -HOUR(12) },    /* New Zealand Standard */
  2729. X    { "nzdt",    tDAYZONE,  -HOUR(12) },    /* New Zealand Daylight */
  2730. X
  2731. X    /* For completeness we include the following entries. */
  2732. X#if 0
  2733. X
  2734. X    /* Duplicate names.  Either they conflict with a zone listed above
  2735. X     * (which is either more likely to be seen or just been in circulation
  2736. X     * longer), or they conflict with another zone in this section and
  2737. X     * we could not reasonably choose one over the other. */
  2738. X    { "fst",    tZONE,     HOUR( 2) },    /* Fernando De Noronha Standard */
  2739. X    { "fdt",    tDAYZONE,  HOUR( 2) },    /* Fernando De Noronha Daylight */
  2740. X    { "bst",    tZONE,     HOUR( 3) },    /* Brazil Standard */
  2741. X    { "est",    tZONE,     HOUR( 3) },    /* Eastern Standard (Brazil) */
  2742. X    { "edt",    tDAYZONE,  HOUR( 3) },    /* Eastern Daylight (Brazil) */
  2743. X    { "wst",    tZONE,     HOUR( 4) },    /* Western Standard (Brazil) */
  2744. X    { "wdt",    tDAYZONE,  HOUR( 4) },    /* Western Daylight (Brazil) */
  2745. X    { "cst",    tZONE,     HOUR( 5) },    /* Chile Standard */
  2746. X    { "cdt",    tDAYZONE,  HOUR( 5) },    /* Chile Daylight */
  2747. X    { "ast",    tZONE,     HOUR( 5) },    /* Acre Standard */
  2748. X    { "adt",    tDAYZONE,  HOUR( 5) },    /* Acre Daylight */
  2749. X    { "cst",    tZONE,     HOUR( 5) },    /* Cuba Standard */
  2750. X    { "cdt",    tDAYZONE,  HOUR( 5) },    /* Cuba Daylight */
  2751. X    { "est",    tZONE,     HOUR( 6) },    /* Easter Island Standard */
  2752. X    { "edt",    tDAYZONE,  HOUR( 6) },    /* Easter Island Daylight */
  2753. X    { "sst",    tZONE,     HOUR(11) },    /* Samoa Standard */
  2754. X    { "ist",    tZONE,     -HOUR(2) },    /* Israel Standard */
  2755. X    { "idt",    tDAYZONE,  -HOUR(2) },    /* Israel Daylight */
  2756. X    { "idt",    tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
  2757. X    { "ist",    tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
  2758. X    { "cst",     tZONE,     -HOUR(8) },    /* China Standard */
  2759. X    { "cdt",     tDAYZONE,  -HOUR(8) },    /* China Daylight */
  2760. X    { "sst",     tZONE,     -HOUR(8) },    /* Singapore Standard */
  2761. X
  2762. X    /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
  2763. X    { "gst",    tZONE,     HOUR( 3) },    /* Greenland Standard */
  2764. X    { "wat",    tZONE,     -HOUR(1) },    /* West Africa */
  2765. X    { "at",    tZONE,     HOUR( 2) },    /* Azores */
  2766. X    { "gst",    tZONE,     -HOUR(10) },    /* Guam Standard */
  2767. X    { "nft",    tZONE,     HOUR(3)+30 }, /* Newfoundland */
  2768. X    { "idlw",    tZONE,     HOUR(12) },    /* International Date Line West */
  2769. X    { "mewt",    tZONE,     -HOUR(1) },    /* Middle European Winter */
  2770. X    { "mest",    tDAYZONE,  -HOUR(1) },    /* Middle European Summer */
  2771. X    { "swt",    tZONE,     -HOUR(1) },    /* Swedish Winter */
  2772. X    { "sst",    tDAYZONE,  -HOUR(1) },    /* Swedish Summer */
  2773. X    { "fwt",    tZONE,     -HOUR(1) },    /* French Winter */
  2774. X    { "fst",    tDAYZONE,  -HOUR(1) },    /* French Summer */
  2775. X    { "bt",    tZONE,     -HOUR(3) },    /* Baghdad */
  2776. X    { "it",    tZONE,     -(HOUR(3)+30) }, /* Iran */
  2777. X    { "zp4",    tZONE,     -HOUR(4) },    /* USSR Zone 3 */
  2778. X    { "zp5",    tZONE,     -HOUR(5) },    /* USSR Zone 4 */
  2779. X    { "ist",    tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
  2780. X    { "zp6",    tZONE,     -HOUR(6) },    /* USSR Zone 5 */
  2781. X    { "nst",    tZONE,     -HOUR(7) },    /* North Sumatra */
  2782. X    { "sst",    tZONE,     -HOUR(7) },    /* South Sumatra */
  2783. X    { "jt",    tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
  2784. X    { "nzt",    tZONE,     -HOUR(12) },    /* New Zealand */
  2785. X    { "idle",    tZONE,     -HOUR(12) },    /* International Date Line East */
  2786. X    { "cat",    tZONE,     HOUR(10) },    /* -- expired 1967 */
  2787. X    { "nt",    tZONE,     HOUR(11) },    /* -- expired 1967 */
  2788. X    { "ahst",    tZONE,     HOUR(10) },    /* -- expired 1983 */
  2789. X    { "hdt",    tDAYZONE,  HOUR(10) },    /* -- expired 1986 */
  2790. X#endif /* 0 */
  2791. X};
  2792. X
  2793. X
  2794. X/* ARGSUSED */
  2795. Xstatic void
  2796. Xdate_error(s)
  2797. X    char    *s;
  2798. X{
  2799. X    /* NOTREACHED */
  2800. X}
  2801. X
  2802. X
  2803. Xstatic time_t
  2804. XToSeconds(Hours, Minutes, Seconds, Meridian)
  2805. X    time_t    Hours;
  2806. X    time_t    Minutes;
  2807. X    time_t    Seconds;
  2808. X    MERIDIAN    Meridian;
  2809. X{
  2810. X    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
  2811. X    return -1;
  2812. X    if (Meridian == MER24) {
  2813. X    if (Hours < 0 || Hours > 23)
  2814. X        return -1;
  2815. X    }
  2816. X    else {
  2817. X    if (Hours < 1 || Hours > 12)
  2818. X        return -1;
  2819. X    if (Hours == 12)
  2820. X        Hours = 0;
  2821. X    if (Meridian == MERpm)
  2822. X        Hours += 12;
  2823. X    }
  2824. X    return (Hours * 60L + Minutes) * 60L + Seconds;
  2825. X}
  2826. X
  2827. X
  2828. Xstatic time_t
  2829. XConvert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, dst)
  2830. X    time_t    Month;
  2831. X    time_t    Day;
  2832. X    time_t    Year;
  2833. X    time_t    Hours;
  2834. X    time_t    Minutes;
  2835. X    time_t    Seconds;
  2836. X    MERIDIAN    Meridian;
  2837. X    DSTMODE    dst;
  2838. X{
  2839. X    static int    DaysNormal[13] = {
  2840. X    0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  2841. X    };
  2842. X    static int    DaysLeap[13] = {
  2843. X    0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  2844. X    };
  2845. X    static int    LeapYears[] = {
  2846. X    1972, 1976, 1980, 1984, 1988, 1992, 1996,
  2847. X    2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
  2848. X    };
  2849. X    register int    *yp;
  2850. X    register int    *mp;
  2851. X    register time_t    Julian;
  2852. X    register int    i;
  2853. X    time_t        tod;
  2854. X
  2855. X    if (Year < 0)
  2856. X    Year = -Year;
  2857. X    if (Year < 100)
  2858. X    Year += 1900;
  2859. X    if (Year < EPOCH)
  2860. X    Year += 100;
  2861. X    for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
  2862. X    if (Year == *yp) {
  2863. X        mp = DaysLeap;
  2864. X        break;
  2865. X    }
  2866. X    if (Year < EPOCH || Year > END_OF_TIME
  2867. X     || Month < 1 || Month > 12
  2868. X     /* NOSTRICT *//* conversion from long may lose accuracy */
  2869. X     || Day < 1 || Day > mp[(int)Month])
  2870. X    return -1;
  2871. X
  2872. X    Julian = Day - 1 + (Year - EPOCH) * 365;
  2873. X    for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
  2874. X    if (Year <= *yp)
  2875. X        break;
  2876. X    for (i = 1; i < Month; i++)
  2877. X    Julian += *++mp;
  2878. X    Julian *= SECSPERDAY;
  2879. X    Julian += yyTimezone * 60L;
  2880. X    if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
  2881. X    return -1;
  2882. X    Julian += tod;
  2883. X    tod = Julian;
  2884. X    if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
  2885. X    Julian -= DST_OFFSET * 60L * 60L;
  2886. X    return Julian;
  2887. X}
  2888. X
  2889. X
  2890. Xstatic time_t
  2891. XDSTcorrect(Start, Future)
  2892. X    time_t    Start;
  2893. X    time_t    Future;
  2894. X{
  2895. X    time_t    StartDay;
  2896. X    time_t    FutureDay;
  2897. X
  2898. X    StartDay = (localtime(&Start)->tm_hour + 1) % 24;
  2899. X    FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
  2900. X    return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60L * 60L;
  2901. X}
  2902. X
  2903. X
  2904. Xstatic time_t
  2905. XRelativeMonth(Start, RelMonth)
  2906. X    time_t    Start;
  2907. X    time_t    RelMonth;
  2908. X{
  2909. X    struct tm    *tm;
  2910. X    time_t    Month;
  2911. X    time_t    Year;
  2912. X
  2913. X    tm = localtime(&Start);
  2914. X    Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
  2915. X    Year = Month / 12;
  2916. X    Month = Month % 12 + 1;
  2917. X    return DSTcorrect(Start,
  2918. X        Convert(Month, (time_t)tm->tm_mday, Year,
  2919. X        (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
  2920. X        MER24, DSTmaybe));
  2921. X}
  2922. X
  2923. X
  2924. Xstatic int
  2925. XLookupWord(buff, length)
  2926. X    char        *buff;
  2927. X    register int    length;
  2928. X{
  2929. X    register char    *p;
  2930. X    register char    *q;
  2931. X    register TABLE    *tp;
  2932. X    register int    c;
  2933. X
  2934. X    p = buff;
  2935. X    c = p[0];
  2936. X
  2937. X    /* See if we have an abbreviation for a month. */
  2938. X    if (length == 3 || (length == 4 && p[3] == '.'))
  2939. X    for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
  2940. X        q = tp->name;
  2941. X        if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
  2942. X        yylval.Number = tp->value;
  2943. X        return tp->type;
  2944. X        }
  2945. X    }
  2946. X    else
  2947. X    for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
  2948. X        if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
  2949. X        yylval.Number = tp->value;
  2950. X        return tp->type;
  2951. X        }
  2952. X
  2953. X    /* Try for a timezone. */
  2954. X    for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
  2955. X    if (c == tp->name[0] && p[1] == tp->name[1]
  2956. X     && strcmp(p, tp->name) == 0) {
  2957. X        yylval.Number = tp->value;
  2958. X        return tp->type;
  2959. X    }
  2960. X
  2961. X    /* Try the units table. */
  2962. X    for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
  2963. X    if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
  2964. X        yylval.Number = tp->value;
  2965. X        return tp->type;
  2966. X    }
  2967. X
  2968. X    /* Strip off any plural and try the units table again. */
  2969. X    if (--length > 0 && p[length] == 's') {
  2970. X    p[length] = '\0';
  2971. X    for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
  2972. X        if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
  2973. X        p[length] = 's';
  2974. X        yylval.Number = tp->value;
  2975. X        return tp->type;
  2976. X        }
  2977. X    p[length] = 's';
  2978. X    }
  2979. X    length++;
  2980. X
  2981. X    /* Drop out any periods. */
  2982. X    for (p = buff, q = (char*)buff; *q; q++)
  2983. X    if (*q != '.')
  2984. X        *p++ = *q;
  2985. X    *p = '\0';
  2986. X
  2987. X    /* Try the meridians. */
  2988. X    if (buff[1] == 'm' && buff[2] == '\0') {
  2989. X    if (buff[0] == 'a') {
  2990. X        yylval.Meridian = MERam;
  2991. X        return tMERIDIAN;
  2992. X    }
  2993. X    if (buff[0] == 'p') {
  2994. X        yylval.Meridian = MERpm;
  2995. X        return tMERIDIAN;
  2996. X    }
  2997. X    }
  2998. X
  2999. X    /* If we saw any periods, try the timezones again. */
  3000. X    if (p - buff != length) {
  3001. X    c = buff[0];
  3002. X    for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
  3003. X        if (c == tp->name[0] && p[1] == tp->name[1]
  3004. X        && strcmp(p, tp->name) == 0) {
  3005. X        yylval.Number = tp->value;
  3006. X        return tp->type;
  3007. X        }
  3008. X    }
  3009. X
  3010. X    /* Unknown word -- assume GMT timezone. */
  3011. X    yylval.Number = 0;
  3012. X    return tZONE;
  3013. X}
  3014. X
  3015. X
  3016. Xint
  3017. Xdate_lex()
  3018. X{
  3019. X    register char    c;
  3020. X    register char    *p;
  3021. X    char        buff[20];
  3022. X    register int    sign;
  3023. X    register int    i;
  3024. X    register int    nesting;
  3025. X
  3026. X    for ( ; ; ) {
  3027. X    /* Get first character after the whitespace. */
  3028. X    for ( ; ; ) {
  3029. X        while (isspace(*yyInput))
  3030. X        yyInput++;
  3031. X        c = *yyInput;
  3032. X
  3033. X        /* Ignore RFC 822 comments, typically time zone names. */
  3034. X        if (c != LPAREN)
  3035. X        break;
  3036. X        for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
  3037. X        if (c == LPAREN)
  3038. X            nesting++;
  3039. X        else if (!IS7BIT(c) || c == '\0' || c == '\r'
  3040. X             || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
  3041. X            /* Lexical error: bad comment. */
  3042. X            return '?';
  3043. X        yyInput++;
  3044. X    }
  3045. X
  3046. X    /* A number? */
  3047. X    if (isdigit(c) || c == '-' || c == '+') {
  3048. X        if (c == '-' || c == '+') {
  3049. X        sign = c == '-' ? -1 : 1;
  3050. X        yyInput++;
  3051. X        if (!isdigit(*yyInput))
  3052. X            /* Skip the plus or minus sign. */
  3053. X            continue;
  3054. X        }
  3055. X        else
  3056. X        sign = 0;
  3057. X        for (i = 0; (c = *yyInput++) != '\0' && isdigit(c); )
  3058. X        i = 10 * i + c - '0';
  3059. X        yyInput--;
  3060. X        yylval.Number = sign < 0 ? -i : i;
  3061. X        return sign ? tSNUMBER : tUNUMBER;
  3062. X    }
  3063. X
  3064. X    /* A word? */
  3065. X    if (isalpha(c)) {
  3066. X        for (p = buff; (c = *yyInput++) == '.' || isalpha(c); )
  3067. X        if (p < &buff[sizeof buff - 1])
  3068. X            *p++ = isupper(c) ? tolower(c) : c;
  3069. X        *p = '\0';
  3070. X        yyInput--;
  3071. X        return LookupWord(buff, p - buff);
  3072. X    }
  3073. X
  3074. X    return *yyInput++;
  3075. X    }
  3076. X}
  3077. X
  3078. X
  3079. Xtime_t
  3080. Xparsedate(p)
  3081. X    char        *p;
  3082. X{
  3083. X    extern int        date_parse();
  3084. X    time_t        Start;
  3085. X
  3086. X    yyInput = p;
  3087. X
  3088. X    yyYear = 0;
  3089. X    yyMonth = 0;
  3090. X    yyDay = 0;
  3091. X    yyTimezone = 0;
  3092. X    yyDSTmode = DSTmaybe;
  3093. X    yyHour = 0;
  3094. X    yyMinutes = 0;
  3095. X    yySeconds = 0;
  3096. X    yyMeridian = MER24;
  3097. X    yyRelSeconds = 0;
  3098. X    yyRelMonth = 0;
  3099. X    yyHaveDate = 0;
  3100. X    yyHaveRel = 0;
  3101. X    yyHaveTime = 0;
  3102. X
  3103. X    if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
  3104. X    return -1;
  3105. X
  3106. X    if (yyHaveDate || yyHaveTime) {
  3107. X    Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
  3108. X            yyMeridian, yyDSTmode);
  3109. X    if (Start < 0)
  3110. X        return -1;
  3111. X    }
  3112. X    else
  3113. X    return -1;
  3114. X
  3115. X    Start += yyRelSeconds;
  3116. X    if (yyRelMonth)
  3117. X    Start += RelativeMonth(Start, yyRelMonth);
  3118. X
  3119. X    /* Have to do *something* with a legitimate -1 so it's distinguishable
  3120. X     * from the error return value.  (Alternately could set errno on error.) */
  3121. X    return Start == -1 ? 0 : Start;
  3122. X}
  3123. X
  3124. X
  3125. X#ifdef TEST
  3126. X
  3127. X#if YYDEBUG
  3128. Xextern int    yydebug;
  3129. X#endif /* YYDEBUG */
  3130. X
  3131. X/* ARGSUSED */
  3132. Xint
  3133. Xmain(ac, av)
  3134. X    int        ac;
  3135. X    char    *av[];
  3136. X{
  3137. X    char    buff[128];
  3138. X    time_t    d;
  3139. X
  3140. X#if YYDEBUG
  3141. X    yydebug = 1;
  3142. X#endif /* YYDEBUG */
  3143. X
  3144. X    (void)printf("Enter date, or blank line to exit.\n\t> ");
  3145. X    for ( ; ; ) {
  3146. X    (void)printf("\t> ");
  3147. X    (void)fflush(stdout);
  3148. X    if (gets(buff) == NULL || buff[0] == '\n')
  3149. X        break;
  3150. X#if YYDEBUG
  3151. X    if (strcmp(buff, "yydebug") == 0) {
  3152. X        yydebug = !yydebug;
  3153. X        printf("yydebug = %s\n", yydebug ? "on" : "off");
  3154. X        continue;
  3155. X    }
  3156. X#endif /* YYDEBUG */
  3157. X    d = parsedate(buff, (TIMEINFO *)NULL);
  3158. X    if (d == -1)
  3159. X        (void)printf("Bad format - couldn't convert.\n");
  3160. X    else
  3161. X        (void)printf("%s", ctime(&d));
  3162. X    }
  3163. X
  3164. X    exit(0);
  3165. X    /* NOTREACHED */
  3166. X}
  3167. X#endif /* TEST */
  3168. END_OF_FILE
  3169. if test 21261 -ne `wc -c <'parsedate.y'`; then
  3170.     echo shar: \"'parsedate.y'\" unpacked with wrong size!
  3171. fi
  3172. # end of 'parsedate.y'
  3173. fi
  3174. echo shar: End of archive 2 \(of 4\).
  3175. cp /dev/null ark2isdone
  3176. MISSING=""
  3177. for I in 1 2 3 4 ; do
  3178.     if test ! -f ark${I}isdone ; then
  3179.     MISSING="${MISSING} ${I}"
  3180.     fi
  3181. done
  3182. if test "${MISSING}" = "" ; then
  3183.     echo You have unpacked all 4 archives.
  3184.     rm -f ark[1-9]isdone
  3185. else
  3186.     echo You still need to unpack the following archives:
  3187.     echo "        " ${MISSING}
  3188. fi
  3189. ##  End of shell archive.
  3190. exit 0
  3191.