home *** CD-ROM | disk | FTP | other *** search
/ Garbo / Garbo.cdr / mac / source / netnwscd.sit / newslists.c < prev    next >
Text File  |  1990-10-18  |  14KB  |  725 lines

  1. /*
  2.  * newslists.c
  3.  * Copyright ⌐ Tom Bereiter, 1990
  4.  */
  5.  
  6. #include <ctype.h>
  7. #include <stdio.h>
  8. #include <string.h>
  9. #include <stdlib.h>
  10. #include "CGroupList.h"
  11. #include "CArticleList.h"
  12.  
  13. #include "newslists.h"
  14. #include "nntp.h"
  15. #include "bits.h"
  16.  
  17. extern CursHandle    gWatchCursor;        /* Watch cursor for waiting            */
  18.  
  19. #define    dlogConfig    140
  20.  
  21. char newsrc[]="newsrc";
  22. char newsrcnew[]="newsrc.new";
  23. char newsrcold[]="newsrc.old";
  24.  
  25. char server[128];
  26. int last_ngrps;
  27. int last_txtsize;
  28.  
  29. char *Xrealloc(Ptr p, long n);
  30. char *savestr();
  31. long strtol();
  32.  
  33. char *txt;        /* group name string space */
  34. long txtsize;
  35.  
  36. #define    ARTBUF_SZ    0x7F00L        /* almost 32k */
  37. #define ARTLINE_SZ    0x100L
  38. char *artbuf;    /* article buffer */
  39. int artlen;
  40.  
  41. grp_t *grps;            /* group list */
  42. long ngrps;                /* number of groups in group list */
  43. grp_t *grp_head;        /* head of subscribed list */
  44. long nsgrps;            /* number of subscribed groups */
  45.  
  46. act_compar(a,b)
  47. grp_t *a,*b;
  48. {
  49.     return strcmp(a->gname, b->gname);
  50. }
  51.  
  52.  
  53. init_news() {
  54.     CGroupList *lwin;
  55.     char imsg[256];
  56.     int response;
  57.     
  58.     getconfig();
  59.     
  60.     sprintf(imsg, "Connecting to %s...", server);
  61.     SetCursor(*gWatchCursor);
  62.     
  63.     lwin = new(CGroupList);
  64.     lwin->IGroupList();
  65.     lwin->TmpMsg(imsg);
  66.  
  67.     artbuf = NewPtr(ARTBUF_SZ + ARTLINE_SZ);
  68.  
  69.     response = server_init(server, imsg, 256);
  70.     if (response < 0) {
  71.         errmsg(2,"Couldn't connect to %s news server, try again later.",server);
  72.         exit(1);
  73.     }
  74.     lwin->TmpMsg(imsg);
  75.     response = atoi(imsg);
  76.     
  77.     if (response != OK_NOPOST & response != OK_CANPOST)
  78.         exit(1);
  79.  
  80.     /* get and process active list */
  81.     getactive();
  82.     addnew();
  83.     qsort(grps, ngrps, sizeof(grp_t), act_compar);
  84.  
  85.     /* get current list */
  86.     getcur();
  87.  
  88.     /* initial group list */
  89.     lwin->ReDo();
  90. }
  91.  
  92. getactive() {
  93.     char ser_line[256];
  94.     grp_t *gp, *gp1;
  95.     char *txtp;
  96.     char *s;
  97.     long n;
  98.  
  99.     /*
  100.      * allocate area for active list.  For convenience this list is forced to
  101.      *    to be a contiguous array rather than a linked list.
  102.      */
  103.     ngrps = last_ngrps;
  104.     txtsize = last_txtsize;
  105.     if ((grps = (grp_t *)NewPtr((long)ngrps * sizeof(grp_t))) == NULL) {
  106.         errmsg(3,"no mem");
  107.         exit(1);
  108.     }
  109.     if ((txt = NewPtr(txtsize)) == NULL) {
  110.         errmsg(3,"no mem");
  111.         exit(1);
  112.     }
  113.  
  114.     /* open the active file */
  115.     put_server("LIST\r\n");        /* tell server we want the active file */
  116.     (void) get_server(ser_line, sizeof(ser_line));
  117.     if (*ser_line != CHAR_OK) {    /* and then see if that's ok */
  118.         errmsg(3,"Can't get active file from server: %s", ser_line);
  119.         exit(1);
  120.     }
  121.  
  122.     gp = grps;
  123.     txtp = txt;
  124.     while (get_server(ser_line, sizeof(ser_line)) >= 0) {
  125.         if (ser_line[0] == '.')        /* no more */
  126.             break;
  127.         if (gp >= &grps[ngrps]) {
  128.             n = gp - grps;
  129.             ngrps += 20;
  130.             grps = (grp_t *)Xrealloc((Ptr)grps, ngrps * sizeof(grp_t));
  131.             gp = &grps[n];
  132.         }
  133.         s = strchr(ser_line, ' ');
  134.         *s++ = '\0';
  135.  
  136.         /* save group name */
  137.         if (txtp + (s-ser_line) >= &txt[txtsize]) {
  138.             n = txtp - txt;
  139.             txtsize += 1024;
  140.             txtp = txt;        /* remember old */
  141.             txt = Xrealloc(txt, txtsize);
  142.             /* update all text pointers */
  143.             for (gp1=grps; gp1 <= gp; gp1++)
  144.                 gp1->gname = &txt[gp1->gname - txtp];
  145.             txtp = &txt[n];
  146.         }
  147.         gp->gname = txtp;
  148.         txtp += (s-ser_line);
  149.         strcpy(gp->gname, ser_line);
  150.  
  151.         gp->last = strtol(s,0,10);
  152.         s = strchr(s, ' ');
  153.         gp->first = strtol(s+1,0,10);
  154.         gp->unread = (gp->last == gp->first) ? 0 : gp->last - gp->first + 1;
  155.         gp->bm = NULL;
  156.         gp->art_head = NULL;
  157.         gp->flags = 0;
  158.         gp->subindex = 0;
  159.         gp->next = NULL;
  160.         gp++;
  161.     }
  162.     /* new sizes */
  163.     txtsize = ((long)(txtp - txt) + (1024-1)) & ~(1024-1);    /* round up to next K */
  164.     ngrps = gp - grps;
  165. }
  166.  
  167. /*
  168.  * subscribe to new groups
  169.  */
  170. addnew() {
  171.     grp_t *gp;
  172.     int i, j;
  173.     
  174.     /*
  175.      * don't ask if there are too many
  176.      */
  177.     if (ngrps - last_ngrps > 10) {
  178.         errmsg(1,"%ld new groups.  Add from main menu.",ngrps - last_ngrps);
  179.         return;
  180.     }
  181.     
  182.     for (i=last_ngrps;  i<ngrps; i++) {
  183.         gp = &grps[i];
  184.         if (askYesNo("Subscribe to new group '%s' ?", gp->gname)) {
  185.             gp->flags = G_SUB;
  186.             gp->subindex = 32000;        /* a big number */
  187.         }
  188.     }
  189. }
  190.  
  191. /*
  192.  * read first line.
  193.  *        #config: host nnn mmm
  194.  */
  195. getconfig() {
  196.     char line[256];
  197.     FILE *fs;
  198.     long n,m;
  199.  
  200.     if ((fs=fopen(newsrc,"r"))!=NULL) {
  201.         if (fgets(line,256,fs)!=NULL)
  202.             if (strncmp("#config: ", line, 9) == 0)
  203.                 if (sscanf(&line[9], "%s %ld %ld", server, &n, &m) == 3) {
  204.                     last_ngrps = n;
  205.                     last_txtsize = m;
  206.                 }
  207.         fclose(fs);
  208.     }
  209.     /* enforce some arbitrary limits */
  210.     if (last_ngrps < 100) last_ngrps = 100;
  211.     if (last_ngrps > 5000) last_ngrps = 100;
  212.     if (last_txtsize < 8192) last_txtsize = 8192;
  213.     if (last_txtsize > 64000) last_txtsize = 8192;
  214.     
  215.     /* get server name */
  216.     if (server[0] == '\0') {
  217.         DialogPtr dptr;
  218.         int item, type;
  219.         Handle ih;
  220.         Rect r;
  221.         
  222.         dptr = GetNewDialog(dlogConfig, 0L, -1L);
  223.         ModalDialog(0L, &item);
  224.         if (item == 2)    /* cancel */
  225.             ExitToShell();
  226.         GetDItem(dptr, 3, &type, &ih, &r);
  227.         GetIText(ih, server);
  228.         PtoCstr(server);
  229.         DisposDialog(dptr);
  230.     }
  231. }
  232.  
  233. /*
  234.  * read newsrc file -- currently subscribed groups
  235.  */
  236. getcur() {
  237.     FILE *fs;
  238.     char group[256];
  239.     char *p;
  240.     grp_t *gp, *pgp, *findgrp();
  241.     int i;
  242.     Boolean active;
  243.  
  244.     /* get master group list */
  245.     if ((fs=fopen(newsrc,"r"))==NULL) {
  246.         errmsg(2,"No %s found",newsrc);
  247.         return;
  248.     }
  249. top:
  250.     while (fgets(group,256,fs)!=NULL) {
  251.         p = group;
  252.         if (*p == '#')
  253.             continue;
  254.         while (*p!='!' && *p!=':')
  255.             if (*p++ == '\0') {
  256.                 errmsg(1,"syntax error: %s",group);
  257.                 goto top;
  258.             }
  259.         active = (*p == ':');
  260.         *p = '\0';
  261.         if ((gp = findgrp(group)) == NULL) {
  262.             errmsg(1,"bogus group: %s",group);
  263.             continue;
  264.         }
  265.         /* set bits for all previously read articles */
  266.         gp->bm = bmalloc(bmTOTAL(gp));
  267.         gp->unread = asciiToBits(gp, p+1);
  268.         if (active) {
  269.             gp->flags = G_SUB;
  270.             gp->subindex = nsgrps++;
  271.         }
  272.         else gp->flags = G_INACT;
  273.     }
  274.     fclose(fs);
  275.     
  276.     /* make subscribed links */
  277.     nsgrps = 0;
  278.     for (i=0; i<ngrps; i++)
  279.         if (grps[i].flags & G_SUB)
  280.             add_sgrp(&grps[i]);
  281. }
  282.  
  283. /*
  284.  * add a group to suscribed list
  285.  */
  286. add_sgrp(grp_t *addgp)
  287. {
  288.     grp_t *gp, *pgp;
  289.     
  290.     if (grp_head == NULL)
  291.         grp_head = addgp;
  292.     else {        /* find ordered position in list */
  293.         for (gp=pgp=grp_head; ; pgp=gp,gp=gp->next)
  294.             if (gp==NULL || gp->subindex > addgp->subindex) {
  295.                 if (gp && pgp == grp_head)
  296.                     grp_head = addgp;
  297.                 else
  298.                     pgp->next = addgp;
  299.                 addgp->next = gp;
  300.                 break;
  301.             }
  302.     }
  303.     nsgrps++;
  304. }
  305.  
  306. /*
  307.  * remove a group from suscribed list
  308.  */
  309. rm_sgrp(grp_t *rmgp)
  310. {
  311.     grp_t *gp, *pgp;
  312.     
  313.     for (gp=pgp=grp_head; gp; pgp=gp,gp=gp->next)
  314.         if (gp == rmgp) {
  315.             if (gp == grp_head)
  316.                 grp_head = gp->next;
  317.             else
  318.                 pgp->next = gp->next;
  319.             break;
  320.         }
  321.     nsgrps--;
  322. }
  323.  
  324.  
  325. /*
  326.  * convert strings of form:  1-45,47,49,52-120
  327.  *   to a bitmap
  328.  */
  329. asciiToBits(grp_t *gp, char *numlist)
  330. {
  331.     int cnt;
  332.     long n=0, m, i;
  333.     char *p = numlist, *p1;
  334.     
  335.     cnt = bmTOTAL(gp);
  336.     
  337.     while (*p) {
  338.         while (isspace(*p)) p++;
  339.         p1 = p;
  340.         while (isdigit(*p)) p++;
  341.         if (p == p1) {
  342. bad:        errmsg(2,"bad number list");
  343.             return 0;
  344.         }
  345.         switch(*p) {
  346.         case '-':
  347.             n = strtol(p1,0,10);
  348.             break;
  349.         case ',':
  350.         case '\r':
  351.         case '\n':
  352.             m = strtol(p1,0,10);
  353.             if (m < gp->first) {        /* not in current article set */
  354.                 n = 0;
  355.                 break;
  356.             }
  357.             if (n == 0)        /* single number */
  358.                 n = m;
  359.             else if (n < gp->first)
  360.                 n = gp->first;
  361.             i = m-n + 1;
  362.             if ((cnt -= i) < 0)
  363.                 goto bad;
  364.             bfset(gp->bm, n - gp->first, i);
  365.             n = 0;
  366.             break;
  367.         default:
  368.             goto bad;
  369.         }
  370.         p++;
  371.     }
  372.     return cnt;
  373. }
  374.  
  375. grp_t *
  376. findgrp(gname)
  377. char *gname;
  378. {
  379.     grp_t *gp;
  380.     int c;
  381.  
  382.     for (gp=grps; gp < &grps[ngrps]; gp++)
  383.         if (strcmp(gp->gname, gname) == 0)
  384.             return gp;
  385.     return NULL;    
  386. }
  387.  
  388.  
  389. art_t *art_tail;    /* temporary, for building article list */
  390.  
  391. /* add article */
  392. addart(grp_t *gp, long n, char *txt)
  393. {
  394.     char *s;
  395.     art_t *ap;
  396.     int re=0;
  397.  
  398.     s = txt;
  399.     
  400.     /* make subject search string */
  401.     s = strchr(s,' ');
  402.     while (isspace(*s)) s++;
  403.     if ((s[0]|040)=='r' && (s[1]|040)=='e' && s[2]==':') {
  404.         s += 3;
  405.         re++;
  406.     }
  407.     while (isspace(*s)) s++;
  408.  
  409.     /*
  410.      * look for previous thread with same subject line.  A 40 character match
  411.      * is considered close enough.
  412.      */
  413.     for (ap=gp->art_head; ap; ap=ap->next)
  414.         if (strncmp(s, ap->title, 40) == 0) {    /* found a thread */
  415.             if ((ap->nthread % THREADINC) == 0)
  416.                 ap->thread = (unsigned int  *)Xrealloc((Ptr)ap->thread,
  417.                     (ap->nthread + THREADINC) * sizeof(unsigned int  *));
  418.             ap->thread[ap->nthread++] = n;
  419.             return;
  420.         }
  421.     /* new one */
  422.     ap = (art_t *)NewPtr(sizeof(art_t));
  423.     ap->title = savestr(s);
  424.     ap->re = re;
  425.     ap->thread = (unsigned int  *)NewPtr(THREADINC * sizeof(unsigned int  *));
  426.     ap->nthread = 1;
  427.     ap->tindex = 0;
  428.     ap->thread[0] = n;
  429.     ap->gp = gp;
  430.     ap->refcnt = 0;
  431.     if (gp->art_head == NULL) {    /* first one */
  432.         gp->art_head = ap;
  433.         ap->prev = NULL;
  434.     }
  435.     else {
  436.         ap->prev = art_tail;
  437.         art_tail->next = ap;
  438.     }
  439.     art_tail = ap;
  440.     ap->next = NULL;
  441. }
  442.  
  443. /*
  444.  * return next article, remove if 'zap'.  update parent display window if 'modline'.
  445.  */
  446. art_t *
  447. next_article(CArticleList *alist, art_t *ap, Boolean zap, Boolean modline)
  448. {
  449.     unsigned int i, cnt;
  450.     art_t *nap;
  451.     
  452.     ap->refcnt--;
  453.     if (ap->thread == NULL) {        /* previously unlinked */
  454.         if (ap->refcnt == 0)
  455.             DisposPtr(ap);
  456.         return (NULL);
  457.     }
  458.  
  459.     /* mark article(s) as read */
  460.     cnt = zap ? ap->nthread - ap->tindex : 1;
  461.     for (i=0; i<cnt; i++, ap->tindex++)
  462.         Bset(ap->gp->bm, ap->thread[ap->tindex] - ap->gp->first);
  463.     ap->gp->unread -= cnt;
  464.     
  465.     ap->re = 1;        /* can nolonger be original thread */
  466.     if (!zap && ap->tindex < ap->nthread) {    /* more threads this article */
  467.         if (modline)
  468.             alist->ModLine(ap, 1);
  469.         return (ap);
  470.     }
  471.     /* find next article with unread threads */
  472.     for (nap=ap->next; nap; nap=nap->next)
  473.         if (nap->tindex + nap->refcnt < nap->nthread)
  474.             break;
  475.         
  476.     if (modline)
  477.         alist->ModLine(ap, 0);
  478.     del_article(ap);
  479.     
  480.     return (nap);
  481. }
  482.  
  483. /* delete single article */
  484. del_article(art_t *ap)
  485. {
  486.     DisposPtr(ap->title);
  487.     DisposPtr(ap->thread);
  488.     if (ap->gp->art_head == ap) {    /* remove at head of list */
  489.         ap->gp->art_head = ap->next;
  490.         if (ap->next)
  491.             ap->next->prev = NULL;
  492.     }
  493.     else {
  494.         ap->prev->next = ap->next;
  495.         if (ap->next)
  496.             ap->next->prev = ap->prev;
  497.     }
  498.  
  499.     if (ap->refcnt == 0)
  500.         DisposPtr(ap);
  501.     else {
  502.         ap->thread = NULL;    /* mark it unlinked */
  503.         ap->next = ap->prev = NULL;
  504.     }
  505. }
  506.  
  507. /* delete entire article list */
  508. dispose_artlist(grp_t *gp)
  509. {
  510.     art_t *ap, *nap;
  511.     
  512.     /* remove previous */
  513.     if (gp->art_head) {
  514.         for (ap=gp->art_head; ap; ap=nap) {
  515.             DisposPtr(ap->title);
  516.             nap = ap->next;
  517.             DisposPtr(ap->thread);
  518.             if (ap->refcnt == 0)
  519.                 DisposPtr(ap);
  520.             else {
  521.                 ap->thread = NULL;        /* mark it unlinked */
  522.                 ap->next = ap->prev = NULL;
  523.             }
  524.         }
  525.         gp->art_head = NULL;
  526.     }
  527. }
  528.  
  529. /*
  530.  * init a group's article list
  531.  */
  532. igroup(grp_t *gp) 
  533. {
  534.     char msg[256], *s;
  535.     art_t *ap, *nap;
  536.     long first, last, n, cnt;
  537.  
  538.     if (gp->art_head)    /* list already open */
  539.         return;
  540.  
  541.     SetCursor(*gWatchCursor);
  542.  
  543.     if (gp->bm == NULL)        /* lazy allocation for new groups and master list */
  544.         gp->bm = bmalloc(bmTOTAL(gp));
  545.         
  546.     first = gp->first + bfffc(gp->bm, 0, bmTOTAL(gp));
  547.     last = gp->last;
  548.     
  549.     sprintf(msg, "GROUP %s\r\n", gp->gname);
  550.     put_server(msg);
  551.     if (get_server(msg, sizeof(msg)) < 0) {
  552.         errmsg(3,"rrn: Unexpected close of server socket.");
  553.         exit(1);
  554.     }
  555.     if (*msg != CHAR_OK) {
  556.         if (atoi(msg) != ERR_NOGROUP)
  557.             errmsg(2,"rrn: server response to GROUP %s:%s",
  558.                 gp->gname, msg);
  559.         return (-1);
  560.     }
  561.  
  562.     /* get subjects */
  563.     sprintf(msg,"XHDR subject %ld-%ld\r\n",first,last);
  564.  
  565.     put_server(msg);
  566.     if (get_server(msg, sizeof(msg)) < 0) {
  567.         errmsg(3,"rrn: Unexpected close of server socket.");
  568.         exit(1);
  569.     }
  570.     if (*msg == CHAR_FATAL)
  571.         return -1;
  572.     cnt = 0;
  573.     while (get_server(msg, sizeof(msg)) >= 0) {
  574.         if (*msg == '.')
  575.             break;
  576.  
  577.         /* parse article number */
  578.         s = msg;
  579.         while (isspace(*s)) s++;
  580.         n = strtol(s,0,10);
  581.         s = strchr(s,' ');
  582.     
  583.         if (n < first || n > last)        /* out of range */
  584.             continue;
  585.         if (Btst(gp->bm, n - gp->first))        /* already read */
  586.             continue;
  587.             
  588.         /* missing articles */
  589.         if (n != first)
  590.             bfset(gp->bm, first - gp->first, n-first);
  591.         
  592.         addart(gp, n, s);
  593.         
  594.         first = n+1;
  595.         cnt++;
  596.     }
  597.     gp->unread = cnt;
  598. }
  599.  
  600. readart(art_t *ap)
  601. {
  602.     char msg[256], *p;
  603.     int n;
  604.     Boolean trunc = FALSE;
  605.     unsigned art = ap->thread[ap->tindex];
  606.  
  607.     SetCursor(*gWatchCursor);
  608.  
  609.     sprintf(msg, "ARTICLE %u\r\n", art);
  610.     put_server(msg);    /* ask the server for the article */
  611.     if (get_server(msg, sizeof(msg)) < 0) {
  612.         errmsg(3,"rrn: Unexpected close of server socket.");
  613.         exit(1);
  614.     }
  615.     if (*msg != CHAR_OK) {
  616.         errmsg(1, "%s",msg);
  617.         return -1;
  618.     }
  619.  
  620.     p = artbuf;
  621.     for (;;) {
  622.         if (get_server(p, (int)ARTLINE_SZ) < 0) {
  623.             errmsg(3,"rrn: Unexpected close of server socket.");
  624.             exit(1);
  625.         }
  626.         if (p[0] == '.' && p[1] == '\0')
  627.             break;
  628.         if (trunc)
  629.             continue;
  630.         n = strlen(p);
  631.         if (p+n >= &artbuf[ARTBUF_SZ]) {
  632.             errmsg(1,"article truncated");
  633.             trunc = TRUE;
  634.             continue;
  635.         }
  636.         p += n;
  637.         *p++ = '\r';
  638.     }
  639.     artlen = p - artbuf;
  640.     ap->refcnt++;
  641.     
  642.     return 0;
  643. }
  644.  
  645. save_newsrc() {
  646.     FILE *nf;
  647.     grp_t *gp;
  648.     
  649.     /* create new newsrc */
  650.     if ((nf=fopen(newsrcnew,"w"))==NULL) {
  651.         errmsg(3,"Cannot create %s",newsrcnew); return; }
  652.         
  653.     fprintf(nf,"#config: %s %ld %ld\n",server,ngrps,txtsize);
  654.  
  655.     /* write subscribed groups */
  656.     for (gp=grp_head; gp; gp=gp->next) {
  657.         fprintf(nf, "%s: ",gp->gname);
  658.         putbitlist(nf, gp);
  659.         putc('\n', nf);
  660.     }
  661.     
  662.     /* tack on any gone, but not forgotten groups */
  663.     for (gp=grps; gp < &grps[ngrps]; gp++) {
  664.         if (gp->flags & G_INACT) {
  665.             fprintf(nf, "%s! ",gp->gname);
  666.             putbitlist(nf, gp);
  667.             putc('\n', nf);
  668.         }
  669.     }
  670.     
  671.     fclose(nf);
  672.     
  673.     remove(newsrcold);
  674.     rename(newsrc, newsrcold);
  675.     rename(newsrcnew, newsrc);
  676. }
  677.  
  678. putbitlist(FILE *nf, grp_t *gp)
  679. {
  680.     long n, first, last;
  681.     
  682.     if (gp->bm == NULL) {    /* new group, never listed */
  683.         fprintf(nf, "1-%ld", Max(gp->first-1, 1));
  684.         return;
  685.     }
  686.  
  687.     /* convert bitmap back to numeric form */
  688.     first = 1;
  689.     n = bfffc(gp->bm, 0, bmTOTAL(gp));
  690.     last = (n == -1) ? gp->last : gp->first + n - 1;
  691.     if (first == last)
  692.         fprintf(nf, "%ld", first);
  693.     else fprintf(nf, "%ld-%ld", first,last);        
  694.     
  695.     while (last != gp->last) {
  696.         if ((n = bfffs(gp->bm, n, bmTOTAL(gp))) == -1)
  697.             break;
  698.         first = gp->first + n;
  699.     
  700.         n = bfffc(gp->bm, first-gp->first, bmTOTAL(gp));
  701.         last = (n == -1) ? gp->last : gp->first + n -1;
  702.         if (first == last)
  703.             fprintf(nf, ",%ld", first);
  704.         else fprintf(nf, ",%ld-%ld", first,last);        
  705.     }
  706. }
  707.  
  708. char *
  709. Xrealloc(p, n)
  710. char *p;
  711. long n;
  712. {
  713.     char *p1 = NewPtr(n);
  714.     BlockMove(p, p1, n);
  715.     DisposPtr(p);
  716.     return p1;
  717. }
  718.  
  719. char *
  720. savestr(s) char *s; {
  721.     char *p = NewPtr(strlen(s)+1);
  722.     strcpy(p, s);
  723.     return (p);
  724. }
  725.