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