ka@spanky.UUCP (06/06/83)
/* * readr - visual news interface. */ static char SccsId[] = "@(#)visual.c 2.26 5/14/83"; static char Author[] = "@(#)visual interface written by Kenneth Almquist"; #ifdef HCURSES /* Mark Horton's version of curses */ #define CURSES #endif #ifdef ACURSES /* Ken Arnold's version of curses */ #define CURSES #endif #ifdef CURSES #ifdef USG #ifdef ACURSES #include <termio.h> #endif #endif #include <curses.h> #define SLEN LINES #else #include <fcntl.h> extern int _junked; #define clearok(xxx, flag) _junked = flag #define COLS 80 #define SLEN 24 #define HASSCROLL #ifdef USG #include <termio.h> #else #include <sgtty.h> #endif USG #endif CURSES #include <errno.h> #include "rparams.h" #ifdef CURSES #undef LINES #endif #define ARTWLEN (SLEN-2)/* number of lines used to display article */ #define PRLINE (SLEN-1)/* prompter line */ #define SPLINE (SLEN-2)/* secondary prompt line */ #define SECPRLEN 100 /* length of secondary prompter */ #define INTR 0177 /* returned by vgetc on interrupt */ #define META 0200 /* meta chatacter bit (as in emacs) */ /* print (display) flags */ #define HDRONLY 0001 /* print header only */ #define NOPRT 0002 /* don't print at all */ #define NEWART 0004 /* force article display to be regenerated */ #define HELPMSG 0010 /* display currently contains help message */ /* prun flags */ #define CWAIT 0001 /* type "continue?" and wait for return */ #define BKGRND 0002 /* run process in the background */ /* values of curflag */ #define CURP1 1 /* cursor after prompt */ #define CURP2 2 /* cursor after secondary prompt */ #define CURHOME 3 /* cursor at home position */ /* shell procedures to reply and post followups */ #ifndef REPLYPROG #define REPLYPROG "/usr/lib/news/reply" #endif #ifndef FOLLOWPROG #define FOLLOWPROG "/usr/lib/news/followup" #endif /* other files */ #ifndef VHELP #define VHELP "/usr/lib/news/vnews.help" #endif #define saveart oobit = bit;strcpy(ofilename1, filename);strcpy(ogroupdir, groupdir);hbufcp(&hbuf1, &h);ongsize = pngsize #define NLINES(h, fp) (h.numlines[0] ? h.intnumlines : (h.intnumlines=linecnt(fp),sprintf(h.numlines, "%d", h.intnumlines), h.intnumlines)) extern int errno; FILE *tmpfile(); int onint(), onquit(); /* * Kludge: space so that Mark's code can access beyond * the end of arrays without messing me up. */ static char junk[64]; /* variables shared between vnews routines */ static char linebuf[LBUFLEN]; /* temporary workspace */ static FILE *tfp; /* temporary file */ static char tfname[] = "/tmp/vnXXXXXX"; /* name of temp file */ static long artbody; /* offset of body into article */ static int quitflg; /* if set, then quit */ static int erased; /* current article has been erased */ static int artlines; /* # lines in article body */ static int artread; /* entire article has been read */ static int hdrend; /* end of header */ static int lastlin; /* number of lines in tempfile */ static int tflinno = 0; /* next line in tempfile */ static char secpr[SECPRLEN]; /* secondary prompt */ static char prompt[30]; /* prompter */ static short prflags; /* print flags (controls updscr) */ static short curflag; /* where to locate cursor */ static int dlinno; /* top line on screen */ static char timestr[20]; /* current time */ static int ismail; /* true if user has mail */ static char *mailf; /* user's mail file */ static int alflag; /* set if unprocessed alarm signal */ static int atend; /* set if at end of article */ static char cerase; /* erase character */ static char ckill; /* kill character */ static int ospeed; /* terminal speed */ static int pstatus; /* status return form process */ static int intflag; /* set if interrupt received */ #ifdef DIGPAGE static int endsuba; /* end of sub-article in digest */ #endif char *tft = "/usr/tmp/folXXXXXX"; /* * These were made static for u370 with its buggy cc. * I judged it better to have one copy with no ifdefs than * to conditionally compile them as automatic variables * in readr (which they originally were). Performance * considerations might warrent moving some of the simple * things into register variables, but I don't know what * breaks the u370 cc. */ static char goodone[BUFLEN]; /* last decent article */ static char ogroupdir[BUFLEN]; /* last groupdir */ static int rfq = 0; /* for last article */ static long ongsize; /* Previous ngsize */ static long pngsize; /* Printing ngsize */ static char *bptr; /* temp pointer. */ static char *tfilename; /* temporary file name */ static char ofilename1[BUFLEN]; /* previous file name */ static struct hbuf hbuf1, hbuf2, *hptr; /* for minusing */ static char *ptr1, *ptr2, *ptr3; /* for reply manipulation */ static int news = 0; static int aabs = FALSE; /* TRUE if we asked absolutely */ static char *ed, tf[100]; static struct hbuf h; /* ditto. */ static int i; static int oobit; /* last bit, really */ static char *oldsig; static int dgest = 0; static FILE *ofp; /* Current output file to terminal*/ static FILE *fp; /* current article to be printed*/ static int holdup; /* 1 iff should stop before hdr */ static int ignorenews; /* 1 iff readnews -p > /dev/null*/ static long timelastsaved; /* time newsrc last written out */ int catchcont(); readr() { #ifdef DEBUG fprintf(stderr, "readr()\n"); #endif if (aflag) { if (*datebuf) { if ((atime = cgtdate(datebuf)) == -1) xerror("Cannot parse date string"); } else atime = 0L; } if (pflag && ignoring()) ignorenews = TRUE; if (sigtrap) xxit(1); mktemp(tfname); if ((tfp = fopen(tfname, "w+")) == NULL) xerror("Can't create temp file"); unlink(tfname); mailf = getenv("MAIL"); ttysave(); signal(SIGINT, onint); signal(SIGQUIT, onquit); if (sigtrap) xxit(1); ttyraw(); timer(); /* loop reading articles. */ fp = NULL; obit = -1; nextng(); quitflg = 0; while (quitflg == 0) { if (getnextart(FALSE)) break; #ifdef DEBUG printf("after getnextart, fp %x, pos %d, bit %d, group '%s', filename '%s'\n", fp, ftell(fp), bit, groupdir, filename); #endif strcpy(goodone, filename); if (sigtrap) return; vcmd(); } if (news) botscreen(); ttycooked(); if (!news) fprintf(stderr, "No news.\n"); } /* * Read and execute a command. */ vcmd() { register c; char *p; int count; int countset; #ifdef DIGPAGE appfile(fp, dlinno + ARTWLEN + 1); endsuba = findend(dlinno); if (artlines > dlinno + ARTWLEN || endsuba > 0 && endsuba < artlines #else if ((appfile(fp, dlinno + ARTWLEN + 1), artlines > dlinno + ARTWLEN) #endif || (prflags & HDRONLY) && artlines > hdrend) { atend = 0; strcpy(prompt, "more? "); } else { atend = 1; strcpy(prompt, "next? "); if (! erased) clear(bit); /* article read */ } curflag = CURP1; p = prompt + strlen(prompt); countset = 0; count = 0; while ((c = vgetc()) >= '0' && c <= '9') { count = (count * 10) + (c - '0'); sprintf(p, "%d", count); countset = 1; } if (c == '\033') { /* escape */ strcat(prompt, "M-"); c = vgetc(); if (c != INTR) c |= META; } secpr[0] = '\0'; if (countset == 0) count = 1; docmd(c, count); if (c != '?') /* UGGH */ prflags &=~ HELPMSG; if (dlinno > 0) prflags &=~ HDRONLY; } /* * Process one command, which has already been typed in. */ docmd(c, count) { int i; char *findhist(); switch (c) { /* Show more of current article, or advance to next article */ case '\r': prflags &=~ NOPRT; if (atend) goto next; else if (prflags & HDRONLY) prflags &=~ HDRONLY; #ifdef DIGPAGE else if (endsuba > 0) dlinno = endsuba; #endif #ifdef HASSCROLL else if ((appfile(fp, dlinno + 2 * ARTWLEN), artread) && artlines - dlinno <= ARTWLEN + 2) dlinno = artlines - ARTWLEN; #endif else dlinno += ARTWLEN; break; /* No. Go on to next article. */ next: case 'n': itsbeenseen(h.ident); readmode = NEXT; if (fp != NULL) { /* always false? */ fclose(fp); fp = NULL; } clear(bit); saveart; nextbit(); break; /* Back up count pages */ case '\2': /* Control-B */ dlinno -= ARTWLEN * count; if (dlinno < 0) dlinno = 0; break; /* forward half a page */ case '\4': /* Control-D, as in vi */ dlinno += ARTWLEN/2 * count; break; /* forward count lines */ case '\16': /* Control-N */ case '\32': /* control-Z */ dlinno += count; break; /* bakcwards count lines */ case '\20': /* Control-P */ case '\31': /* Control-Y */ dlinno -= count; if (dlinno < 0) dlinno = 0; break; /* Turn displaying of article back on */ case 'l': case 'd': prflags &=~ NOPRT; break; /* display header */ case 'h': dlinno = 0; prflags |= HDRONLY; prflags &=~ NOPRT; break; /* * Unsubscribe to the newsgroup and go on to next group */ case 'U': case 'u': strcat(prompt, "u"); c = vgetc(); if (c == 'g') { obit = -1; if (fp != NULL) { fclose(fp); fp = NULL; } zapng = TRUE; saveart; if (nextng()) { if (actdirect == BACKWARD) msg("Can't back up."); else quitflg = 1; /* probably unnecessary */ } } else { if (c != INTR && c != ckill) msg("Illegal command"); } break; /* Print the current version of news */ case 'v': msg("News version: %s", news_version); break; /* Decrypt joke. Always does rot 13 */ case 'D': appfile(fp, 32767); for (i = hdrend ; i < artlines ; i++) { register char c, *p; tfget(linebuf, i); for (p = linebuf ; (c = *p) != '\0' ; p++) { if (c >= 'a' && c <= 'z') *p = (c - 'a' + 13) % 26 + 'a'; else if (c >= 'A' && c <= 'Z') *p = (c - 'A' + 13) % 26 + 'A'; } tfput(linebuf, i); } prflags |= NEWART; prflags &=~ (HDRONLY|NOPRT); break; /* write out the article someplace */ /* w writes out without the header */ case 's': case 'w': if (prget("file: ", linebuf)) break; if (linebuf[0] == '|') { /* not tested */ sprintf(secpr, "!(%s)<$A", linebuf + 1); updscr(); sprintf(linebuf, "A=%s export A;%s", filename, secpr + 1); secpr[0] = '\0'; goto execcmd; break; } { char *grn = groupdir; bptr = linebuf; /* vnews; look at this again later */ if (*bptr != '|' && *bptr != '/') { char hetyped[BUFLEN]; char *boxptr; strcpy(hetyped, bptr); if (boxptr = getenv("NEWSBOX")) if (index(boxptr, '%')) sprintf(bptr, boxptr, grn); else strcpy(bptr, boxptr); else if (hetyped[0] == '~' && hetyped[1] == '/') { strcpy(hetyped, bptr+2); strcpy(bptr, userhome); } else bptr[0] = '\0'; if (bptr[0]) strcat(bptr, "/"); if (hetyped[0] != '\0') strcat(bptr, hetyped); else strcat(bptr, "Articles"); } vsave(bptr, c == 's'); } break; /* back up */ case '-': case 'b': minus: aabs = TRUE; if (!*ofilename1) { msg("Can't back up."); break; } if (fp != NULL) { /* always true? */ fclose(fp); fp = NULL; } hbufcp(&hbuf2, &h); hbufcp(&h, &hbuf1); hbufcp(&hbuf1, &hbuf2); strcpy(bfr, filename); strcpy(filename, ofilename1); strcpy(ofilename1, bfr); obit = bit; if (strcmp(groupdir, ogroupdir)) { strcpy(bfr, groupdir); selectng(ogroupdir); strcpy(groupdir, ogroupdir); strcpy(ogroupdir, bfr); ngrp = 1; back(); } bit = oobit; oobit = obit; obit = -1; getnextart(TRUE); return FALSE; /* skip forwards */ case '+': caseplus: if (count == 0) break; saveart; last = bit; for (i = 0; i < count; i++) { nextbit(); if ((bit > pngsize) || (rflag && bit < 1)) break; } if (fp != NULL) { /* always true? */ fclose(fp); fp = NULL; } obit = -1; break; /* exit - time updated to that of most recently read article */ case 'q': quitflg = 1; break; /* cancel the article. */ case 'c': strcpy(prompt, "cancel? "); if (vgetc() != '\r') break; cancel_command(); break; /* escape to shell */ case '!': { register char *p; int flags; sprintf(linebuf, "A=%s export A;", filename); for (p = linebuf ; *p ; p++); if (prget("!", p)) break; execcmd: flags = CWAIT; if (*p == '\0') { strcpy(p, SHELL); flags = 0; } while (*p) p++; while (p > linebuf && p[-1] == ' ') p--; if (*--p == '&') { *p = '\0'; flags = BKGRND; } else { prflags |= NOPRT; } shcmd(linebuf, flags); break; } /* mail reply */ case 'r': reply(0); break; /* next newsgroup */ case 'N': if (fp != NULL) { fclose(fp); fp = NULL; } if (next_ng_command()) quitflg = 1; break; /* sepecific number */ case 'A': if (count > pngsize) { msg("not that many articles"); break; } readmode = SPEC; aabs = TRUE; bit = count; obit = -1; break; /* display parent article */ case 'p': if (h.followid[0] == '\0') { msg("no references line"); break; } if ((ptr1 = rindex(h.followid, ' ')) != NULL) ptr1++; else ptr1 = h.followid; strcpy(linebuf, ptr1); msg("%s", linebuf); curflag = CURP2; updscr(); /* may take this out later */ goto searchid; /* specific message ID. */ case '<': /* could improve this */ linebuf[0] = '<'; if (prget("<", linebuf+1)) break; searchid: secpr[0] = '\0'; if (index(linebuf, '@') == NULL && index(linebuf, '>') == NULL) { ptr1 = linebuf; if (*ptr1 == '<') ptr1++; ptr2 = index(ptr1, '.'); if (ptr2 != NULL) { *ptr2++ = '\0'; sprintf(bfr, "<%s@%s.UUCP>", ptr2, ptr1); strcpy(linebuf, bfr); } } if (index(linebuf, '>') == NULL) strcat(linebuf, ">"); ptr1 = findhist(linebuf); if (ptr1 == NULL) { msg("%s not found", linebuf); break; } ptr2 = index(ptr1, '\t'); ptr3 = index(++ptr2, '\t'); ptr2 = index(++ptr3, ' '); if (ptr2) *ptr2 = '\0'; ptr2 = index(ptr3, '/'); *ptr2++ = '\0'; aabs = TRUE; if (cflag) clear(bit); else { fclose(fp); fp = NULL; putc('\n', ofp); } hbufcp(&hbuf1, &h); saveart; strcpy(ogroupdir, ptr3); if (strcmp(groupdir, ogroupdir)) { strcpy(bfr, groupdir); selectng(ogroupdir); strcpy(groupdir, ogroupdir); strcpy(ogroupdir, bfr); back(); } sscanf(ptr2, "%d", &bit); oobit = obit; obit = -1; getnextart(TRUE); rfq = 0; break; /* follow-up article */ case 'f': reply(1); break; /* erase - pretend we haven't seen this article. */ case 'e': erased = 1; set(bit); goto caseplus; /* skip this article for now */ break; case '#': msg("Article %d of %ld", rfq ? oobit : bit, pngsize); break; /* error */ case '?': { FILE *helpf; if ((helpf = fopen(VHELP, "r")) == NULL) { msg("Can't open help file"); break; } move(0, 0); while (fgets(linebuf, 1000, helpf) != NULL) addstr(linebuf); fclose(helpf); prflags |= HELPMSG|NEWART; } break; /* Interrupt received */ case INTR: break; default: if (c != ckill) msg("Illegal command"); break; } return FALSE; } cancel_command() { tfilename = filename; hptr = &h; readmode = SPEC; strcpy(rcbuf, hptr->path); ptr1 = index(rcbuf, ' '); if (ptr1) *ptr1 = 0; if (uid == ROOTID) i = 0; /* root gets to cancel */ else i = strcmp(username, rcbuf); if (i != 0) { msg("Can't cancel what you didn't write."); return; } if (!cancel(ofp, hptr, i) && hptr == &h) { clear(bit); saveart; nextbit(); obit = -1; fp = NULL; } if (fp != NULL) fclose(fp); fp = NULL; } /* * Generate replies and followups. * We generate a file and then call a shell procedure to do the work. */ #define MODGROUPS "fa.all,mod.all,all.mod,all.announce," reply(followup) { char *arg[8]; FILE *tfp; char subj[132]; char *p; char *replyname(); strcpy(tf, tft); mktemp(tf); if ((tfp = fopen(tf, "w")) == NULL) { msg("Can't create %s", tf) ; return; } strcpy(subj, h.title); if (!prefix(subj, "Re:") && !prefix(subj, "re:")) { strcpy(bfr, subj); sprintf(subj, "Re: %s", bfr); } arg[0] = "/bin/sh"; /* test for moderated group */ if (followup) { strcpy(linebuf, h.nbuf); ngcat(linebuf); if (ngmatch(linebuf, MODGROUPS)) followup = 2; } if (followup != 1) { /* send mail */ #ifdef INTERNET if (followup == 2 && h.sender[0]) p = h.sender; else #endif p = replyname(&h); fprintf(tfp, "To: %s\n", p); fprintf(tfp, "Subject: %s\n", subj); if (followup != 2) fprintf(tfp, "In-reply-to: your article %s\n", h.ident); arg[1] = REPLYPROG; arg[4] = p; } else { p = h.nbuf; if (h.followto[0]) p = h.followto; strcpy(linebuf, p); launder(linebuf); fprintf(tfp, "Newsgroups: %s\n", linebuf); fprintf(tfp, "Subject: %s\n", subj); if (h.keywords[0]) fprintf(tfp, "Keywords: %s\n", h.keywords); if (h.distribution[0]) fprintf(tfp, "Distribution: %s\n", h.distribution); arg[1] = FOLLOWPROG; arg[4] = linebuf; } if (followup != 0) { bfr[0] = 0; /* References */ if (h.followid[0]) { strcpy(bfr, h.followid); strcat(bfr, " "); } strcat(bfr, h.ident); fprintf(tfp, "References: %s\n", bfr); } putc('\n', tfp); fclose(tfp); arg[2] = tf; arg[3] = filename; /* arg[4] (name or newsgroup) set above */ arg[5] = NULL; prun(arg, 0); if (pstatus != 0) { if (pstatus != 22 << 8) p = "Command failed"; else if (followup == 1) p = "Article not posted"; else p = "Mail not sent"; msg(p); } prflags |= NOPRT; } next_ng_command() { if (prget("group? ", linebuf)) return FALSE; bptr = linebuf; if (!*bptr || *bptr == '-') { obit = -1; if (*bptr) actdirect = BACKWARD; saveart; if (nextng()) { if (actdirect == BACKWARD) msg("Can't back up."); else return TRUE; } return FALSE; } while (isspace(*bptr)) bptr++; if (!validng(bptr)) { msg("No such group."); return FALSE; } obit = -1; readmode = SPEC; saveart; back(); selectng(bptr); return FALSE; } /* * Find the next article we want to consider, if we're done with * the last one, and show the header. */ getnextart(minus) int minus; { if (minus) goto nextart2; /* Kludge for "-" command. */ if (bit == obit) /* Return if still on same article as last time */ return 0; nextart: if (news) { curflag = CURHOME; #ifdef CURSES move(0, 0); /* let user know we are thinking */ refresh(); #else _amove(0, 0); vflush(); #endif } dgest = 0; /* If done with this newsgroup, find the next one. */ while (((long) bit > ngsize) || (rflag && bit < 1)) { int i; if (i=nextng()) { if (actdirect == BACKWARD) { msg("Can't back up."); actdirect = FORWARD; continue; } else /* if (rfq++ || pflag || cflag) */ return 1; } if (rflag) bit = ngsize + 1L; else bit = -1; } /* speed things up by not searching for article -1 */ if (bit < 0) { bit = 0; nextbit(); aabs = FALSE; goto nextart; } nextart2: #ifdef DEBUG fprintf(stderr, "article: %s/%d\n", groupdir, bit); #endif if (rcreadok) rcreadok = 2; /* have seen >= 1 article */ sprintf(filename, "%s/%d", dirname(groupdir), bit); if (rfq && goodone[0]) /* ??? */ strcpy(filename, goodone); if (sigtrap == SIGHUP) return 1; #ifdef DEBUG fprintf(stderr, "filename = '%s'\n", filename); #endif /* Decide if we want to show this article. */ if (ignorenews || ((fp = fopen(filename, "r")) == NULL) || (hread(&h, fp, TRUE) == NULL) || (!rfq && !select(&h, aabs))) { #ifdef DEBUG fprintf(stderr, "Bad article '%s'\n", filename); #endif if (fp != NULL) { fclose(fp); fp = NULL; } clear(bit); obit = -1; nextbit(); aabs = FALSE; goto nextart; } aabs = FALSE; actdirect = FORWARD; news = TRUE; { /* strip off any notesfile header */ register c; if ((c = strlen(h.title)) >= 4 && strcmp(h.title + c - 4, "(nf)") == 0) { if ((c = getc(fp)) != '#') { ungetc(c, fp); } else { while ((c = getc(fp)) != '\n' && c != EOF); while ((c = getc(fp)) != '\n' && c != EOF); while ((c = getc(fp)) != '\n' && c != EOF); } } } artbody = ftell(fp); fmthdr(); artlines = lastlin; artread = 0; prflags |= NEWART; prflags &=~ NOPRT; if (hdrend < ARTWLEN) prflags |= HDRONLY; dlinno = 0; erased = 0; obit = bit; return 0; } /* * Print out whatever the appropriate header is */ hdr() { abort(); } fmthdr() { char *vbriefdate(); lastlin = 0; if (ngrp) { pngsize = ngsize; ngrp--; } if (!hflag) { sprintf(linebuf, "Article %s %s", h.ident, vbriefdate(h.subdate)); tfappend(linebuf); } vhprint(&h, pflag ? 1 : 0); sprintf(linebuf, "(%d lines)", NLINES(h, fp)); tfappend(linebuf); tfappend(""); hdrend = lastlin; } /* Arpa format: Sat, 14-May-83 03:45:14 EDT */ /* Bugs: doesn't work on article with non-arpa dates */ char * vbriefdate(q) register char *q; { register char *p; register i; char day[2]; p = bfr; for (i = 3 ; --i >= 0 ; ) *p++ = *q++; *p++ = ' '; q += 2; day[0] = *q++; if (*q != '-') day[1] = *q++; else day[1] = '\0'; q++; for (i = 3 ; --i >= 0 ; ) *p++ = *q++; *p++ = ' '; *p++ = day[0]; if (day[1]) *p++ = day[1]; q += 5; *p++ = ' '; if (q[-1] != '0') *p++ = q[-1]; for (i = 4 ; --i >= 0 ; ) *p++ = *q++; *p++ = '\0'; return bfr; } /* * Print the file header to the temp file. */ vhprint(hp, verbose) register struct hbuf *hp; int verbose; { register char *p1, *p2; char fname[BUFLEN]; char *tailpath(); fname[0] = '\0'; /* init name holder */ p1 = index(hp->from, '('); /* Find the sender's full name. */ if (p1 == NULL && hp->path[0]) p1 = index(hp->path, '('); if (p1 != NULL) { strcpy(fname, p1+1); p2 = index(fname, ')'); if (p2 != NULL) *p2 = '\0'; } sprintf(linebuf, "Subject: %s", hp->title); tfappend(linebuf); if (!hflag && hp->keywords[0]) sprintf(linebuf, "Keywords: %s", hp->keywords), tfappend(linebuf); if (verbose) { sprintf(linebuf, "From: %s", hp->from); tfappend(linebuf); sprintf(linebuf, "Path: %s", hp->path); tfappend(linebuf); if (hp->organization[0]) sprintf(linebuf, "Organization: %s", hp->organization), tfappend(linebuf); } else { if (p1 != NULL) *--p1 = '\0'; /* bump over the '(' */ #ifdef INTERNET /* * Prefer Path line if it's in internet format, or if we don't * understand internet format here, or if there is no reply-to. */ sprintf(linebuf, "From: %s", hp->from); #else sprintf(linebuf, "Path: %s", tailpath(hp)); #endif if (fname[0] != '\0') { strcat(linebuf, " ("); strcat(linebuf, fname); if (hp->organization[0] && !hflag) { strcat(linebuf, " @ "); strcat(linebuf, hp->organization); } strcat(linebuf, ")"); } tfappend(linebuf); if (p1 != NULL) *p1 = ' '; if (hp->ctlmsg[0]) { sprintf(linebuf, "Control: %s", hp->ctlmsg); tfappend(linebuf); } } ngdel(strcpy(bfr, hp->nbuf)); if (verbose) { sprintf(linebuf, "Newsgroups: %s", bfr); tfappend(linebuf); sprintf(linebuf, "Date: %s", hp->subdate); tfappend(linebuf); if (hp->sender[0]) sprintf(linebuf, "Sender: %s", hp->sender), tfappend(linebuf); if (hp->replyto[0]) sprintf(linebuf, "Reply-To: %s", hp->replyto), tfappend(linebuf); if (hp->followto[0]) sprintf(linebuf, "Followup-To: %s", hp->followto), tfappend(linebuf); } else if (strcmp(bfr, groupdir) != 0) sprintf(linebuf, "Newsgroups: %s", bfr), tfappend(linebuf); } /* * Append file to temp file, handling control characters, folding lines, etc. * We don't grow the temp file to more than nlines so that a user won't have * to wait for 20 seconds to read in a monster file from net.sources. * What we rally want is coroutines--any year now. */ #define ULINE 0200 static char *maxcol; appfile(iop, nlines) register FILE *iop; { register int c; register char *icol; /* &linebuf[0] <= icol <= maxcol */ if (artread || artlines >= nlines) return; maxcol = linebuf; icol = linebuf; while ((c = getc(iop)) != EOF) { switch (c) { case ' ': if (icol == maxcol && icol < linebuf + LBUFLEN - 1) { *icol++ = ' '; maxcol = icol; } else { icol++; } break; case '\t': icol = (icol - linebuf &~ 07) + 8 + linebuf; growlin(icol); break; case '\b': if (icol > linebuf) --icol; break; case '\n': outline(); if (artlines >= nlines) return; icol = linebuf; break; case '\r': icol = linebuf; break; case '\f': outline(); outline(); outline(); if (artlines >= nlines) return; icol = linebuf; break; default: if (c < ' ' || c > '~') break; else if (icol >= linebuf + LBUFLEN - 1) icol++; else if (icol == maxcol) { *icol++ = c; maxcol = icol; } else if (*icol == ' ') *icol++ = c; else if (c == '_') *icol++ |= ULINE; else *icol++ = (c | ULINE); break; } } if (maxcol != linebuf) /* file not terminated with newline */ outline(); artread++; } growline(col) char *col; { while (maxcol < col && maxcol < linebuf + LBUFLEN - 1) *maxcol++ = ' '; } outline() { *maxcol = '\0'; if (strncmp(linebuf, ">From ", 6) == 0) { register char *p; for (p = linebuf ; (*p = p[1]) != '\0' ; p++); } tfappend(linebuf); if (maxcol > linebuf) artlines = lastlin; maxcol = linebuf; } prget(prompter, buf) char *prompter, *buf; { char *p, *q, *r; int c, lastc; curflag = CURP2; r = buf; lastc = '\0'; for (;;) { *r = '\0'; p = secpr; for (q = prompter ; *q ; q++) *p++ = *q; for (q = buf ; *q ; q++) { if (p < &secpr[SECPRLEN-1] && *q >= ' ' && *p <= '~') *p++ = *q; } *p = '\0'; c = vgetc(); if (c == '\r' || c == INTR) { break; } if (c == cerase) { if (lastc == '\\') r[-1] = c; else if (r > buf) r--; } else if (c == ckill) { if (lastc == '\\') r[-1] = c; else r = buf; } else { *r++ = c; } lastc = c; } curflag = CURHOME; secpr[0] = '\0'; return (c == INTR); } /* * Execute a shell command. */ shcmd(cmd, flags) char *cmd; { char *arg[4]; arg[0] = SHELL, arg[1] = "-c", arg[2] = cmd, arg[3] = NULL; prun(arg, flags); } prun(args, flags) char **args; { int pid; int i; int (*savequit)(); if (!(flags & BKGRND)) { botscreen(); ttycooked(); } while ((pid = fork()) == -1) sleep(1); /* must not clear alarm */ if (pid == 0) { for (i = 3 ; i < 20 ; i++) close(i); if (flags & BKGRND) { signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); close(0), close(1), close(2); open("/dev/null", 2); dup(0), dup(0); } umask(savmask); execvp(args[0], args); fprintf(stderr, "%s: not found\n", args[0]); exit(20); } if (!(flags & BKGRND)) { savequit = signal(SIGQUIT, SIG_IGN); while ((i = wait(&pstatus)) != pid && (i != -1 || errno == EINTR)); if (flags & CWAIT) { fprintf(stderr, "continue? "); while (getchar() != '\n'); } signal(SIGQUIT, savequit); ttyraw(); clearok(curscr, 1); } } #ifdef DIGPAGE /* * Find end of current subarticle in digets. */ findend(l) { register i; register char *p; for (i = l ; i < l + ARTWLEN && i < lastlin ; i++) { tfget(linebuf, i); for (p = linebuf ; *p == '-' ; p++); if (p > linebuf + 24) return i + 1; } return 0; } #endif /*** Routines for handling temporary file ***/ /* * Append to temp file. * Long lines are folded. */ tfappend(line) char *line; { while (strlen(line) > COLS) { tfput(line, lastlin++); line += COLS; } tfput(line, lastlin++); } tfput(line, linno) char *line; { register char *p; register FILE *rtfp; /* try to make it a litte faster */ register int i; p = line, i = COLS; tfseek(linno, 1); rtfp = tfp; while (--i >= 0) { if (*p) putc(*p++, rtfp); else putc('\0', rtfp); } tflinno++; } tfget(line, linno) char *line; { tfseek(linno, 0); fread(line, COLS, 1, tfp); line[COLS] = '\0'; tflinno++; } tfseek(linno, wrflag) { static int last = 1; if (linno != tflinno || wrflag != last) { fseek(tfp, (long)linno * COLS, 0); tflinno = linno; last = wrflag; } } msg(s, a1, a2, a3, a4) char *s; { sprintf(secpr, s, a1, a2, a3, a4); } /* * Update the display. * The display is entirely controlled by this routine, * which means that this routine may get pretty snarled. */ static int savelinno = -1; /* dlinno on last call to updscr */ static int savepr; /* prflags on last call */ updscr() { int count; int i; if (checkin()) return; if ((prflags & HELPMSG) == 0 && (dlinno != savelinno || savepr != prflags) && quitflg == 0) { if (dlinno != savelinno) prflags &=~ NOPRT; count = ARTWLEN; if (prflags & NOPRT) count = 0; if ((prflags & HDRONLY) && count > hdrend) count = hdrend - dlinno; #ifdef DIGPAGE if (endsuba > 0 && count > endsuba - dlinno) count = endsuba - dlinno; #endif #ifndef CURSES if ((prflags & NEWART) == 0 && abs(dlinno - savelinno) <= count/2) ushift(0, ARTWLEN - 1, dlinno - savelinno); #endif if (count > lastlin - dlinno) count = lastlin - dlinno; for (i = 0 ; i < ARTWLEN ; i++) clrline(i); for (i = 0 ; i < count ; i++) { tfget(linebuf, dlinno + i); mvaddstr(i, 0, linebuf); } prflags &=~ NEWART; savepr = prflags; savelinno = dlinno; } clrline(SPLINE), clrline(PRLINE); if (strlen(secpr) <= COLS) mvaddstr(PRLINE, 0, prompt); mvaddstr(PRLINE, 48, timestr); mvaddstr(PRLINE, 20, groupdir); addch(' '); addnum(bit); addch('/'); addnum((int)pngsize); addch(' '); if (ismail) mvaddstr(PRLINE, 62, ismail > 1? "MAIL" : "mail"); mvaddstr(SPLINE, 0, secpr); if (curflag == CURP1) move(PRLINE, strlen(prompt)); else if (curflag == CURHOME) move(0, 0); refresh(); } addnum(n) { if (n >= 10) addnum(n / 10); addch(n % 10 + '0'); } /*** alarm handler ***/ /* #include <time.h> */ /* * Called on alarm signal. * Simply sets flag, signal processed later. */ onalarm() { alflag++; } /* * Process alarm signal (or start clock) */ timer() { long tod; int hour; int i; struct tm *t; struct stat statb; long time(); static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; static long oldmsize = 1000000L; static int rccount = 10; alflag = 0; signal(SIGALRM, onalarm); time(&tod); t = localtime(&tod); i = 60 - t->tm_sec; alarm(i > 30? 30 : i); /* reset alarm */ hour = t->tm_hour % 12; if (hour == 0) hour = 12; sprintf(timestr, "%.3s %d %d:%02d", months + 3 * t->tm_mon, t->tm_mday, hour, t->tm_min); ismail = 0; if (mailf != NULL && stat(mailf, &statb) >= 0 && statb.st_size > 0L) { ismail = 1; if (oldmsize < statb.st_size) { ismail = 2; /* new mail */ beep(); } } else { statb.st_size = 0L; } oldmsize = statb.st_size; if (uflag && !xflag && --rccount < 0) { writeoutrc(); if (secpr[0] == '\0') strcpy(secpr, ".newsrc updated"); rccount = 10; } } /*** Terminal I/O ***/ #define INBUFSIZ 8 char inbuf[INBUFSIZ]; /* input buffer */ char outbuf[BUFSIZ]; /* output buffer */ int innleft = 0; /* # of chars in input buffer */ int outnleft = BUFSIZ; /* room left in output buffer */ char *innext; /* next input character */ char *outnext = outbuf; /* next space in output buffer */ #ifndef CURSES #ifdef USG int oflags; /* fcntl flags (for nodelay read) */ #endif #endif /* * Input a character */ vgetc() { register c; recurse: if (--innleft >= 0) { c = *innext++; } else { if (alflag) timer(); updscr(); /* update the display */ for (;;) { if (innleft > 0 || alflag) goto recurse; intflag = 0; #ifndef CURSES #ifdef USG if (oflags & O_NDELAY) { oflags &=~ O_NDELAY; fcntl(0, F_SETFL, oflags); } #endif #endif if ((innleft = read(0, inbuf, INBUFSIZ)) > 0) break; if (errno != EINTR) abort(); /* "Can't happen" */ if (intflag) { intflag--; return INTR; } } innext = inbuf + 1; innleft--; c = inbuf[0]; } #ifndef USG #ifndef CBREAK c &= 0177; if (c == '\034') /* FS character */ onquit(); #endif #endif if (c == '\f') { clearok(curscr, 1); goto recurse; } return c; } /* * Check for terminal input */ checkin() { #ifndef CURSES #ifndef USG long count; #endif #endif if (innleft > 0 || alflag) return 1; #if !defined(CURSES) && (defined(USG) || defined(FIONREAD)) if (ospeed == B9600) return 0; vflush(); if (ospeed <= B300) ttyowait(); #ifdef USG if ((oflags & O_NDELAY) == 0) { oflags |= O_NDELAY; fcntl(0, F_SETFL, oflags); } if ((innleft = read(0, inbuf, INBUFSIZ)) > 0) { innext = inbuf; return 1; } #else count = 0; /* in case FIONREAD fails */ ioctl(0, FIONREAD, &count); if (count) return 1; #endif #endif return 0; } /* * flush terminal input queue. */ clearin() { #ifdef USG ioctl(0, TCFLSH, 0); #else struct sgttyb tty; gtty(0, &tty); stty(0, &tty); #endif innleft = 0; } vputc(c) { if (--outnleft < 0) { vflush(); outnleft--; } *outnext++ = c; } /* * Flush the output buffer */ vflush() { register char *p; register int i; for (p = outbuf ; p < outnext ; p += i) { if ((i = write(1, p, outnext - p)) < 0) { if (errno != EINTR) abort(); /* "Can't happen" */ i = 0; } } outnleft = BUFSIZ; outnext = outbuf; } /*** terminal modes ***/ #ifndef CURSES #ifdef USG static struct termio oldtty, newtty; /* * Save tty modes */ ttysave() { if (ioctl(1, TCGETA, &oldtty) < 0) xerror("Can't get tty modes"); newtty = oldtty; newtty.c_iflag &=~ (INLCR|IGNCR|ICRNL); newtty.c_oflag &=~ (OPOST); newtty.c_lflag &=~ (ICANON|ECHO|ECHOE|ECHOK|ECHONL); newtty.c_lflag |= (NOFLSH); newtty.c_cc[VMIN] = 1; newtty.c_cc[VTIME] = 0; cerase = oldtty.c_cc[VERASE]; ckill = oldtty.c_cc[VKILL]; ospeed = oldtty.c_cflag & CBAUD; } /* * Set tty modes for visual processing */ ttyraw() { while (ioctl(1, TCSETAF, &newtty) < 0 && errno == EINTR); } ttyowait() { /* wait for output queue to drain */ while (ioctl(1, TCSETAW, &newtty) < 0 && errno == EINTR); } /* * Restore tty modes */ ttycooked() { while (ioctl(1, TCSETAF, &oldtty) < 0 && errno == EINTR); oflags &=~ O_NDELAY; fcntl(0, F_SETFL, oflags) ; } #else static struct sgttyb oldtty, newtty; /* * Save tty modes */ ttysave() { if (gtty(1, &oldtty) < 0) xerror("Can't get tty modes"); newtty = oldtty; newtty.sg_flags &=~ (CRMOD|ECHO|XTABS); #ifdef CBREAK newtty.sg_flags |= CBREAK; #else newtty.sg_flags |= RAW; #endif cerase = oldtty.sg_erase; ckill = oldtty.sg_kill; ospeed = oldtty.sg_ospeed; } /* * Set tty modes for visual processing */ ttyraw() { while (stty(1, &newtty) < 0 && errno == EINTR); } ttyowait() { /* wait for output queue to drain */ /* I'm not sure how to do this on Berkeley */ } /* * Restore tty modes */ ttycooked() { while (stty(1, &oldtty) < 0 && errno == EINTR); } #endif #else #ifdef HCURSES ttysave() { initscr(); idlok(stdscr, 1); intrflush(curscr, 0); cerase = erasechar(); ckill = killchar(); } ttyraw() { reset_prog_mode(); cbreak(); nonl(); noecho(); } ttycooked() { reset_shell_mode(); } int _endwin; /* [expletives deleted] */ #else ttysave() { #ifdef USG struct termio tty; #else struct sgttyb tty; #endif initscr(); #ifdef USG ioctl(0, TCGETA, &tty); cerase = tty.c_cc[VERASE]; ckill = tty.c_cc[VKILL]; #else gtty(0, &tty); cerase = tty.sg_erase; ckill = tty.sg_kill; #endif } ttyraw() { /* * Vnews really isn't designed to work with RAW mode, so if you * have anything approaching CBREAK mode, use it. */ #ifdef CBREAK crmode(); #else raw(); #endif nonl(); noecho(); } ttycooked() { resetty(); } #endif #endif CURSES /*** signal handlers ***/ onint() { if (!news) { ttycooked(); xxit(1); } signal(SIGINT, onint); clearin(); /* flush input queue */ intflag++; } onquit() { ttycooked(); abort(); /* later, change to exit(0) */ } /*** stolen from rfuncs2.c and modified ***/ vsave(to, hdflag) register char *to; { register FILE *ufp; int isprogram = 0; int isnew = 1; long saveoff; #define hh h #define hfp fp saveoff = ftell(fp); fseek(fp, artbody, 0); if (hdflag) { ufp = fopen(to, "r"); if (ufp != NULL) { fclose(ufp); isnew = 0; } } umask(savmask); if ((ufp = fopen(to, hdflag? "a" : "w")) == NULL) { msg("Cannot open %s", to); goto out; } /* * V7MAIL code is here to conform to V7 mail format. * If you need a different format to be able to * use your local mail command (such as four ^A's * on the end of articles) substitute it here. */ if (hdflag) { #ifdef V7MAIL fprintf(ufp, "From %s %s", #ifdef INTERNET hh.from, #else hh.path, #endif ctime(&hh.subtime)); #endif hprint(&hh, ufp, 2); #ifdef V7MAIL tprint(hfp, ufp, TRUE); putc('\n', ufp); /* force blank line at end (ugh) */ #else tprint(hfp, ufp, FALSE); #endif } else { tprint(hfp, ufp, FALSE); } if (isprogram) pclose (ufp); else fclose(ufp); if (hdflag) msg("file: %s %s", to, isnew ? "created" : "appended"); else msg("file: %s written", to); out: umask(N_UMASK); fseek(fp, saveoff, 0); }