home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume25 / trn / part11 / mt-process.c
Encoding:
C/C++ Source or Header  |  1991-12-02  |  35.7 KB  |  1,496 lines

  1. /* $Id: mt-process.c,v 4.4.3.1 1991/11/22 04:12:15 davison Trn $
  2. **
  3. ** $Log: mt-process.c,v $
  4. ** Revision 4.4.3.1  1991/11/22  04:12:15  davison
  5. ** Trn Release 2.0
  6. ** 
  7. */
  8.  
  9. #include "EXTERN.h"
  10. #include "common.h"
  11. #include "threads.h"
  12. #include "mthreads.h"
  13. #include "ndir.h"
  14. #ifdef SERVER
  15. #include "server.h"
  16. #endif
  17. #include "INTERN.h"
  18. #include "bits.h"
  19.  
  20. #include <time.h>
  21. #ifndef TZSET
  22. # include <sys/timeb.h>
  23. #endif
  24.  
  25. #if defined(SERVER) && !defined(USLEEP)
  26. # include <sys/time.h>
  27. #endif
  28.  
  29. #define buff buf
  30.  
  31. char references[1024];
  32.  
  33. char subject_str[80];
  34. bool found_Re;
  35.  
  36. char author_str[20];
  37.  
  38. extern int log_verbosity, slow_down;
  39.  
  40. long num;
  41.  
  42. DOMAIN *next_domain;
  43.  
  44. void insert_article(), expire(), trim_roots(), order_roots(), trim_authors();
  45. void make_root(), use_root(), merge_roots(), set_root(), unlink_root();
  46. void link_child(), unlink_child();
  47. void free_article(), free_domain(), free_subject(), free_root(), free_author();
  48. void get_subject_str(), get_author_str();
  49. ARTICLE *get_article();
  50. SUBJECT *new_subject();
  51. AUTHOR *new_author();
  52.  
  53. #ifdef TZSET
  54. extern time_t tnow;
  55. extern long timezone;
  56. #else
  57. extern struct timeb ftnow;
  58. #endif
  59.  
  60. #ifndef SERVER
  61. static FILE *fp_article;
  62. #endif
  63.  
  64. /* Given the upper/lower bounds of the articles in the current group, add all
  65. ** the ones that we don't know about and remove all the ones that have expired.
  66. ** The current directory must be the newsgroup's spool directory.
  67. */
  68. void
  69. process_articles(first_article, last_article)
  70. ART_NUM first_article, last_article;
  71. {
  72.     register char *cp, *str;
  73.     register ARTICLE *article;
  74.     register ART_NUM i;
  75.     time_t date;
  76.     bool has_xrefs;
  77.     int len;
  78. #ifdef SERVER
  79.     bool orig_extra = extra_expire;
  80. #endif
  81. #ifdef TMPTHREAD
  82.     extern int start;
  83. #else
  84.     int start = total.last + 1;
  85. #endif
  86.     extern int errno;
  87.     extern int sys_nerr;
  88.     extern char *sys_errlist[];
  89.  
  90.     if (first_article > start) {
  91.     start = first_article;
  92.     }
  93.     added_count = last_article - start + 1;
  94.     if (added_count < 0) {
  95.     added_count = 0;
  96.     } else if (added_count > 1000) {
  97.     /* Don't overwork ourselves the first time */
  98.     added_count = 1000;
  99.     start = last_article - 1000 + 1;
  100.     }
  101.     expired_count = 0;
  102.  
  103. #ifdef TMPTHREAD
  104.     if (added_count) {
  105.     printf("\nThreading %d article%s...", added_count,
  106.         added_count == 1 ? nullstr : "s"), fflush(stdout);
  107.     }
  108. #endif
  109.  
  110.     for (i = start; i <= last_article; i++) {
  111. #ifdef TMPTHREAD
  112.     if ((i - start) % 20 == 0) {
  113.         if (i - start) {
  114.         printf("%d...", i - start), fflush(stdout);
  115.         }
  116.     }
  117. #endif
  118. #ifdef SERVER
  119.     if (slow_down) {
  120.         usleep(slow_down);
  121.     }
  122.     sprintf(buff, "HEAD %ld", (long)i);
  123.     put_server(buff);
  124.     if (get_server(buff, sizeof buff) < 0 || *buff == CHAR_FATAL) {
  125.         last_article = i - 1;
  126.         extra_expire = FALSE;
  127.         break;
  128.     }
  129.     if (*buff != CHAR_OK) {
  130.         added_count--;
  131.         continue;
  132.     }
  133. #else
  134.     /* Open article in current directory. */
  135.     sprintf(buff, "%ld", (long)i);
  136.     /* Set errno for purely paranoid reasons */
  137.     errno = 0;
  138.     if ((fp_article = fopen(buff, "r")) == Nullfp) {
  139.         /* Missing files are ok -- they've just been expired or canceled */
  140.         if (errno != 0 && errno != ENOENT) {
  141.         if (errno < 0 || errno > sys_nerr) {
  142.             log_error("Can't open `%s': Error %d.\n", buff, errno);
  143.         } else {
  144.             log_error("Can't open `%s': %s.\n", buff,
  145.               sys_errlist[errno]);
  146.         }
  147.         }
  148.         added_count--;
  149.         continue;
  150.     }
  151. #endif
  152.  
  153.     article = Nullart;
  154.     *references = '\0';
  155.     *author_str = '\0';
  156.     *subject_str = '\0';
  157.     found_Re = 0;
  158.     date = 0;
  159.     has_xrefs = FALSE;
  160.  
  161. #ifdef SERVER
  162.     while (get_server(cp = buff, sizeof buff) == 0) {
  163.       process_line:
  164.         if (*cp == '.') {
  165.         if (cp[1]) {
  166.             log_error("Header line starts with '.'! [%ld].\n",
  167.                 (long)i);
  168.             continue;
  169.         }
  170.         break;
  171.         }
  172. #else
  173.     while ((cp = fgets(buff, sizeof buff, fp_article)) != Nullch) {
  174.       process_line:
  175.         if (*cp == '\n') {        /* check for end of header */
  176.         break;            /* break out when found */
  177.         }
  178. #endif
  179.         if ((unsigned char)*cp <= ' ') {     /* skip continuation lines */
  180.         continue;        /* (except references -- see below) */
  181.         }
  182.         if ((str = index(cp, ':')) == Nullch) {
  183. #ifdef SERVER
  184.         if (log_verbosity) {
  185.             log_error("Header line missing colon! [%ld].\n", (long)i);
  186.         }
  187.         continue;        /* skip bogus header line */
  188. #else
  189.         break;            /* end of header if no colon found */
  190. #endif
  191.         }
  192.         if ((len = str - cp) > 10) {
  193.         continue;        /* skip keywords > 10 chars */
  194.         }
  195. #ifndef SERVER
  196.         cp[strlen(cp)-1] = '\0';    /* remove newline */
  197. #endif
  198.         while (cp < str) {        /* lower-case the keyword */
  199.         if ((unsigned char)*cp <= ' ') { /* stop at any whitespace */
  200.             break;
  201.         }
  202.         if (isupper(*cp)) {
  203.             *cp = tolower(*cp);
  204.         }
  205.         cp++;
  206.         }
  207.         *cp = '\0';
  208.         cp = buff;
  209.         if (len == 4 && strEQ(cp, "date")) {
  210. #ifdef TZSET
  211.             date = get_date(str + 1, tnow, timezone);
  212. #else
  213.         date = get_date(str + 1, ftnow.time, (long) ftnow.timezone);
  214. #endif
  215.         } else
  216.         if (len == 4 && strEQ(cp, "from")) {
  217.         get_author_str(str + 1);
  218.         } else
  219.         if (len == 4 && strEQ(cp, "xref")) {
  220.         has_xrefs = TRUE;
  221.         } else
  222.         if (len == 7 && strEQ(cp, "subject")) {
  223.         get_subject_str(str + 1);
  224.         } else
  225.         if (len == 10 && strEQ(cp, "message-id")) {
  226.         if (!article) {
  227.             article = get_article(str + 1);
  228.         } else {
  229.             if (log_verbosity) {
  230.             log_error("Found multiple Message-IDs! [%ld].\n",
  231.                 (long)i);
  232.             }
  233.         }
  234.         } else
  235.         if (len == 10 && strEQ(cp, "references")) {
  236.         /* include preceding space in saved reference */
  237.         len = strlen(str + 1);
  238.         bcopy(str + 1, references, len + 1);
  239.         str = references + len;
  240.         /* check for continuation lines */
  241. #ifdef SERVER
  242.         while (get_server(cp = buff, sizeof buff) == 0) {
  243. #else
  244.         while ((cp = fgets(buff, sizeof buff, fp_article)) != Nullch) {
  245. #endif
  246.             if (*cp != ' ' && *cp != '\t') {
  247.             goto process_line;
  248.             }
  249.             while (*++cp == ' ' || *cp == '\t') {
  250.             ;
  251.             }
  252.             *--cp = ' ';
  253.             /* If the references are too long, shift them over to
  254.             ** always save the most recent ones.
  255.             */
  256.             if ((len += strlen(cp)) > 1023) {
  257.             strcpy(buff, buff + len - 1023);
  258.             str -= len - 1023;
  259.             len = 1023;
  260.             }
  261.             strcpy(str, cp);
  262.         }/* while */
  263.         break;
  264.         }/* if */
  265.     }/* while */
  266.     if (article) {
  267.         num = i;
  268.         insert_article(article, date);
  269.         if (has_xrefs) {
  270.         article->flags |= HAS_XREFS;
  271.         }
  272.     } else {
  273.         if (log_verbosity) {
  274.         log_error("Message-ID line missing! [%ld].\n", (long)i);
  275.         }
  276.     }
  277. #ifndef SERVER
  278.     fclose(fp_article);
  279. #endif
  280.     }
  281.  
  282.     if (extra_expire || first_article > total.first) {
  283.     absfirst = first_article;
  284.     lastart = last_article;
  285.     expire(first_article <= last_article ? extra_expire : FALSE);
  286.     }
  287.     trim_roots();
  288.     order_roots();
  289.     trim_authors();
  290.  
  291.     total.first = first_article;
  292.     total.last = last_article;
  293. #ifdef SERVER
  294.     extra_expire = orig_extra;
  295. #endif
  296. }
  297.  
  298. /* Search all articles for numbers less than new_first.  Traverse the list
  299. ** using the domain links so we don't have to deal with the tree structure.
  300. ** If extra is true, list all articles in the directory to setup a bitmap
  301. ** with the existing articles marked as 'read', and drop everything that
  302. ** isn't there.
  303. */
  304. void
  305. expire(extra)
  306. bool_int extra;
  307. {
  308.     register DOMAIN *domain;
  309.     register ARTICLE *article, *next_art, *hold;
  310. #ifndef TMPTHREAD
  311.     register ART_NUM art;
  312. #ifndef SERVER
  313.     register DIR *dirp;
  314. #endif
  315. #endif
  316.  
  317. #ifdef TMPTHREAD
  318.     extra = FALSE;
  319. #else
  320.     if (extra) {
  321.       MEM_SIZE ctlsize;
  322.  
  323.     /* Allocate a bitmap large enough for absfirst thru lastart. */
  324. #ifndef lint
  325.     ctlsize = (MEM_SIZE)(OFFSET(lastart)/BITSPERBYTE+20);
  326. #endif
  327.     ctlarea = safemalloc(ctlsize);
  328.     bzero(ctlarea, ctlsize);
  329.  
  330.     /* List all articles and use ctl_set() to keep track of what's there. */
  331. #ifdef SERVER
  332.     sprintf(buff, "XHDR message-id %ld-%ld", (long)absfirst, (long)lastart);
  333.     put_server(buff);
  334.     if (get_server(buff, sizeof buff) == 0 && *buff == CHAR_OK) {
  335.         while (1) {
  336.         if (get_server(buff, sizeof buff) < 0) {
  337.             extra = 0;
  338.             break;
  339.         }
  340.         if (*buff == '.') {
  341.             break;
  342.         }
  343.         art = atol(buff);
  344.         if (art >= absfirst && art <= lastart) {
  345.             ctl_set(art);
  346.         }
  347.         }
  348.     } else {
  349.         extra = 0;
  350.     }
  351. #else
  352.     if ((dirp = opendir(".")) != 0) {
  353.       register struct DIRTYPE *dp;
  354.  
  355.         while ((dp = readdir(dirp)) != Null(struct DIRTYPE *)) {
  356.           register char *p;
  357.  
  358.         for (p = dp->d_name; *p; p++) {
  359.             if (!isdigit(*p)) {
  360.             goto nope;
  361.             }
  362.         }
  363.         art = atol(dp->d_name);
  364.         if (art >= absfirst && art <= lastart) {
  365.             ctl_set(art);
  366.         }
  367.       nope: ;
  368.         }
  369.         closedir(dirp);
  370.     } else {
  371.         extra = 0;
  372.     }
  373. #endif
  374.     } else {
  375.     ctlarea = Nullch;
  376.     }
  377. #endif /* TMPTHREAD */
  378.  
  379.     for (domain = &unk_domain; domain; domain = next_domain) {
  380.     next_domain = domain->link;
  381.     for (article = domain->ids; article; article = next_art) {
  382.         next_art = article->id_link;
  383.         if (!article->subject) {
  384.         continue;
  385.         }
  386.         if (article->num < absfirst
  387. #ifndef TMPTHREAD
  388.          || (extra && !ctl_read(article->num))
  389. #endif
  390.        ) {
  391.         article->subject->count--;
  392.         article->subject = 0;
  393.         article->flags &= ~HAS_XREFS;
  394.         article->author->count--;
  395.         article->author = 0;
  396.         /* Free expired article if it has no children.  Then check
  397.         ** if the parent(s) are also fake and can be freed.  We'll
  398.         ** free any empty roots later.
  399.         */
  400.         while (!article->children) {
  401.             hold = article->parent;
  402.             unlink_child(article);
  403.             free_article(article);
  404.             if (hold && !hold->subject) {
  405.             if ((article = hold) == next_art) {
  406.                 next_art = next_art->id_link;
  407.             }
  408.             } else {
  409.             break;
  410.             }
  411.         }
  412.         expired_count++;
  413.         }/* if */
  414.     }/* for */
  415.     }/* for */
  416.     next_domain = Null(DOMAIN*);
  417.  
  418. #ifndef TMPTHREAD
  419.     safefree(&ctlarea);
  420. #endif
  421. }
  422.  
  423. /* Trim the article chains down so that we don't have more than one faked
  424. ** article between the root and any real ones.
  425. */
  426. void
  427. trim_roots()
  428. {
  429.     register ROOT *root, *last_root;
  430.     register ARTICLE *article, *next;
  431.     register SUBJECT *subject, *last_subj;
  432.     register int found;
  433.  
  434. #ifndef lint
  435.     last_root = (ROOT *)&root_root;
  436. #else
  437.     last_root = Null(ROOT*);
  438. #endif
  439.     for (root = root_root; root; root = last_root->link) {
  440.     for (article = root->articles; article; article = article->siblings) {
  441.         /* If an article has no subject, it is a "fake" reference node.
  442.         ** If all of its immediate children are also fakes, delete it
  443.         ** and graduate the children to the root.  If everyone is fake,
  444.         ** the chain dies.
  445.         */
  446.         while (!article->subject) {
  447.         found = 0;
  448.         for (next = article->children; next; next = next->siblings) {
  449.             if (next->subject) {
  450.             found = 1;
  451.             break;
  452.             }
  453.         }
  454.         if (!found) {
  455.             /* Remove this faked article and move all its children
  456.             ** up to the root.
  457.             */
  458.             next = article->children;
  459.             unlink_child(article);
  460.             free_article(article);
  461.             for (article = next; article; article = next) {
  462.             next = article->siblings;
  463.             article->parent = Nullart;
  464.             link_child(article);
  465.             }
  466.             article = root->articles;    /* start this root over */
  467.         } else {
  468.             break;            /* else, on to next article */
  469.         }
  470.         }
  471.     }
  472.     /* Free all unused subject strings.  Begin by trying to find a
  473.     ** subject for the root's pointer.
  474.     */
  475.     for (subject = root->subjects; subject && !subject->count; subject = root->subjects) {
  476.         root->subjects = subject->link;
  477.         free_subject(subject);
  478.         root->subject_cnt--;
  479.     }
  480.     /* Then free up any unused intermediate subjects.
  481.     */
  482.     if ((last_subj = subject) != Null(SUBJECT*)) {
  483.         while ((subject = subject->link) != Null(SUBJECT*)) {
  484.         if (!subject->count) {
  485.             last_subj->link = subject->link;
  486.             free_subject(subject);
  487.             root->subject_cnt--;
  488.             subject = last_subj;
  489.         } else {
  490.             last_subj = subject;
  491.         }
  492.         }
  493.     }
  494.     /* Now, free all roots without articles.  Flag unexpeced errors.
  495.     */
  496.     if (!root->articles) {
  497.         if (root->subjects) {
  498.         log_error("** Empty root still had subjects remaining! **\n");
  499.         }
  500.         last_root->link = root->link;
  501.         free_root(root);
  502.     } else {
  503.         last_root = root;
  504.     }
  505.     }
  506. }
  507.  
  508. /* Descend the author list, find any author names that aren't used
  509. ** anymore and free them.
  510. */
  511. void
  512. trim_authors()
  513. {
  514.     register AUTHOR *author, *last_author;
  515.  
  516. #ifndef lint
  517.     last_author = (AUTHOR *)&author_root;
  518. #else
  519.     last_author = Null(AUTHOR*);
  520. #endif
  521.     for (author = author_root; author; author = last_author->link) {
  522.     if (!author->count) {
  523.         last_author->link = author->link;
  524.         free_author(author);
  525.     } else {
  526.         last_author = author;
  527.     }
  528.     }
  529. }
  530.  
  531. /* Reorder the roots to place the oldest ones first (age determined by
  532. ** date of oldest article).
  533. */
  534. void
  535. order_roots()
  536. {
  537.     register ROOT *root, *next, *search;
  538.  
  539.     /* If we don't have at least two roots, we're done! */
  540.     if (!(root = root_root) || !(next = root->link)) {
  541.     return;                        /* RETURN */
  542.     }
  543.     /* Break the old list off after the first root, and then start
  544.     ** inserting the roots into the list by date.
  545.     */
  546.     root->link = Null(ROOT*);
  547.     while ((root = next) != Null(ROOT*)) {
  548.     next = next->link;
  549.     if ((search = root_root)->articles->date >= root->articles->date) {
  550.         root->link = root_root;
  551.         root_root = root;
  552.     } else {
  553.         while (search->link
  554.          && search->link->articles->date < root->articles->date) {
  555.         search = search->link;
  556.         }
  557.         root->link = search->link;
  558.         search->link = root;
  559.     }
  560.     }
  561. }
  562.  
  563. #define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y))
  564.  
  565. /* Parse the subject into 72 characters or less.  Remove any "Re[:^]"s from
  566. ** the front (noting that it's there), and any "(was: old)" stuff from
  567. ** the end.  Then, compact multiple whitespace characters into one space,
  568. ** trimming leading/trailing whitespace.  If it's still too long, unmercifully
  569. ** cut it off.  We don't bother with subject continuation lines either.
  570. */
  571. void
  572. get_subject_str(str)
  573. register char *str;
  574. {
  575.     register char *cp;
  576.     register int len;
  577.  
  578.     while (*str && (unsigned char)*str <= ' ') {
  579.     str++;
  580.     }
  581.     if (!*str) {
  582.     bcopy("<None>", subject_str, 7);
  583.     return;                        /* RETURN */
  584.     }
  585.     cp = str;
  586.     while (EQ(cp[0], 'r') && EQ(cp[1], 'e')) {    /* check for Re: */
  587.     cp += 2;
  588.     if (*cp == '^') {                /* allow Re^2: */
  589.         while (*++cp <= '9' && *cp >= '0') {
  590.         ;
  591.         }
  592.     }
  593.     if (*cp != ':') {
  594.         break;
  595.     }
  596.     while (*++cp == ' ') {
  597.         ;
  598.     }
  599.     found_Re = 1;
  600.     str = cp;
  601.     }
  602.     /* Remove "(was: oldsubject)", because we already know the old subjects.
  603.     ** Also match "(Re: oldsubject)".  Allow possible spaces after the ('s.
  604.     */
  605.     for (cp = str; (cp = index(cp+1, '(')) != Nullch;) {
  606.     while (*++cp == ' ') {
  607.         ;
  608.     }
  609.     if (EQ(cp[0], 'w') && EQ(cp[1], 'a') && EQ(cp[2], 's')
  610.      && (cp[3] == ':' || cp[3] == ' '))
  611.     {
  612.         *--cp = '\0';
  613.         break;
  614.     }
  615.     if (EQ(cp[0], 'r') && EQ(cp[1], 'e')
  616.      && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':'))) {
  617.         *--cp = '\0';
  618.         break;
  619.     }
  620.     }
  621.     /* Copy subject to a temporary string, compacting multiple spaces/tabs */
  622.     for (len = 0, cp = subject_str; len < 72 && *str; len++) {
  623.     if ((unsigned char)*str <= ' ') {
  624.         while (*++str && (unsigned char)*str <= ' ') {
  625.         ;
  626.         }
  627.         *cp++ = ' ';
  628.     } else {
  629.         *cp++ = *str++;
  630.     }
  631.     }
  632.     if (cp[-1] == ' ') {
  633.     cp--;
  634.     }
  635.     *cp = '\0';
  636. }
  637.  
  638. /* Try to fit the author name in 16 bytes.  Use the comment portion in
  639. ** parenthesis if present.  Cut off non-commented names at the '@' or '%'.
  640. ** Then, put as many characters as we can into the 16 bytes, packing multiple
  641. ** whitespace characters into a single space.
  642. ** We might want to implement a nice name shortening algorithm sometime.
  643. */
  644. void
  645. get_author_str(str)
  646. char *str;
  647. {
  648.     register char *cp, *cp2;
  649.  
  650.     if ((cp = index(str, '(')) != Nullch) {
  651.     str = cp+1;
  652.     if ((cp = rindex(str, ')')) != Nullch) {
  653.         *cp = '\0';
  654.     }
  655.     } else {
  656.     if ((cp = index(str, '@')) != Nullch) {
  657.         *cp = '\0';
  658.     }
  659.     if ((cp = index(str, '%')) != Nullch) {
  660.         *cp = '\0';
  661.     }
  662.     }
  663.     for (cp = str, cp2 = author_str; *cp && cp2-author_str < 16;) {
  664.     /* Pack white space and turn ctrl-chars into spaces. */
  665.     if (*cp <= ' ') {
  666.         while (*++cp && *cp <= ' ') {
  667.         ;
  668.         }
  669.         if (cp2 != author_str) {
  670.         *cp2++ = ' ';
  671.         }
  672.     } else {
  673.         *cp2++ = *cp++;
  674.     }
  675.     }
  676.     *cp2 = '\0';
  677. }
  678.  
  679. /* Take a message-id and see if we already know about it.  If so, return it.
  680. ** If not, create it.  We separate the id into its id@domain parts, and
  681. ** link all the unique ids to one copy of the domain portion.  This saves
  682. ** a bit of space.
  683. */
  684. ARTICLE *
  685. get_article(msg_id)
  686. char *msg_id;
  687. {
  688.     register DOMAIN *domain;
  689.     register ARTICLE *article;
  690.     register char *cp, *after_at;
  691.  
  692.     /* Take message id, break it up into <id@domain>, and try to match it.
  693.     */
  694.     while (*msg_id == ' ') {
  695.     msg_id++;
  696.     }
  697.     cp = msg_id + strlen(msg_id) - 1;
  698.     if (msg_id >= cp) {
  699.     if (log_verbosity) {
  700.         log_error("Message-ID is empty! [%ld]\n", num);
  701.     }
  702.     return Nullart;
  703.     }
  704.     if (*msg_id++ != '<') {
  705.     if (log_verbosity) {
  706.         log_error("Message-ID doesn't start with '<' [%ld]\n", num);
  707.     }
  708.     msg_id--;
  709.     }
  710.     if (*cp != '>') {
  711.     if (log_verbosity) {
  712.         log_error("Message-ID doesn't end with '>' [%ld]\n", num);
  713.     }
  714.     cp++;
  715.     }
  716.     *cp = '\0';
  717.     if (msg_id == cp) {
  718.     if (log_verbosity) {
  719.         log_error("Message-ID is null! [%ld]\n", num);
  720.     }
  721.     return Nullart;
  722.     }
  723.  
  724.     if ((after_at = index(msg_id, '@')) == Nullch) {
  725.     domain = &unk_domain;
  726.     } else {
  727.     *after_at++ = '\0';
  728.     for (cp = after_at; *cp; cp++) {
  729.         if (isupper(*cp)) {
  730.         *cp = tolower(*cp);        /* lower-case domain portion */
  731.         }
  732.     }
  733.     *cp = '\0';
  734.     /* Try to find domain name in database. */
  735.     for (domain = unk_domain.link; domain; domain = domain->link) {
  736.         if (strEQ(domain->name, after_at)) {
  737.         break;
  738.         }
  739.     }
  740.     if (!domain) {        /* if domain doesn't exist, create it */
  741.       register int len = cp - after_at + 1;
  742.         domain = (DOMAIN *)safemalloc(sizeof (DOMAIN));
  743.         total.domain++;
  744.         domain->name = safemalloc(len);
  745.         total.string2 += len;
  746.         bcopy(after_at, domain->name, len);
  747.         domain->ids = Nullart;
  748.         domain->link = unk_domain.link;
  749.         unk_domain.link = domain;
  750.     }
  751.     }
  752.     /* Try to find id in this domain. */
  753.     for (article = domain->ids; article; article = article->id_link) {
  754.     if (strEQ(article->id, msg_id)) {
  755.         break;
  756.     }
  757.     }
  758.     if (!article) {        /* If it doesn't exist, create an article */
  759.       register int len = strlen(msg_id) + 1;
  760.     article = (ARTICLE *)safemalloc(sizeof (ARTICLE));
  761.     bzero(article, sizeof (ARTICLE));
  762.     total.article++;
  763.     article->num = 0;
  764.     article->id = safemalloc(len);
  765.     total.string2 += len;
  766.     bcopy(msg_id, article->id, len);
  767.     article->domain = domain;
  768.     article->id_link = domain->ids;
  769.     domain->ids = article;
  770.     }
  771.     return article;
  772. }
  773.  
  774. /* Take all the data we've accumulated about the article and shove it into
  775. ** the article tree at the best place we can possibly imagine.
  776. */
  777. void
  778. insert_article(article, date)
  779. ARTICLE *article;
  780. time_t date;
  781. {
  782.     register ARTICLE *node, *last;
  783.     register char *cp, *end;
  784.     int len;
  785.  
  786.     if (article->subject) {
  787.     if (log_verbosity) {
  788.         log_error("We've already seen article #%ld (%s@%s)\n",
  789.         num, article->id, article->domain->name);
  790.     }
  791.     return;                        /* RETURN */
  792.     }
  793.     article->date = date;
  794.     article->num = num;
  795.     article->flags = 0;
  796.  
  797.     if (!*references && found_Re) {
  798.     if (log_verbosity > 1) {
  799.         log_error("Missing reference line!  [%ld]\n", num);
  800.     }
  801.     }
  802.     /* If the article has a non-zero root, it is already in a thread somewhere.
  803.     ** Unlink it to try to put it in the best possible spot.
  804.     */
  805.     if (article->root) {
  806.     /* Check for a real or shared-fake parent.  Articles that have never
  807.     ** existed have a num of 0.  Expired articles that remain as references
  808.     ** have a valid num.  (Valid date too, but no subject.)
  809.     */
  810.     for (node = article->parent;
  811.          node && !node->num && node->child_cnt == 1;
  812.          node = node->parent)
  813.     {
  814.         ;
  815.     }
  816.     unlink_child(article);
  817.     if (node) {            /* do we have decent parents? */
  818.         /* Yes: assume that our references are ok, and just reorder us
  819.         ** with our siblings by date.
  820.         */
  821.         link_child(article);
  822.         use_root(article, article->root);
  823.         /* Freshen the date in any faked parent articles. */
  824.         for (node = article->parent;
  825.          node && !node->num && date < node->date;
  826.          node = node->parent)
  827.         {
  828.         node->date = date;
  829.         unlink_child(node);
  830.         link_child(node);
  831.         }
  832.         return;                    /* RETURN */
  833.     }
  834.     /* We'll assume that this article has as good or better references
  835.     ** than the child that faked us initially.  Free the fake reference-
  836.     ** chain and process our references as usual.
  837.     */
  838.     for (node = article->parent; node; node = node->parent) {
  839.         unlink_child(node);
  840.         free_article(node);
  841.     }
  842.     article->parent = Nullart;        /* neaten up */
  843.     article->siblings = Nullart;
  844.     }
  845.   check_references:
  846.     if (!*references) {    /* If no references but "Re:" in subject, */
  847.     if (found_Re) {    /* search for a reference in any cited text */
  848. #ifndef SERVER
  849.         for (len = 4; len && fgets(buff, sizeof buff, fp_article); len--) {
  850.         if ((cp = index(buff, '<')) && (end = index(cp, ' '))) {
  851.             if (end[-1] == ',') {
  852.             end--;
  853.             }
  854.             *end = '\0';
  855.             if ((end = index(cp, '>')) == Nullch) {
  856.             end = cp + strlen(cp) - 1;
  857.             }
  858.             if (valid_message_id(cp, end)) {
  859.             strcpy(references+1, cp);
  860.             *references = ' ';
  861.             if (log_verbosity > 2) {
  862.                 log_error("Found cited-text reference: '%s' [%ld]\n",
  863.                 references+1, num);
  864.             }
  865.             break;
  866.             }
  867.         }
  868.         }
  869. #endif
  870.     } else {
  871.         article->flags |= ROOT_ARTICLE;
  872.     }
  873.     }
  874.     /* If we have references, process them from the right end one at a time
  875.     ** until we either run into somebody, or we run out of references.
  876.     */
  877.     if (*references) {
  878.     last = article;
  879.     node = Nullart;
  880.     end = references + strlen(references) - 1;
  881.     while ((cp = rindex(references, '<')) != Nullch) {
  882.         while (end >= cp && ((unsigned char)*end <= ' ' || *end == ',')) {
  883.         end--;
  884.         }
  885.         end[1] = '\0';
  886.         /* Quit parsing references if this one is garbage. */
  887.         if (!valid_message_id(cp, end)) {
  888.         if (log_verbosity) {
  889.             log_error("Bad ref '%s' [%ld]\n", cp, num);
  890.         }
  891.         break;
  892.         }
  893.         /* Dump all domains that end in '.', such as "..." & "1@DEL." */
  894.         if (end[-1] == '.') {
  895.         break;
  896.         }
  897.         node = get_article(cp);
  898.         *cp = '\0';
  899.  
  900.         /* Check for duplicates on the reference line.  Brand-new data has
  901.         ** no date.  Data we just allocated earlier on this line has a
  902.         ** date but no root.  Special-case the article itself, since it
  903.         ** MIGHT have a root.
  904.         */
  905.         if ((node->date && !node->root) || node == article) {
  906.         if (log_verbosity) {
  907.             log_error("Reference line contains duplicates [%ld]\n",
  908.             num);
  909.         }
  910.         if ((node = last) == article) {
  911.             node = Nullart;
  912.         }
  913.         continue;
  914.         }
  915.         last->parent = node;
  916.         link_child(last);
  917.         if (node->root) {
  918.         break;
  919.         }
  920.         node->date = date;
  921.         last = node;
  922.         end = cp-1;
  923.     }
  924.     if (!node) {
  925.         *references = '\0';
  926.         goto check_references;
  927.     }
  928.     /* Check if we ran into anybody that was already linked.  If so, we
  929.     ** just use their root.
  930.     */
  931.     if (node->root) {
  932.         /* See if this article spans the gap between what we thought
  933.         ** were two different roots.
  934.         */
  935.         if (article->root && article->root != node->root) {
  936.         merge_roots(node->root, article->root);
  937.         /* Set the roots of any children we brought with us. */
  938.         set_root(article, node->root);
  939.         }
  940.         use_root(article, node->root);
  941.     } else {
  942.         /* We didn't find anybody we knew, so either create a new root or
  943.         ** use the article's root if it was previously faked.
  944.         */
  945.         if (!article->root) {
  946.         make_root(node);
  947.         use_root(article, node->root);
  948.         } else {
  949.         node->root = article->root;
  950.         link_child(node);
  951.         use_root(article, article->root);
  952.         }
  953.     }
  954.     /* Set the roots of the faked articles we created as references. */
  955.     for (node = article->parent; node && !node->root; node = node->parent) {
  956.         node->root = article->root;
  957.     }
  958.     /* Make sure we didn't circularly link to a child article(!), by
  959.     ** ensuring that we run into the root before we run into ourself.
  960.     */
  961.     while (node && node->parent != article) {
  962.         node = node->parent;
  963.     }
  964.     if (node) {
  965.         /* Ugh.  Someone's tweaked reference line with an incorrect
  966.         ** article-order arrived first, and one of our children is
  967.         ** really one of our ancestors. Cut off the bogus child branch
  968.         ** right where we are and link it to the root.
  969.         */
  970.         if (log_verbosity) {
  971.         log_error("Found ancestral child -- fixing.\n");
  972.         }
  973.         unlink_child(node);
  974.         node->parent = Nullart;
  975.         link_child(node);
  976.     }
  977.     } else {
  978.     /* The article has no references.  Either turn it into a new root, or
  979.     ** re-attach fleshed-out (previously faked) article to its old root.
  980.     */
  981.     if (!article->root) {
  982.         make_root(article);
  983.     } else {
  984.         link_child(article);
  985.         use_root(article, article->root);
  986.     }
  987.     }
  988. }
  989.  
  990. /* Check if the string we've found looks like a valid message-id reference.
  991. */
  992. int
  993. valid_message_id(start, end)
  994. register char *start, *end;
  995. {
  996.     char *mid;
  997.  
  998.     if (start == end) {
  999.     return 0;
  1000.     }
  1001.  
  1002.     if (*end != '>') {
  1003.     /* Compensate for space cadets who include the header in their
  1004.     ** subsitution of all '>'s into another citation character.
  1005.     */
  1006.     if (*end == '<' || *end == '-' || *end == '!' || *end == '%'
  1007.      || *end == ')' || *end == '|' || *end == ':' || *end == '}'
  1008.      || *end == '*' || *end == '+' || *end == '#' || *end == ']'
  1009.      || *end == '@' || *end == '$') {
  1010.         if (log_verbosity) {
  1011.         log_error("Reference ended in '%c' [%ld]\n", *end, num);
  1012.         }
  1013.         *end = '>';
  1014.     }
  1015.     } else if (end[-1] == '>') {
  1016.     if (log_verbosity) {
  1017.         log_error("Reference ended in '>>' [%ld]\n", num);
  1018.     }
  1019.     *(end--) = '\0';
  1020.     }
  1021.     /* Id must be "<...@...>" */
  1022.     if (*start != '<' || *end != '>' || (mid = index(start, '@')) == Nullch
  1023.      || mid == start+1 || mid+1 == end) {
  1024.     return 0;                    /* RETURN */
  1025.     }
  1026.     return 1;
  1027. }
  1028.  
  1029. /* Remove an article from its parent/siblings.  Leave parent pointer intact.
  1030. */
  1031. void
  1032. unlink_child(child)
  1033. register ARTICLE *child;
  1034. {
  1035.     register ARTICLE *last;
  1036.  
  1037.     if (!(last = child->parent)) {
  1038.     child->root->thread_cnt--;
  1039.     if ((last = child->root->articles) == child) {
  1040.         child->root->articles = child->siblings;
  1041.     } else {
  1042.         goto sibling_search;
  1043.     }
  1044.     } else {
  1045.     last->child_cnt--;
  1046.     if (last->children == child) {
  1047.         last->children = child->siblings;
  1048.     } else {
  1049.         last = last->children;
  1050.       sibling_search:
  1051.         while (last->siblings != child) {
  1052.         last = last->siblings;
  1053.         }
  1054.         last->siblings = child->siblings;
  1055.     }
  1056.     }
  1057. }
  1058.  
  1059. /* Link an article to its parent article.  If its parent pointer is zero,
  1060. ** link it to its root.  Sorts siblings by date.
  1061. */
  1062. void
  1063. link_child(child)
  1064. register ARTICLE *child;
  1065. {
  1066.     register ARTICLE *node;
  1067.     register ROOT *root;
  1068.  
  1069.     if (!(node = child->parent)) {
  1070.     root = child->root;
  1071.     root->thread_cnt++;
  1072.     node = root->articles;
  1073.     if (!node || child->date < node->date) {
  1074.         child->siblings = node;
  1075.         root->articles = child;
  1076.     } else {
  1077.         goto sibling_search;
  1078.     }
  1079.     } else {
  1080.     node->child_cnt++;
  1081.     node = node->children;
  1082.     if (!node || child->date < node->date) {
  1083.         child->siblings = node;
  1084.         child->parent->children = child;
  1085.     } else {
  1086.       sibling_search:
  1087.         for (; node->siblings; node = node->siblings) {
  1088.         if (node->siblings->date > child->date) {
  1089.             break;
  1090.         }
  1091.         }
  1092.         child->siblings = node->siblings;
  1093.         node->siblings = child;
  1094.     }
  1095.     }
  1096. }
  1097.  
  1098. /* Create a new root for the specified article.  If the current subject_str
  1099. ** matches any pre-existing root's subjects, we'll instead add it on as a
  1100. ** parallel thread.
  1101. */
  1102. void
  1103. make_root(article)
  1104. ARTICLE *article;
  1105. {
  1106.     register ROOT *new, *node;
  1107.     register SUBJECT *subject;
  1108.  
  1109. #ifndef NO_SUBJECT_MATCHING
  1110.     /* First, check the other root's subjects for a match. */
  1111.     for (node = root_root; node; node = node->link) {
  1112.     for (subject = node->subjects; subject; subject = subject->link) {
  1113.         if (subject_equal(subject->str, subject_str)) {
  1114.         use_root(article, node);        /* use it instead */
  1115.         link_child(article);
  1116.         return;                    /* RETURN */
  1117.         }
  1118.     }
  1119.     }
  1120. #endif
  1121.  
  1122.     /* Create a new root. */
  1123.     new = (ROOT *)safemalloc(sizeof (ROOT));
  1124.     total.root++;
  1125.     new->articles = article;
  1126.     new->root_num = article->num;
  1127.     new->thread_cnt = 1;
  1128.     if (article->num) {
  1129.     article->author = new_author();
  1130.     new->subject_cnt = 1;
  1131.     new->subjects = article->subject = new_subject();
  1132.     } else {
  1133.     new->subject_cnt = 0;
  1134.     new->subjects = Null(SUBJECT*);
  1135.     }
  1136.     article->root = new;
  1137.     new->link = root_root;
  1138.     root_root = new;
  1139. }
  1140.  
  1141. /* Add this article's subject onto the indicated root's list.  Point the
  1142. ** article at the root.
  1143. */
  1144. void
  1145. use_root(article, root)
  1146. ARTICLE *article;
  1147. ROOT *root;
  1148. {
  1149.     register SUBJECT *subject;
  1150.     register ROOT *root2;
  1151.     SUBJECT *hold, *child_subj = Null(SUBJECT*), *sib_subj = Null(SUBJECT*);
  1152.     ARTICLE *node;
  1153.  
  1154.     article->root = root;
  1155.  
  1156.     /* If it's a fake, there's no subject to add. */
  1157.     if (!article->num) {
  1158.     return;                        /* RETURN */
  1159.     }
  1160.  
  1161.     /* If we haven't picked a unique message number to represent this root,
  1162.     ** use the first non-zero number we encounter.  Which one doesn't matter.
  1163.     */
  1164.     if (!root->root_num) {
  1165.     root->root_num = article->num;
  1166.     }
  1167.     article->author = new_author();
  1168.  
  1169.     /* Check if the new subject matches any of the other subjects in this root.
  1170.     ** If so, we just update the count.  If not, check all the other roots for
  1171.     ** a match.  If found, the new subject is common between the two roots, so
  1172.     ** we merge the two roots together.
  1173.     */
  1174.     root2 = root;
  1175. #ifndef NO_SUBJECT_MATCHING
  1176.     do {
  1177. #endif
  1178.     for (subject = root2->subjects; subject; subject = subject->link) {
  1179.         if (subject_equal(subject->str, subject_str)) {
  1180.         article->subject = subject;
  1181.         subject->count++;
  1182. #ifndef NO_SUBJECT_MATCHING
  1183.         if (root2 != root) {
  1184.             merge_roots(root, root2);
  1185.         }
  1186. #endif
  1187.         return;                    /* RETURN */
  1188.         }
  1189.     }
  1190. #ifndef NO_SUBJECT_MATCHING
  1191.     if ((root2 = root2->link) == Null(ROOT*)) {
  1192.         root2 = root_root;
  1193.     }
  1194.     } while (root2 != root);
  1195. #endif
  1196.  
  1197.     article->subject = hold = new_subject();
  1198.     root->subject_cnt++;
  1199.  
  1200.     /* Find the subject of any pre-existing children or siblings.  We want
  1201.     ** to insert the new subject before one of these to keep the numbering
  1202.     ** intuitive in the newsreader.
  1203.     */
  1204.     for (node = article->children; node; node = node->children) {
  1205.     if (node->subject) {
  1206.         child_subj = node->subject;
  1207.         break;
  1208.     }
  1209.     }
  1210.     for (node = article->siblings; node; node = node->siblings) {
  1211.     if (node->subject) {
  1212.         sib_subj = node->subject;
  1213.         break;
  1214.     }
  1215.     }
  1216.     if (!(subject = root->subjects)
  1217.      || subject == child_subj || subject == sib_subj) {
  1218.     hold->link = root->subjects;
  1219.     root->subjects = hold;
  1220.     } else {
  1221.     while (subject->link
  1222.      && subject->link != child_subj && subject->link != sib_subj) {
  1223.         subject = subject->link;
  1224.     }
  1225.     hold->link = subject->link;
  1226.     subject->link = hold;
  1227.     }
  1228. }
  1229.  
  1230. /* Check subjects in a case-insignificant, punctuation-ignoring manner.
  1231. */
  1232. int
  1233. subject_equal(str1, str2)
  1234. register char *str1, *str2;
  1235. {
  1236.     register char ch1, ch2;
  1237.  
  1238.     while ((ch1 = *str1++)) {
  1239.     if (ch1 == ' ' || ispunct(ch1)) {
  1240.         while (*str1 && (*str1 == ' ' || ispunct(*str1))) {
  1241.         str1++;
  1242.         }
  1243.         ch1 = ' ';
  1244.     } else if (isupper(ch1)) {
  1245.         ch1 = tolower(ch1);
  1246.     }
  1247.     if (!(ch2 = *str2++)) {
  1248.         return 0;
  1249.     }
  1250.     if (ch2 == ' ' || ispunct(ch2)) {
  1251.         while (*str2 && (*str2 == ' ' || ispunct(*str2))) {
  1252.         str2++;
  1253.         }
  1254.         ch2 = ' ';
  1255.     } else if (isupper(ch2)) {
  1256.         ch2 = tolower(ch2);
  1257.     }
  1258.     if (ch1 != ch2) {
  1259.         return 0;
  1260.     }
  1261.     }
  1262.     if (*str2) {
  1263.     return 0;
  1264.     }
  1265.     return 1;
  1266. }
  1267.  
  1268. /* Create a new subject structure. */
  1269. SUBJECT *
  1270. new_subject()
  1271. {
  1272.     register int len = strlen(subject_str) + 1;
  1273.     register SUBJECT *subject;
  1274.  
  1275.     subject = (SUBJECT *)safemalloc(sizeof (SUBJECT));
  1276.     total.subject++;
  1277.     subject->count = 1;
  1278.     subject->link = Null(SUBJECT*);
  1279.     subject->str = safemalloc(len);
  1280.     total.string1 += len;
  1281.     bcopy(subject_str, subject->str, len);
  1282.  
  1283.     return subject;
  1284. }
  1285.  
  1286. /* Create a new author structure. */
  1287. AUTHOR *
  1288. new_author()
  1289. {
  1290.     register len = strlen(author_str) + 1;
  1291.     register AUTHOR *author, *last_author;
  1292.  
  1293.     last_author = Null(AUTHOR*);
  1294.     for (author = author_root; author; author = author->link) {
  1295. #ifndef DONT_COMPARE_AUTHORS    /* might like to define this to save time */
  1296.     if (strEQ(author->name, author_str)) {
  1297.         author->count++;
  1298.         return author;                /* RETURN */
  1299.     }
  1300. #endif
  1301.     last_author = author;
  1302.     }
  1303.  
  1304.     author = (AUTHOR *)safemalloc(sizeof (AUTHOR));
  1305.     total.author++;
  1306.     author->count = 1;
  1307.     author->link = Null(AUTHOR*);
  1308.     author->name = safemalloc(len);
  1309.     total.string1 += len;
  1310.     bcopy(author_str, author->name, len);
  1311.  
  1312.     if (last_author) {
  1313.     last_author->link = author;
  1314.     } else {
  1315.     author_root = author;
  1316.     }
  1317.     return author;
  1318. }
  1319.  
  1320. /* Insert all of root2 into root1, setting the proper root values and
  1321. ** updating subject counts.
  1322. */
  1323. void
  1324. merge_roots(root1, root2)
  1325. ROOT *root1, *root2;
  1326. {
  1327.     register ARTICLE *node, *next;
  1328.     register SUBJECT *subject;
  1329.  
  1330.     /* Remember whoever's root num is lower.  This could screw up a
  1331.     ** newsreader's kill-thread code if someone already saw the roots as
  1332.     ** being separate, but it must be done.  The newsreader code will have
  1333.     ** to handle this as best as it can.
  1334.     */
  1335.     if (root1->root_num > root2->root_num) {
  1336.     root1->root_num = root2->root_num;
  1337.     }
  1338.  
  1339.     for (node = root2->articles; node; node = next) {
  1340.     /* For each article attached to root2: detach it, set the branch's
  1341.     ** root pointer to root1, and then attach it to root1.
  1342.     */
  1343.     next = node->siblings;
  1344.     unlink_child(node);
  1345.     node->siblings = Nullart;
  1346.     set_root(node, root1);        /* sets children too */
  1347.     /* Link_child() depends on node->parent being null and node->root
  1348.     ** being set.
  1349.     */
  1350.     link_child(node);
  1351.     }
  1352.     root1->subject_cnt += root2->subject_cnt;
  1353.     if (!(subject = root1->subjects)) {
  1354.     root1->subjects = root2->subjects;
  1355.     } else {
  1356.     while (subject->link) {
  1357.         subject = subject->link;
  1358.     }
  1359.     subject->link = root2->subjects;
  1360.     }
  1361.     unlink_root(root2);
  1362.     free_root(root2);
  1363. }
  1364.  
  1365. /* When merging roots, we need to reset all the root pointers.
  1366. */
  1367. void
  1368. set_root(node, root)
  1369. ARTICLE *node;
  1370. ROOT *root;
  1371. {
  1372.     do {
  1373.     node->root = root;
  1374.     if (node->children) {
  1375.         set_root(node->children, root);
  1376.     }
  1377.     } while (node = node->siblings);
  1378. }
  1379.  
  1380. /* Unlink a root from its neighbors. */
  1381. void
  1382. unlink_root(root)
  1383. register ROOT *root;
  1384. {
  1385.     register ROOT *node;
  1386.  
  1387.     if ((node = root_root) == root) {
  1388.     root_root = root->link;
  1389.     } else {
  1390.     while (node->link != root) {
  1391.         node = node->link;
  1392.     }
  1393.     node->link = root->link;
  1394.     }
  1395. }
  1396.  
  1397. /* Free an article and its message-id string.  All other resources must
  1398. ** already be free, and it must not be attached to any threads.
  1399. */
  1400. void
  1401. free_article(this)
  1402. ARTICLE *this;
  1403. {
  1404.     register ARTICLE *art;
  1405.  
  1406.     if ((art = this->domain->ids) == this) {
  1407.     if (!(this->domain->ids = this->id_link)) {
  1408.         free_domain(this->domain);
  1409.     }
  1410.     } else {
  1411.     while (this != art->id_link) {
  1412.         art = art->id_link;
  1413.     }
  1414.     art->id_link = this->id_link;
  1415.     }
  1416.     total.string2 -= strlen(this->id) + 1;
  1417.     free(this->id);
  1418.     free(this);
  1419.     total.article--;
  1420. }
  1421.  
  1422. /* Free the domain only when its last unique id has been freed. */
  1423. void
  1424. free_domain(this)
  1425. DOMAIN *this;
  1426. {
  1427.     register DOMAIN *domain;
  1428.  
  1429.     if (this == (domain = &unk_domain)) {
  1430.     return;
  1431.     }
  1432.     if (this == next_domain) {    /* help expire routine skip freed domains */
  1433.     next_domain = next_domain->link;
  1434.     }
  1435.     while (this != domain->link) {
  1436.     domain = domain->link;
  1437.     }
  1438.     domain->link = this->link;
  1439.     total.string2 -= strlen(this->name) + 1;
  1440.     free(this->name);
  1441.     free(this);
  1442.     total.domain--;
  1443. }
  1444.  
  1445. /* Free the subject structure and its string. */
  1446. void
  1447. free_subject(this)
  1448. SUBJECT *this;
  1449. {
  1450.     total.string1 -= strlen(this->str) + 1;
  1451.     free(this->str);
  1452.     free(this);
  1453.     total.subject--;
  1454. }
  1455.  
  1456. /* Free a root.  It must already be unlinked. */
  1457. void
  1458. free_root(this)
  1459. ROOT *this;
  1460. {
  1461.     free(this);
  1462.     total.root--;
  1463. }
  1464.  
  1465. /* Free the author structure when it's not needed any more. */
  1466. void
  1467. free_author(this)
  1468. AUTHOR *this;
  1469. {
  1470.     total.string1 -= strlen(this->name) + 1;
  1471.     free(this->name);
  1472.     free(this);
  1473.     total.author--;
  1474. }
  1475.  
  1476. #if defined(SERVER) && !defined(USLEEP)
  1477. usleep(usec)
  1478. long usec;
  1479. {
  1480. # ifndef USELECT
  1481.     if (usec /= 1000000) {
  1482.     sleep((int)usec);
  1483.     }
  1484. # else
  1485.     struct timeval t;
  1486.  
  1487.     if (usec <= 0) {
  1488.     return;
  1489.     }
  1490.     t.tv_usec = usec % 1000000;
  1491.     t.tv_sec  = usec / 1000000;
  1492.     (void) select(1, 0, 0, 0, &t);
  1493. # endif
  1494. }
  1495. #endif
  1496.