list.c revision 1.17 1 /* $NetBSD: list.c,v 1.17 2006/10/31 20:07:32 christos Exp $ */
2
3 /*
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)list.c 8.4 (Berkeley) 5/1/95";
36 #else
37 __RCSID("$NetBSD: list.c,v 1.17 2006/10/31 20:07:32 christos Exp $");
38 #endif
39 #endif /* not lint */
40
41 #include "rcv.h"
42 #include "extern.h"
43 #include "format.h"
44
45 int matchto(char *, int);
46
47 /*
48 * Mail -- a mail program
49 *
50 * Message list handling.
51 */
52
53 /*
54 * Convert the user string of message numbers and
55 * store the numbers into vector.
56 *
57 * Returns the count of messages picked up or -1 on error.
58 */
59 int
60 getmsglist(char *buf, int *vector, int flags)
61 {
62 int *ip;
63 struct message *mp;
64
65 if (msgCount == 0) {
66 *vector = 0;
67 return 0;
68 }
69 if (markall(buf, flags) < 0)
70 return(-1);
71 ip = vector;
72 for (mp = &message[0]; mp < &message[msgCount]; mp++)
73 if (mp->m_flag & MMARK)
74 *ip++ = mp - &message[0] + 1;
75 *ip = 0;
76 return(ip - vector);
77 }
78
79 /*
80 * Mark all messages that the user wanted from the command
81 * line in the message structure. Return 0 on success, -1
82 * on error.
83 */
84
85 /*
86 * Bit values for colon modifiers.
87 */
88
89 #define CMNEW 01 /* New messages */
90 #define CMOLD 02 /* Old messages */
91 #define CMUNREAD 04 /* Unread messages */
92 #define CMDELETED 010 /* Deleted messages */
93 #define CMREAD 020 /* Read messages */
94 #define CMMASK 037 /* Mask the valid bits */
95
96 /*
97 * The following table describes the letters which can follow
98 * the colon and gives the corresponding modifier bit.
99 */
100
101 struct coltab {
102 char co_char; /* What to find past : */
103 int co_bit; /* Associated modifier bit */
104 int co_mask; /* m_status bits to mask */
105 int co_equal; /* ... must equal this */
106 } coltab[] = {
107 { 'n', CMNEW, MNEW, MNEW },
108 { 'o', CMOLD, MNEW, 0 },
109 { 'u', CMUNREAD, MREAD, 0 },
110 { 'd', CMDELETED, MDELETED, MDELETED },
111 { 'r', CMREAD, MREAD, MREAD },
112 { 0, 0, 0, 0 }
113 };
114
115 static int lastcolmod;
116
117
118 static int
119 ignore_message(int m_flag, int colmod)
120 {
121 struct coltab *colp;
122 for (colp = &coltab[0]; colp->co_char; colp++)
123 if (colp->co_bit & colmod)
124 if ((m_flag & colp->co_mask)
125 != colp->co_equal)
126 return 1;
127 return 0;
128 }
129
130 /*
131 * Turn the character after a colon modifier into a bit
132 * value.
133 */
134 static int
135 evalcol(int col)
136 {
137 struct coltab *colp;
138
139 if (col == 0)
140 return(lastcolmod);
141 for (colp = &coltab[0]; colp->co_char; colp++)
142 if (colp->co_char == col)
143 return(colp->co_bit);
144 return(0);
145 }
146
147 int
148 markall(char buf[], int f)
149 {
150 char **np;
151 int i;
152 struct message *mp;
153 char *namelist[NMLSIZE], *bufp;
154 int tok, beg, mc, star, other, valdot, colmod, colresult;
155
156 valdot = dot - &message[0] + 1;
157 colmod = 0;
158 for (i = 1; i <= msgCount; i++)
159 unmark(i);
160 bufp = buf;
161 mc = 0;
162 np = &namelist[0];
163 scaninit();
164 tok = scan(&bufp);
165 star = 0;
166 other = 0;
167 beg = 0;
168 while (tok != TEOL) {
169 switch (tok) {
170 case TNUMBER:
171 number:
172 if (star) {
173 (void)printf("No numbers mixed with *\n");
174 return(-1);
175 }
176 mc++;
177 other++;
178 if (beg != 0) {
179 if (check(lexnumber, f))
180 return(-1);
181 for (i = beg; i <= lexnumber; i++)
182 if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0)
183 mark(i);
184 beg = 0;
185 break;
186 }
187 beg = lexnumber;
188 if (check(beg, f))
189 return(-1);
190 tok = scan(&bufp);
191 regret(tok);
192 if (tok != TDASH) {
193 mark(beg);
194 beg = 0;
195 }
196 break;
197
198 case TPLUS:
199 if (beg != 0) {
200 (void)printf("Non-numeric second argument\n");
201 return(-1);
202 }
203 i = valdot;
204 do {
205 i++;
206 if (i > msgCount) {
207 (void)printf("Referencing beyond EOF\n");
208 return(-1);
209 }
210 } while ((message[i - 1].m_flag & MDELETED) != f);
211 mark(i);
212 break;
213
214 case TDASH:
215 if (beg == 0) {
216 i = valdot;
217 do {
218 i--;
219 if (i <= 0) {
220 (void)printf("Referencing before 1\n");
221 return(-1);
222 }
223 } while ((message[i - 1].m_flag & MDELETED) != f);
224 mark(i);
225 }
226 break;
227
228 case TSTRING:
229 if (beg != 0) {
230 (void)printf("Non-numeric second argument\n");
231 return(-1);
232 }
233 other++;
234 if (lexstring[0] == ':') {
235 colresult = evalcol(lexstring[1]);
236 if (colresult == 0) {
237 (void)printf("Unknown colon modifier \"%s\"\n",
238 lexstring);
239 return(-1);
240 }
241 colmod |= colresult;
242 }
243 else
244 *np++ = savestr(lexstring);
245 break;
246
247 case TDOLLAR:
248 case TUP:
249 case TDOT:
250 lexnumber = metamess(lexstring[0], f);
251 if (lexnumber == -1)
252 return(-1);
253 goto number;
254
255 case TSTAR:
256 if (other) {
257 (void)printf("Can't mix \"*\" with anything\n");
258 return(-1);
259 }
260 star++;
261 break;
262
263 case TERROR:
264 return -1;
265 }
266 tok = scan(&bufp);
267 }
268 lastcolmod = colmod;
269 *np = NULL;
270 mc = 0;
271 if (star) {
272 for (i = 0; i < msgCount; i++)
273 if ((message[i].m_flag & MDELETED) == f) {
274 mark(i + 1);
275 mc++;
276 }
277 if (mc == 0) {
278 (void)printf("No applicable messages.\n");
279 return(-1);
280 }
281 return(0);
282 }
283
284 /*
285 * If no numbers were given, mark all of the messages,
286 * so that we can unmark any whose sender was not selected
287 * if any user names were given.
288 */
289
290 if ((np > namelist || colmod != 0) && mc == 0)
291 for (i = 1; i <= msgCount; i++)
292 if ((message[i - 1].m_flag & MDELETED) == f)
293 mark(i);
294
295 /*
296 * If any names were given, go through and eliminate any
297 * messages whose senders were not requested.
298 */
299
300 if (np > namelist) {
301 for (i = 1; i <= msgCount; i++) {
302 for (mc = 0, np = &namelist[0]; *np != NULL; np++)
303 if (**np == '/') {
304 if (matchsubj(*np, i)) {
305 mc++;
306 break;
307 }
308 }
309 else {
310 if (matchsender(*np, i)) {
311 mc++;
312 break;
313 }
314 }
315 if (mc == 0)
316 unmark(i);
317 }
318
319 /*
320 * Make sure we got some decent messages.
321 */
322
323 mc = 0;
324 for (i = 1; i <= msgCount; i++)
325 if (message[i - 1].m_flag & MMARK) {
326 mc++;
327 break;
328 }
329 if (mc == 0) {
330 (void)printf("No applicable messages from {%s",
331 namelist[0]);
332 for (np = &namelist[1]; *np != NULL; np++)
333 (void)printf(", %s", *np);
334 (void)printf("}\n");
335 return(-1);
336 }
337 }
338
339 /*
340 * If any colon modifiers were given, go through and
341 * unmark any messages which do not satisfy the modifiers.
342 */
343
344 if (colmod != 0) {
345 for (i = 1; i <= msgCount; i++)
346 if (ignore_message(message[i - 1].m_flag, colmod))
347 unmark(i);
348 for (mp = &message[0]; mp < &message[msgCount]; mp++)
349 if (mp->m_flag & MMARK)
350 break;
351 if (mp >= &message[msgCount]) {
352 struct coltab *colp;
353
354 (void)printf("No messages satisfy");
355 for (colp = &coltab[0]; colp->co_char; colp++)
356 if (colp->co_bit & colmod)
357 (void)printf(" :%c", colp->co_char);
358 (void)printf("\n");
359 return(-1);
360 }
361 }
362 return(0);
363 }
364
365 /*
366 * Show all headers without paging. (-H flag)
367 */
368 __attribute__((__noreturn__))
369 int
370 show_headers(int flags)
371 {
372 int mesg;
373 char *cp;
374
375 if ((cp = value(ENAME_HDRONLY_SCRNWIDTH)) != NULL) {
376 int width;
377 width = *cp ? atoi(cp) : 0;
378 if (width >= 0)
379 screenwidth = width;
380 }
381 if ((cp = value(ENAME_HDRONLY_FORMAT)) != NULL)
382 assign(ENAME_HEADER_FORMAT, cp);
383
384 flags &= CMMASK;
385 for (mesg = 1; mesg <= msgCount; mesg++)
386 if (!ignore_message(message[mesg - 1].m_flag, flags))
387 printhead(mesg);
388 exit(0);
389 /* NOTREACHED */
390 }
391
392 /*
393 * A hack so -H can have an optional modifier as -H[:flags].
394 *
395 * This depends a bit on the internals of getopt(). In particular,
396 * for flags expecting an argument, argv[optind-1] must contain the
397 * optarg and optarg must point to a substring of argv[optind-1] not a
398 * copy of it.
399 */
400 int
401 get_Hflag(char **argv)
402 {
403 int flags;
404
405 flags = ~CMMASK;
406
407 if (optarg == NULL) /* We had an error, just get the flags. */
408 return flags;
409
410 if (*optarg != ':' || optarg == argv[optind - 1]) {
411 optind--;
412 optreset = 1;
413 if (optarg != argv[optind]) {
414 static char temparg[LINE_MAX];
415 size_t optlen;
416 size_t arglen;
417 char *p;
418
419 optlen = strlen(optarg);
420 arglen = strlen(argv[optind]);
421 p = argv[optind] + arglen - optlen;
422 optlen = MIN(optlen, sizeof(temparg) - 1);
423 temparg[0] = '-';
424 (void)memmove(temparg + 1, p, optlen + 1);
425 argv[optind] = temparg;
426 }
427 }
428 else {
429 char *p;
430 for (p = optarg + 1; *p; p++) {
431 int bit;
432 if ((bit = evalcol(*p)) == 0)
433 errx(EXIT_FAILURE,
434 "Unknown -H modifier '%c'", *p);
435 flags |= bit;
436 }
437 }
438 return flags;
439 }
440
441 /*
442 * Check the passed message number for legality and proper flags.
443 * If f is MDELETED, then either kind will do. Otherwise, the message
444 * has to be undeleted.
445 */
446 int
447 check(int mesg, int f)
448 {
449 struct message *mp;
450
451 if (mesg < 1 || mesg > msgCount) {
452 (void)printf("%d: Invalid message number\n", mesg);
453 return(-1);
454 }
455 mp = &message[mesg - 1];
456 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
457 (void)printf("%d: Inappropriate message\n", mesg);
458 return(-1);
459 }
460 return(0);
461 }
462
463 /*
464 * Scan out the list of string arguments, shell style
465 * for a RAWLIST.
466 */
467 int
468 getrawlist(const char line[], char **argv, int argc)
469 {
470 char c, *cp2, quotec;
471 const char *cp;
472 int argn;
473 char linebuf[BUFSIZ];
474
475 argn = 0;
476 cp = line;
477 for (;;) {
478 for (; *cp == ' ' || *cp == '\t'; cp++)
479 ;
480 if (*cp == '\0')
481 break;
482 if (argn >= argc - 1) {
483 (void)printf(
484 "Too many elements in the list; excess discarded.\n");
485 break;
486 }
487 cp2 = linebuf;
488 quotec = '\0';
489 while ((c = *cp) != '\0') {
490 cp++;
491 if (quotec != '\0') {
492 if (c == quotec)
493 quotec = '\0';
494 else if (c == '\\')
495 switch (c = *cp++) {
496 case '\0':
497 *cp2++ = '\\';
498 cp--;
499 break;
500 case '0': case '1': case '2': case '3':
501 case '4': case '5': case '6': case '7':
502 c -= '0';
503 if (*cp >= '0' && *cp <= '7')
504 c = c * 8 + *cp++ - '0';
505 if (*cp >= '0' && *cp <= '7')
506 c = c * 8 + *cp++ - '0';
507 *cp2++ = c;
508 break;
509 case 'b':
510 *cp2++ = '\b';
511 break;
512 case 'f':
513 *cp2++ = '\f';
514 break;
515 case 'n':
516 *cp2++ = '\n';
517 break;
518 case 'r':
519 *cp2++ = '\r';
520 break;
521 case 't':
522 *cp2++ = '\t';
523 break;
524 case 'v':
525 *cp2++ = '\v';
526 break;
527 default:
528 *cp2++ = c;
529 }
530 else if (c == '^') {
531 c = *cp++;
532 if (c == '?')
533 *cp2++ = '\177';
534 /* null doesn't show up anyway */
535 else if ((c >= 'A' && c <= '_') ||
536 (c >= 'a' && c <= 'z'))
537 *cp2++ = c & 037;
538 else {
539 *cp2++ = '^';
540 cp--;
541 }
542 } else
543 *cp2++ = c;
544 } else if (c == '"' || c == '\'')
545 quotec = c;
546 else if (c == ' ' || c == '\t')
547 break;
548 else
549 *cp2++ = c;
550 }
551 *cp2 = '\0';
552 argv[argn++] = savestr(linebuf);
553 }
554 argv[argn] = NULL;
555 return argn;
556 }
557
558 /*
559 * scan out a single lexical item and return its token number,
560 * updating the string pointer passed **p. Also, store the value
561 * of the number or string scanned in lexnumber or lexstring as
562 * appropriate. In any event, store the scanned `thing' in lexstring.
563 */
564
565 struct lex {
566 char l_char;
567 char l_token;
568 } singles[] = {
569 { '$', TDOLLAR },
570 { '.', TDOT },
571 { '^', TUP },
572 { '*', TSTAR },
573 { '-', TDASH },
574 { '+', TPLUS },
575 { '(', TOPEN },
576 { ')', TCLOSE },
577 { 0, 0 }
578 };
579
580 int
581 scan(char **sp)
582 {
583 char *cp, *cp2;
584 int c;
585 struct lex *lp;
586 int quotec;
587
588 if (regretp >= 0) {
589 (void)strcpy(lexstring, string_stack[regretp]);
590 lexnumber = numberstack[regretp];
591 return(regretstack[regretp--]);
592 }
593 cp = *sp;
594 cp2 = lexstring;
595 c = *cp++;
596
597 /*
598 * strip away leading white space.
599 */
600
601 while (c == ' ' || c == '\t')
602 c = *cp++;
603
604 /*
605 * If no characters remain, we are at end of line,
606 * so report that.
607 */
608
609 if (c == '\0') {
610 *sp = --cp;
611 return(TEOL);
612 }
613
614 /*
615 * If the leading character is a digit, scan
616 * the number and convert it on the fly.
617 * Return TNUMBER when done.
618 */
619
620 if (isdigit(c)) {
621 lexnumber = 0;
622 while (isdigit(c)) {
623 lexnumber = lexnumber*10 + c - '0';
624 *cp2++ = c;
625 c = *cp++;
626 }
627 *cp2 = '\0';
628 *sp = --cp;
629 return(TNUMBER);
630 }
631
632 /*
633 * Check for single character tokens; return such
634 * if found.
635 */
636
637 for (lp = &singles[0]; lp->l_char != 0; lp++)
638 if (c == lp->l_char) {
639 lexstring[0] = c;
640 lexstring[1] = '\0';
641 *sp = cp;
642 return(lp->l_token);
643 }
644
645 /*
646 * We've got a string! Copy all the characters
647 * of the string into lexstring, until we see
648 * a null, space, or tab.
649 * If the lead character is a " or ', save it
650 * and scan until you get another.
651 */
652
653 quotec = 0;
654 if (c == '\'' || c == '"') {
655 quotec = c;
656 c = *cp++;
657 }
658 while (c != '\0') {
659 if (c == quotec) {
660 cp++;
661 break;
662 }
663 if (quotec == 0 && (c == ' ' || c == '\t'))
664 break;
665 if (cp2 - lexstring < STRINGLEN - 1)
666 *cp2++ = c;
667 c = *cp++;
668 }
669 if (quotec && c == 0) {
670 (void)fprintf(stderr, "Missing %c\n", quotec);
671 return TERROR;
672 }
673 *sp = --cp;
674 *cp2 = '\0';
675 return(TSTRING);
676 }
677
678 /*
679 * Unscan the named token by pushing it onto the regret stack.
680 */
681 void
682 regret(int token)
683 {
684 if (++regretp >= REGDEP)
685 errx(1, "Too many regrets");
686 regretstack[regretp] = token;
687 lexstring[STRINGLEN - 1] = '\0';
688 string_stack[regretp] = savestr(lexstring);
689 numberstack[regretp] = lexnumber;
690 }
691
692 /*
693 * Reset all the scanner global variables.
694 */
695 void
696 scaninit(void)
697 {
698 regretp = -1;
699 }
700
701 /*
702 * Find the first message whose flags & m == f and return
703 * its message number.
704 */
705 int
706 first(int f, int m)
707 {
708 struct message *mp;
709
710 if (msgCount == 0)
711 return 0;
712 f &= MDELETED;
713 m &= MDELETED;
714 for (mp = dot; mp < &message[msgCount]; mp++)
715 if ((mp->m_flag & m) == f)
716 return mp - message + 1;
717 for (mp = dot - 1; mp >= &message[0]; mp--)
718 if ((mp->m_flag & m) == f)
719 return mp - message + 1;
720 return 0;
721 }
722
723 /*
724 * See if the passed name sent the passed message number. Return true
725 * if so.
726 */
727 int
728 matchsender(char *str, int mesg)
729 {
730 char *cp, *cp2, *backup;
731
732 if (!*str) /* null string matches nothing instead of everything */
733 return 0;
734 backup = cp2 = nameof(&message[mesg - 1], 0);
735 cp = str;
736 while (*cp2) {
737 if (*cp == 0)
738 return(1);
739 if (upcase(*cp++) != upcase(*cp2++)) {
740 cp2 = ++backup;
741 cp = str;
742 }
743 }
744 return(*cp == 0);
745 }
746
747 /*
748 * See if the passed name received the passed message number. Return true
749 * if so.
750 */
751
752 static const char *to_fields[] = { "to", "cc", "bcc", 0 };
753
754 int
755 matchto(char *str, int mesg)
756 {
757 struct message *mp;
758 char *cp, *cp2, *backup;
759 const char **to;
760
761 str++;
762
763 if (*str == 0) /* null string matches nothing instead of everything */
764 return(0);
765
766 mp = &message[mesg - 1];
767
768 for (to = to_fields; *to; to++) {
769 cp = str;
770 cp2 = hfield(*to, mp);
771 if (cp2 != NULL) {
772 backup = cp2;
773 while (*cp2) {
774 if (*cp == 0)
775 return(1);
776 if (upcase(*cp++) != upcase(*cp2++)) {
777 cp2 = ++backup;
778 cp = str;
779 }
780 }
781 if (*cp == 0)
782 return(1);
783 }
784 }
785 return(0);
786 }
787
788 /*
789 * See if the given string matches inside the subject field of the
790 * given message. For the purpose of the scan, we ignore case differences.
791 * If it does, return true. The string search argument is assumed to
792 * have the form "/search-string." If it is of the form "/," we use the
793 * previous search string.
794 */
795
796 char lastscan[STRINGLEN];
797 int
798 matchsubj(char *str, int mesg)
799 {
800 struct message *mp;
801 char *cp, *cp2, *backup;
802
803 str++;
804 if (*str == '\0')
805 str = lastscan;
806 else {
807 (void)strncpy(lastscan, str, STRINGLEN - 1);
808 lastscan[STRINGLEN - 1] = '\0';
809 }
810 mp = &message[mesg - 1];
811
812 /*
813 * Now look, ignoring case, for the word in the string.
814 */
815
816 if (value("searchheaders") && (cp = strchr(str, ':'))) {
817 /* Check for special case "/To:" */
818 if (upcase(str[0]) == 'T' && upcase(str[1]) == 'O' &&
819 str[2] == ':')
820 return(matchto(cp, mesg));
821 *cp++ = '\0';
822 cp2 = hfield(*str ? str : "subject", mp);
823 cp[-1] = ':';
824 str = cp;
825 } else {
826 cp = str;
827 cp2 = hfield("subject", mp);
828 }
829 if (cp2 == NULL)
830 return(0);
831 backup = cp2;
832 while (*cp2) {
833 if (*cp == 0)
834 return(1);
835 if (upcase(*cp++) != upcase(*cp2++)) {
836 cp2 = ++backup;
837 cp = str;
838 }
839 }
840 return(*cp == 0);
841 }
842
843 /*
844 * Mark the named message by setting its mark bit.
845 */
846 void
847 mark(int mesg)
848 {
849 int i;
850
851 i = mesg;
852 if (i < 1 || i > msgCount)
853 errx(1, "Bad message number to mark");
854 message[i - 1].m_flag |= MMARK;
855 }
856
857 /*
858 * Unmark the named message.
859 */
860 void
861 unmark(int mesg)
862 {
863 int i;
864
865 i = mesg;
866 if (i < 1 || i > msgCount)
867 errx(1, "Bad message number to unmark");
868 message[i - 1].m_flag &= ~MMARK;
869 }
870
871 /*
872 * Return the message number corresponding to the passed meta character.
873 */
874 int
875 metamess(int meta, int f)
876 {
877 int c, m;
878 struct message *mp;
879
880 c = meta;
881 switch (c) {
882 case '^':
883 /*
884 * First 'good' message left.
885 */
886 for (mp = &message[0]; mp < &message[msgCount]; mp++)
887 if ((mp->m_flag & MDELETED) == f)
888 return(mp - &message[0] + 1);
889 (void)printf("No applicable messages\n");
890 return(-1);
891
892 case '$':
893 /*
894 * Last 'good message left.
895 */
896 for (mp = &message[msgCount - 1]; mp >= &message[0]; mp--)
897 if ((mp->m_flag & MDELETED) == f)
898 return(mp - &message[0] + 1);
899 (void)printf("No applicable messages\n");
900 return(-1);
901
902 case '.':
903 /*
904 * Current message.
905 */
906 m = dot - &message[0] + 1;
907 if ((dot->m_flag & MDELETED) != f) {
908 (void)printf("%d: Inappropriate message\n", m);
909 return(-1);
910 }
911 return(m);
912
913 default:
914 (void)printf("Unknown metachar (%c)\n", c);
915 return(-1);
916 }
917 }
918