version from mogul with loop unrolled for speed
[unix-history] / usr / src / usr.bin / msgs / msgs.c
CommitLineData
46ebe0eb 1#ifndef lint
ef7d2615 2static char sccsid[] = "@(#)msgs.c 4.7 %G%";
46ebe0eb
RH
3#endif lint
4/*
5 * msgs - a user bulletin board program
6 *
7 * usage:
8 * msgs [fhlopq] [[-]number] to read messages
9 * msgs -s to place messages
10 * msgs -c [-days] to clean up the bulletin board
11 *
12 * prompt commands are:
13 * y print message
14 * n flush message, go to next message
15 * q flush message, quit
16 * p print message, turn on 'pipe thru more' mode
17 * P print message, turn off 'pipe thru more' mode
18 * - reprint last message
19 * s[-][<num>] [<filename>] save message
20 * m[-][<num>] mail with message in temp mbox
21 * x exit without flushing this message
22 */
23
24#define V7 /* will look for TERM in the environment */
25#define OBJECT /* will object to messages without Subjects */
26/* #define REJECT /* will reject messages without Subjects
27 (OBJECT must be defined also) */
28/* #define UNBUFFERED /* use unbuffered output */
29
30#include <stdio.h>
2e3d53b4 31#include <sys/param.h>
46ebe0eb 32#include <signal.h>
41c894fb 33#include <dir.h>
46ebe0eb
RH
34#include <sys/stat.h>
35#include <ctype.h>
36#include <pwd.h>
37#include <sgtty.h>
38#include "msgs.h"
39
40#define CMODE 0666 /* bounds file creation mode */
41#define NO 0
42#define YES 1
43#define SUPERUSER 0 /* superuser uid */
44#define DAEMON 1 /* daemon uid */
45#define NLINES 24 /* default number of lines/crt screen */
46#define NDAYS 21 /* default keep time for messages */
47#define DAYS *24*60*60 /* seconds/day */
48#define TEMP "/tmp/msgXXXXXX"
49#define MSGSRC ".msgsrc" /* user's rc file */
50#define BOUNDS "bounds" /* message bounds file */
51#define NEXT "Next message? [yq]"
52#define MORE "More? [ynq]"
53#define NOMORE "(No more) [q] ?"
54
55typedef char bool;
56
57FILE *newmsg;
58char *sep = "-";
59char inbuf[BUFSIZ];
60char fname[128];
61char cmdbuf[128];
62char subj[128];
63char from[128];
64char date[128];
65char *ptr;
66char *in;
67bool local;
68bool ruptible;
69bool totty;
70bool seenfrom;
71bool seensubj;
72bool blankline;
73bool printing = NO;
74bool mailing = NO;
75bool quitit = NO;
76bool sending = NO;
77bool intrpflg = NO;
4bdc6489 78bool tstpflag = NO;
46ebe0eb
RH
79int uid;
80int msg;
81int prevmsg;
82int lct;
83int nlines;
84int Lpp = NLINES;
85time_t t;
86time_t keep;
87struct sgttyb otty;
88
89char *ctime();
90char *nxtfld();
91int onintr();
4bdc6489 92int onsusp();
46ebe0eb
RH
93off_t ftell();
94FILE *popen();
95struct passwd *getpwuid();
96
97extern int errno;
98
99/* option initialization */
100bool hdrs = NO;
101bool qopt = NO;
102bool hush = NO;
103bool send = NO;
104bool locomode = NO;
105bool pause = NO;
106bool clean = NO;
107bool lastcmd = NO;
108
109main(argc, argv)
110int argc; char *argv[];
111{
112 bool newrc, already;
113 int rcfirst = 0; /* first message to print (from .rc) */
ef7d2615 114 int rcback = 0; /* amount to back off of rcfirst */
46ebe0eb
RH
115 int firstmsg, nextmsg, lastmsg = 0;
116 int blast = 0;
117 FILE *bounds, *msgsrc;
118
119#ifndef UNBUFFERED
120 char obuf[BUFSIZ];
121 setbuf(stdout, obuf);
122#else
123 setbuf(stdout, NULL);
124#endif
125
126 gtty(fileno(stdout), &otty);
127 time(&t);
128 setuid(uid = getuid());
129 ruptible = (signal(SIGINT, SIG_IGN) == SIG_DFL);
130 if (ruptible)
131 signal(SIGINT, SIG_DFL);
132
133 argc--, argv++;
134 while (argc > 0) {
135 if (isdigit(argv[0][0])) { /* starting message # */
136 rcfirst = atoi(argv[0]);
137 }
138 else if (isdigit(argv[0][1])) { /* backward offset */
139 rcback = atoi( &( argv[0][1] ) );
140 }
141 else {
142 ptr = *argv;
143 while (*ptr) switch (*ptr++) {
144
145 case '-':
146 break;
147
148 case 'c':
149 if (uid != SUPERUSER && uid != DAEMON) {
150 fprintf(stderr, "Sorry\n");
151 exit(1);
152 }
153 clean = YES;
154 break;
155
156 case 'f': /* silently */
157 hush = YES;
158 break;
159
160 case 'h': /* headers only */
161 hdrs = YES;
162 break;
163
164 case 'l': /* local msgs only */
165 locomode = YES;
166 break;
167
168 case 'o': /* option to save last message */
169 lastcmd = YES;
170 break;
171
172 case 'p': /* pipe thru 'more' during long msgs */
173 pause = YES;
174 break;
175
176 case 'q': /* query only */
177 qopt = YES;
178 break;
179
180 case 's': /* sending TO msgs */
181 send = YES;
182 break;
183
184 default:
185 fprintf(stderr,
186 "usage: msgs [fhlopq] [[-]number]\n");
187 exit(1);
188 }
189 }
190 argc--, argv++;
191 }
192
193 /*
194 * determine current message bounds
195 */
196 sprintf(fname, "%s/%s", USRMSGS, BOUNDS);
197 bounds = fopen(fname, "r");
198
199 if (bounds != NULL) {
200 fscanf(bounds, "%d %d\n", &firstmsg, &lastmsg);
201 fclose(bounds);
202 blast = lastmsg; /* save upper bound */
203 }
204
205 if (clean)
206 keep = t - (rcback? rcback : NDAYS) DAYS;
207
208 if (clean || bounds == NULL) { /* relocate message bounds */
2e3d53b4 209 struct direct *dp;
46ebe0eb
RH
210 struct stat stbuf;
211 bool seenany = NO;
2e3d53b4 212 DIR *dirp;
46ebe0eb 213
2e3d53b4
RH
214 dirp = opendir(USRMSGS);
215 if (dirp == NULL) {
46ebe0eb
RH
216 perror(USRMSGS);
217 exit(errno);
218 }
219
220 firstmsg = 32767;
221 lastmsg = 0;
222
2e3d53b4
RH
223 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)){
224 register char *cp = dp->d_name;
46ebe0eb
RH
225 register int i = 0;
226
2e3d53b4
RH
227 if (dp->d_ino == 0)
228 continue;
229 if (dp->d_namlen == 0)
46ebe0eb
RH
230 continue;
231
232 if (clean)
233 sprintf(inbuf, "%s/%s", USRMSGS, cp);
234
235 while (isdigit(*cp))
236 i = i * 10 + *cp++ - '0';
237 if (*cp)
238 continue; /* not a message! */
239
240 if (clean) {
241 if (stat(inbuf, &stbuf) != 0)
242 continue;
243 if (stbuf.st_mtime < keep
244 && stbuf.st_mode&S_IWRITE) {
245 unlink(inbuf);
246 continue;
247 }
248 }
249
250 if (i > lastmsg)
251 lastmsg = i;
252 if (i < firstmsg)
253 firstmsg = i;
254 seenany = YES;
255 }
2e3d53b4 256 closedir(dirp);
46ebe0eb
RH
257
258 if (!seenany) {
259 if (blast != 0) /* never lower the upper bound! */
260 lastmsg = blast;
261 firstmsg = lastmsg + 1;
262 }
263 else if (blast > lastmsg)
264 lastmsg = blast;
265
266 if (!send) {
267 bounds = fopen(fname, "w");
268 if (bounds == NULL) {
269 perror(fname);
270 exit(errno);
271 }
272 chmod(fname, CMODE);
273 fprintf(bounds, "%d %d\n", firstmsg, lastmsg);
274 fclose(bounds);
275 }
276 }
277
278 if (send) {
279 /*
280 * Send mode - place msgs in USRMSGS
281 */
282 bounds = fopen(fname, "w");
283 if (bounds == NULL) {
284 perror(fname);
285 exit(errno);
286 }
287
288 nextmsg = lastmsg + 1;
289 sprintf(fname, "%s/%d", USRMSGS, nextmsg);
290 newmsg = fopen(fname, "w");
291 if (newmsg == NULL) {
292 perror(fname);
293 exit(errno);
294 }
295 chmod(fname, 0644);
296
297 fprintf(bounds, "%d %d\n", firstmsg, nextmsg);
298 fclose(bounds);
299
300 sending = YES;
301 if (ruptible)
302 signal(SIGINT, onintr);
303
304 if (isatty(fileno(stdin))) {
305 ptr = getpwuid(uid)->pw_name;
306 printf("Message %d:\nFrom %s %sSubject: ",
307 nextmsg, ptr, ctime(&t));
308 fflush(stdout);
309 fgets(inbuf, sizeof inbuf, stdin);
310 putchar('\n');
311 fflush(stdout);
312 fprintf(newmsg, "From %s %sSubject: %s\n",
313 ptr, ctime(&t), inbuf);
314 blankline = seensubj = YES;
315 }
316 else
317 blankline = seensubj = NO;
318 for (;;) {
319 fgets(inbuf, sizeof inbuf, stdin);
320 if (feof(stdin) || ferror(stdin))
321 break;
322 blankline = (blankline || (inbuf[0] == '\n'));
323 seensubj = (seensubj || (!blankline && (strncmp(inbuf, "Subj", 4) == 0)));
324 fputs(inbuf, newmsg);
325 }
326#ifdef OBJECT
327 if (!seensubj) {
328 printf("NOTICE: Messages should have a Subject field!\n");
329#ifdef REJECT
330 unlink(fname);
331#endif
332 exit(1);
333 }
334#endif
335 exit(ferror(stdin));
336 }
337 if (clean)
338 exit(0);
339
340 /*
341 * prepare to display messages
342 */
343 totty = (isatty(fileno(stdout)) != 0);
344 pause = pause && totty;
345
346 sprintf(fname, "%s/%s", getenv("HOME"), MSGSRC);
347 msgsrc = fopen(fname, "r");
348 if (msgsrc) {
349 newrc = NO;
350 fscanf(msgsrc, "%d\n", &nextmsg);
351 fclose(msgsrc);
352 if (!rcfirst)
353 rcfirst = nextmsg - rcback;
354 }
355 else {
356 newrc = YES;
357 nextmsg = 0;
358 }
359 msgsrc = fopen(fname, "a");
360 if (msgsrc == NULL) {
361 perror(fname);
362 exit(errno);
363 }
ef7d2615
RC
364 if (rcfirst && rcfirst > firstmsg)
365 firstmsg = rcfirst; /* don't set below first msg */
46ebe0eb
RH
366 if (newrc) {
367 nextmsg = firstmsg;
368 fseek(msgsrc, 0L, 0);
369 fprintf(msgsrc, "%d\n", nextmsg);
370 fflush(msgsrc);
371 }
372
373#ifdef V7
374 if (totty) {
375 if (tgetent(inbuf, getenv("TERM")) <= 0
376 || (Lpp = tgetnum("li")) <= 0) {
377 Lpp = NLINES;
378 }
379 }
380#endif
381 Lpp -= 6; /* for headers, etc. */
382
383 already = NO;
384 prevmsg = firstmsg;
385 printing = YES;
386 if (ruptible)
387 signal(SIGINT, onintr);
388
389 /*
390 * Main program loop
391 */
392 for (msg = firstmsg; msg <= lastmsg; msg++) {
393
394 sprintf(fname, "%s/%d", USRMSGS, msg);
395 newmsg = fopen(fname, "r");
396 if (newmsg == NULL)
397 continue;
398
399 gfrsub(newmsg); /* get From and Subject fields */
400 if (locomode && !local) {
401 fclose(newmsg);
402 continue;
403 }
404
405 if (qopt) { /* This has to be located here */
406 printf("There are new messages.\n");
407 exit(0);
408 }
409
410 if (already && !hdrs)
411 putchar('\n');
412 already = YES;
413
414 /*
415 * Print header
416 */
4bdc6489
CL
417again:
418 tstpflag = NO;
419 if (totty)
420 signal(SIGTSTP, onsusp);
46ebe0eb
RH
421 nlines = 2;
422 if (seenfrom) {
423 printf("Message %d:\nFrom %s %s", msg, from, date);
424 nlines++;
425 }
426 if (seensubj) {
427 printf("Subject: %s", subj);
428 nlines++;
429 }
430 else {
431 if (seenfrom) {
432 putchar('\n');
433 nlines++;
434 }
435 while (nlines < 6
436 && fgets(inbuf, sizeof inbuf, newmsg)
437 && inbuf[0] != '\n') {
438 fputs(inbuf, stdout);
439 nlines++;
440 }
441 }
442
443 lct = linecnt(newmsg);
444 if (lct)
445 printf("(%d%slines) ", lct, seensubj? " " : " more ");
446
447 if (hdrs) {
448 printf("\n-----\n");
449 fclose(newmsg);
450 continue;
451 }
452
453 /*
454 * Ask user for command
455 */
456 if (totty)
457 ask(lct? MORE : (msg==lastmsg? NOMORE : NEXT));
458 else
459 inbuf[0] = 'y';
4bdc6489
CL
460 if (totty)
461 signal(SIGTSTP, SIG_DFL);
462 /*
463 * Loop if we've been suspended
464 */
465 if (tstpflag)
466 goto again;
46ebe0eb
RH
467cmnd:
468 in = inbuf;
469 switch (*in) {
470 case 'x':
471 case 'X':
472 exit(0);
473
474 case 'q':
475 case 'Q':
476 quitit = YES;
477 printf("--Postponed--\n");
478 exit(0);
479 /* intentional fall-thru */
480 case 'n':
481 case 'N':
482 if (msg >= nextmsg) sep = "Flushed";
483 break;
484
485 case 'p':
486 case 'P':
487 pause = (*in++ == 'p');
488 /* intentional fallthru */
489 case '\n':
490 case 'y':
491 default:
492 if (*in == '-') {
493 msg = prevmsg-1;
494 sep = "replay";
495 break;
496 }
497 if (isdigit(*in)) {
498 msg = next(in);
499 sep = in;
500 break;
501 }
502
503 prmesg(nlines + lct + (seensubj? 1 : 0));
504 prevmsg = msg;
505
506 }
507
508 printf("--%s--\n", sep);
509 sep = "-";
510 if (msg >= nextmsg) {
511 nextmsg = msg + 1;
512 fseek(msgsrc, 0L, 0);
513 fprintf(msgsrc, "%d\n", nextmsg);
514 fflush(msgsrc);
515 }
516 if (newmsg)
517 fclose(newmsg);
518 if (quitit)
519 break;
520 }
521
186baa0c
CL
522 /*
523 * Make sure .rc file gets updated
524 */
525 if (--msg >= nextmsg) {
526 nextmsg = msg + 1;
527 fseek(msgsrc, 0L, 0);
528 fprintf(msgsrc, "%d\n", nextmsg);
529 fflush(msgsrc);
530 }
46ebe0eb
RH
531 if (already && !quitit && lastcmd && totty) {
532 /*
533 * save or reply to last message?
534 */
535 msg = prevmsg;
536 ask(NOMORE);
537 if (inbuf[0] == '-' || isdigit(inbuf[0]))
538 goto cmnd;
539 }
540 if (!(already || hush || qopt))
541 printf("No new messages.\n");
542 exit(0);
543}
544
545prmesg(length)
546int length;
547{
548 FILE *outf, *inf;
549 int c;
550
551 if (pause && length > Lpp) {
552 sprintf(cmdbuf, PAGE, Lpp);
553 outf = popen(cmdbuf, "w");
554 if (!outf)
555 outf = stdout;
556 else
557 setbuf(outf, NULL);
558 }
559 else
560 outf = stdout;
561
562 if (seensubj)
563 putc('\n', outf);
564
565 while (fgets(inbuf, sizeof inbuf, newmsg))
566 fputs(inbuf, outf);
567
568 if (outf != stdout) {
569 pclose(outf);
570 }
571 else {
572 fflush(stdout);
573 }
574
575 /* trick to force wait on output */
576 stty(fileno(stdout), &otty);
577}
578
579onintr()
580{
581 signal(SIGINT, onintr);
582 if (mailing)
583 unlink(fname);
584 if (sending) {
585 unlink(fname);
586 puts("--Killed--");
587 exit(1);
588 }
589 if (printing) {
590 putchar('\n');
591 if (hdrs)
592 exit(0);
593 sep = "Interrupt";
594 if (newmsg)
595 fseek(newmsg, 0L, 2);
596 intrpflg = YES;
597 }
598}
599
4bdc6489
CL
600/*
601 * We have just gotten a susp. Suspend and prepare to resume.
602 */
603onsusp()
604{
605 tstpflag = YES;
606 signal(SIGTSTP, SIG_DFL);
607 kill(0, SIGTSTP);
608
609 /* the pc stops here */
610
611 signal(SIGTSTP, onsusp);
612}
613
46ebe0eb
RH
614linecnt(f)
615FILE *f;
616{
617 off_t oldpos = ftell(f);
618 int l = 0;
619 char lbuf[BUFSIZ];
620
621 while (fgets(lbuf, sizeof lbuf, f))
622 l++;
623 clearerr(f);
624 fseek(f, oldpos, 0);
625 return (l);
626}
627
628next(buf)
629char *buf;
630{
631 int i;
632 sscanf(buf, "%d", &i);
633 sprintf(buf, "Goto %d", i);
634 return(--i);
635}
636
637ask(prompt)
638char *prompt;
639{
640 char inch;
641 int n, cmsg;
642 off_t oldpos;
643 FILE *cpfrom, *cpto;
644
645 printf("%s ", prompt);
646 fflush(stdout);
647 intrpflg = NO;
648 gets(inbuf);
649 if (intrpflg)
650 inbuf[0] = 'x';
651
652 /*
653 * Handle 'mail' and 'save' here.
654 */
655 if ((inch = inbuf[0]) == 's' || inch == 'm') {
656 if (inbuf[1] == '-')
657 cmsg = prevmsg;
658 else if (isdigit(inbuf[1]))
659 cmsg = atoi(&inbuf[1]);
660 else
661 cmsg = msg;
662 sprintf(fname, "%s/%d", USRMSGS, cmsg);
663
664 oldpos = ftell(newmsg);
665
666 cpfrom = fopen(fname, "r");
667 if (!cpfrom) {
668 printf("Message %d not found\n", cmsg);
669 ask (prompt);
670 return;
671 }
672
673 if (inch == 's') {
674 in = nxtfld(inbuf);
675 if (*in) {
676 for (n=0; in[n] > ' '; n++) { /* sizeof fname? */
677 fname[n] = in[n];
678 }
679 fname[n] = NULL;
680 }
681 else
682 strcpy(fname, "Messages");
683 }
684 else {
685 strcpy(fname, TEMP);
686 mktemp(fname);
687 sprintf(cmdbuf, MAIL, fname);
688 mailing = YES;
689 }
690 cpto = fopen(fname, "a");
691 if (!cpto) {
692 perror(fname);
693 mailing = NO;
694 fseek(newmsg, oldpos, 0);
695 ask(prompt);
696 return;
697 }
698
699 while (n = fread(inbuf, 1, sizeof inbuf, cpfrom))
700 fwrite(inbuf, 1, n, cpto);
701
702 fclose(cpfrom);
703 fclose(cpto);
704 fseek(newmsg, oldpos, 0); /* reposition current message */
705 if (inch == 's')
706 printf("Message %d saved in \"%s\"\n", cmsg, fname);
707 else {
708 system(cmdbuf);
709 unlink(fname);
710 mailing = NO;
711 }
712 ask(prompt);
713 }
714}
715
716gfrsub(infile)
717FILE *infile;
718{
719 off_t frompos;
720
721 seensubj = seenfrom = NO;
722 local = YES;
723 subj[0] = from[0] = date[0] = NULL;
724
725 /*
726 * Is this a normal message?
727 */
728 if (fgets(inbuf, sizeof inbuf, infile)) {
729 if (strncmp(inbuf, "From", 4)==0) {
730 /*
731 * expected form starts with From
732 */
733 seenfrom = YES;
734 frompos = ftell(infile);
735 ptr = from;
736 in = nxtfld(inbuf);
737 if (*in) while (*in && *in > ' ') {
41c894fb 738 if (*in == ':' || *in == '@' || *in == '!')
46ebe0eb
RH
739 local = NO;
740 *ptr++ = *in++;
741 /* what about sizeof from ? */
742 }
743 *ptr = NULL;
744 if (*(in = nxtfld(in)))
745 strncpy(date, in, sizeof date);
746 else {
747 date[0] = '\n';
748 date[1] = NULL;
749 }
750 }
751 else {
752 /*
753 * not the expected form
754 */
755 fseek(infile, 0L, 0);
756 return;
757 }
758 }
759 else
760 /*
761 * empty file ?
762 */
763 return;
764
765 /*
766 * look for Subject line until EOF or a blank line
767 */
768 while (fgets(inbuf, sizeof inbuf, infile)
769 && !(blankline = (inbuf[0] == '\n'))) {
770 /*
771 * extract Subject line
772 */
773 if (!seensubj && strncmp(inbuf, "Subj", 4)==0) {
774 seensubj = YES;
775 frompos = ftell(infile);
776 strncpy(subj, nxtfld(inbuf), sizeof subj);
777 }
778 }
779 if (!blankline)
780 /*
781 * ran into EOF
782 */
783 fseek(infile, frompos, 0);
784
785 if (!seensubj)
786 /*
787 * for possible use with Mail
788 */
789 strncpy(subj, "(No Subject)\n", sizeof subj);
790}
791
792char *
793nxtfld(s)
794char *s;
795{
796 if (*s) while (*s && *s > ' ') s++; /* skip over this field */
797 if (*s) while (*s && *s <= ' ') s++; /* find start of next field */
798 return (s);
799}