xref: /csrg-svn/usr.bin/mail/lex.c (revision 68818)
122456Sdist /*
262083Sbostic  * Copyright (c) 1980, 1993
362083Sbostic  *	The Regents of the University of California.  All rights reserved.
433499Sbostic  *
542741Sbostic  * %sccs.include.redist.c%
622456Sdist  */
722456Sdist 
834905Sbostic #ifndef lint
9*68818Sdab static char sccsid[] = "@(#)lex.c	8.2 (Berkeley) 04/20/95";
1034905Sbostic #endif /* not lint */
111237Skas 
121237Skas #include "rcv.h"
1324750Sserge #include <errno.h>
1454505Sbostic #include <fcntl.h>
1554505Sbostic #include "extern.h"
161237Skas 
171237Skas /*
181237Skas  * Mail -- a mail program
191237Skas  *
201237Skas  * Lexical processing of commands.
211237Skas  */
221237Skas 
234566Skurt char	*prompt = "& ";
244566Skurt 
251237Skas /*
261311Skas  * Set up editing on the given file name.
2736734Sedward  * If the first character of name is %, we are considered to be
2836734Sedward  * editing the file, otherwise we are reading our mail which has
2936734Sedward  * signficance for mbox and so forth.
301311Skas  */
3154505Sbostic int
setfile(name)3236734Sedward setfile(name)
331311Skas 	char *name;
341311Skas {
351311Skas 	FILE *ibuf;
361311Skas 	int i;
3716837Sralph 	struct stat stb;
3836734Sedward 	char isedit = *name != '%';
3936734Sedward 	char *who = name[1] ? name + 1 : myname;
401311Skas 	static int shudclob;
411311Skas 	extern char tempMesg[];
4224750Sserge 	extern int errno;
431311Skas 
4436734Sedward 	if ((name = expand(name)) == NOSTR)
4536734Sedward 		return -1;
4636734Sedward 
4743865Sedward 	if ((ibuf = Fopen(name, "r")) == NULL) {
4836734Sedward 		if (!isedit && errno == ENOENT)
4936734Sedward 			goto nomail;
5036554Sedward 		perror(name);
511311Skas 		return(-1);
5236554Sedward 	}
5324750Sserge 
5424750Sserge 	if (fstat(fileno(ibuf), &stb) < 0) {
5536554Sedward 		perror("fstat");
5643865Sedward 		Fclose(ibuf);
5724750Sserge 		return (-1);
5816837Sralph 	}
591311Skas 
6024750Sserge 	switch (stb.st_mode & S_IFMT) {
6124750Sserge 	case S_IFDIR:
6243865Sedward 		Fclose(ibuf);
6324750Sserge 		errno = EISDIR;
6436554Sedward 		perror(name);
6524750Sserge 		return (-1);
6624750Sserge 
6724750Sserge 	case S_IFREG:
6824750Sserge 		break;
6924750Sserge 
7024750Sserge 	default:
7143865Sedward 		Fclose(ibuf);
7224750Sserge 		errno = EINVAL;
7336554Sedward 		perror(name);
7424750Sserge 		return (-1);
7524750Sserge 	}
7624750Sserge 
771311Skas 	/*
781311Skas 	 * Looks like all will be well.  We must now relinquish our
793566Skurt 	 * hold on the current set of stuff.  Must hold signals
801311Skas 	 * while we are reading the new file, else we will ruin
811311Skas 	 * the message[] data structure.
821311Skas 	 */
831311Skas 
843566Skurt 	holdsigs();
8536554Sedward 	if (shudclob)
8636554Sedward 		quit();
871311Skas 
881311Skas 	/*
891311Skas 	 * Copy the messages into /tmp
901311Skas 	 * and set pointers.
911311Skas 	 */
921311Skas 
931311Skas 	readonly = 0;
941311Skas 	if ((i = open(name, 1)) < 0)
951311Skas 		readonly++;
961311Skas 	else
971311Skas 		close(i);
981311Skas 	if (shudclob) {
991311Skas 		fclose(itf);
1001311Skas 		fclose(otf);
1011311Skas 	}
1021311Skas 	shudclob = 1;
1031311Skas 	edit = isedit;
10436734Sedward 	strcpy(prevfile, mailname);
1051523Skas 	if (name != mailname)
1061523Skas 		strcpy(mailname, name);
1071311Skas 	mailsize = fsize(ibuf);
1081311Skas 	if ((otf = fopen(tempMesg, "w")) == NULL) {
1091311Skas 		perror(tempMesg);
1101311Skas 		exit(1);
1111311Skas 	}
11256291Sedward 	(void) fcntl(fileno(otf), F_SETFD, 1);
1131311Skas 	if ((itf = fopen(tempMesg, "r")) == NULL) {
1141311Skas 		perror(tempMesg);
1151311Skas 		exit(1);
1161311Skas 	}
11756291Sedward 	(void) fcntl(fileno(itf), F_SETFD, 1);
11846242Sbostic 	rm(tempMesg);
119*68818Sdab 	setptr(ibuf, 0);
1201311Skas 	setmsize(msgCount);
121*68818Sdab 	/*
122*68818Sdab 	 * New mail mail have arrived while we were reading
123*68818Sdab 	 * up the mail file, so reset mailsize to be where
124*68818Sdab 	 * we really are in the file...
125*68818Sdab 	 */
126*68818Sdab 	mailsize = ftell(ibuf);
12743865Sedward 	Fclose(ibuf);
1283566Skurt 	relsesigs();
1291375Skas 	sawcom = 0;
13036734Sedward 	if (!edit && msgCount == 0) {
13136734Sedward nomail:
13236734Sedward 		fprintf(stderr, "No mail for %s\n", who);
13336734Sedward 		return -1;
13436734Sedward 	}
1351311Skas 	return(0);
1361311Skas }
1371311Skas 
138*68818Sdab /*
139*68818Sdab  * Incorporate any new mail that has arrived since we first
140*68818Sdab  * started reading mail.
141*68818Sdab  */
142*68818Sdab int
incfile()143*68818Sdab incfile()
144*68818Sdab {
145*68818Sdab 	int newsize;
146*68818Sdab 	int omsgCount = msgCount;
147*68818Sdab 	FILE *ibuf;
148*68818Sdab 
149*68818Sdab 	ibuf = Fopen(mailname, "r");
150*68818Sdab 	if (ibuf == NULL)
151*68818Sdab 		return -1;
152*68818Sdab 	holdsigs();
153*68818Sdab 	newsize = fsize(ibuf);
154*68818Sdab 	if (newsize == 0)
155*68818Sdab 		return -1;		/* mail box is now empty??? */
156*68818Sdab 	if (newsize < mailsize)
157*68818Sdab 		return -1;		/* mail box has shrunk??? */
158*68818Sdab 	if (newsize == mailsize)
159*68818Sdab 		return 0;		/* no new mail */
160*68818Sdab 	setptr(ibuf, mailsize);
161*68818Sdab 	setmsize(msgCount);
162*68818Sdab 	mailsize = ftell(ibuf);
163*68818Sdab 	Fclose(ibuf);
164*68818Sdab 	relsesigs();
165*68818Sdab 	return(msgCount - omsgCount);
166*68818Sdab }
167*68818Sdab 
16836554Sedward int	*msgvec;
16936554Sedward int	reset_on_stop;			/* do a reset() if stopped */
17036554Sedward 
1711311Skas /*
1721237Skas  * Interpret user commands one by one.  If standard input is not a tty,
1731237Skas  * print no prompt.
1741237Skas  */
17554505Sbostic void
commands()1761237Skas commands()
1771237Skas {
17836554Sedward 	int eofloop = 0;
1791237Skas 	register int n;
1801237Skas 	char linebuf[LINESIZE];
18147708Sbostic 	void intr(), stop(), hangup();
1821237Skas 
18336554Sedward 	if (!sourcing) {
18431142Sedward 		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
18536554Sedward 			signal(SIGINT, intr);
18631142Sedward 		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
18731142Sedward 			signal(SIGHUP, hangup);
18836554Sedward 		signal(SIGTSTP, stop);
18936554Sedward 		signal(SIGTTOU, stop);
19036554Sedward 		signal(SIGTTIN, stop);
1913413Skurt 	}
19236554Sedward 	setexit();
1931237Skas 	for (;;) {
1941237Skas 		/*
1951237Skas 		 * Print the prompt, if needed.  Clear out
1961237Skas 		 * string space, and flush the output.
1971237Skas 		 */
19834750Sedward 		if (!sourcing && value("interactive") != NOSTR) {
199*68818Sdab 			if ((value("autoinc") != NOSTR) && (incfile() > 0))
200*68818Sdab 				printf("New mail has arrived.\n");
20136554Sedward 			reset_on_stop = 1;
20213132Ssam 			printf(prompt);
20331142Sedward 		}
20431142Sedward 		fflush(stdout);
20534968Sedward 		sreset();
2061237Skas 		/*
2071237Skas 		 * Read a line of commands from the current input
2081237Skas 		 * and handle end of file specially.
2091237Skas 		 */
2101237Skas 		n = 0;
2111237Skas 		for (;;) {
21236554Sedward 			if (readline(input, &linebuf[n], LINESIZE - n) < 0) {
21336554Sedward 				if (n == 0)
21436554Sedward 					n = -1;
21536554Sedward 				break;
2161237Skas 			}
2171237Skas 			if ((n = strlen(linebuf)) == 0)
2181237Skas 				break;
2191237Skas 			n--;
2201237Skas 			if (linebuf[n] != '\\')
2211237Skas 				break;
2221237Skas 			linebuf[n++] = ' ';
2231237Skas 		}
22436554Sedward 		reset_on_stop = 0;
22536554Sedward 		if (n < 0) {
22636554Sedward 				/* eof */
22736554Sedward 			if (loading)
22836554Sedward 				break;
22936554Sedward 			if (sourcing) {
23036554Sedward 				unstack();
23136554Sedward 				continue;
23236554Sedward 			}
23336554Sedward 			if (value("interactive") != NOSTR &&
23436554Sedward 			    value("ignoreeof") != NOSTR &&
23536554Sedward 			    ++eofloop < 25) {
23636554Sedward 				printf("Use \"quit\" to quit.\n");
23736554Sedward 				continue;
23836554Sedward 			}
23936554Sedward 			break;
24036554Sedward 		}
24136554Sedward 		eofloop = 0;
2422118Skas 		if (execute(linebuf, 0))
24336554Sedward 			break;
2441237Skas 	}
2451237Skas }
2461237Skas 
2471237Skas /*
24836554Sedward  * Execute a single command.
24936554Sedward  * Command functions return 0 for success, 1 for error, and -1
25036554Sedward  * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
25136554Sedward  * the interactive command loop.
2522118Skas  * Contxt is non-zero if called while composing mail.
2531237Skas  */
25454505Sbostic int
execute(linebuf,contxt)2552118Skas execute(linebuf, contxt)
2561237Skas 	char linebuf[];
25754505Sbostic 	int contxt;
2581237Skas {
2591237Skas 	char word[LINESIZE];
2601237Skas 	char *arglist[MAXARGC];
2611237Skas 	struct cmd *com;
2621237Skas 	register char *cp, *cp2;
2631237Skas 	register int c;
2641311Skas 	int muvec[2];
26536554Sedward 	int e = 1;
2661237Skas 
2671237Skas 	/*
2681237Skas 	 * Strip the white space away from the beginning
2691237Skas 	 * of the command, then scan out a word, which
2701237Skas 	 * consists of anything except digits and white space.
2711237Skas 	 *
2721237Skas 	 * Handle ! escapes differently to get the correct
2731237Skas 	 * lexical conventions.
2741237Skas 	 */
2751237Skas 
27631142Sedward 	for (cp = linebuf; isspace(*cp); cp++)
27731142Sedward 		;
2781237Skas 	if (*cp == '!') {
2791237Skas 		if (sourcing) {
2801237Skas 			printf("Can't \"!\" while sourcing\n");
28136554Sedward 			goto out;
2821237Skas 		}
2831237Skas 		shell(cp+1);
2841237Skas 		return(0);
2851237Skas 	}
2861237Skas 	cp2 = word;
28734987Sedward 	while (*cp && index(" \t0123456789$^.:/-+*'\"", *cp) == NOSTR)
2881237Skas 		*cp2++ = *cp++;
2891237Skas 	*cp2 = '\0';
2901237Skas 
2911237Skas 	/*
2921237Skas 	 * Look up the command; if not found, bitch.
2931237Skas 	 * Normally, a blank command would map to the
2941237Skas 	 * first command in the table; while sourcing,
2951237Skas 	 * however, we ignore blank lines to eliminate
2961237Skas 	 * confusion.
2971237Skas 	 */
2981237Skas 
29931142Sedward 	if (sourcing && *word == '\0')
3001237Skas 		return(0);
3011237Skas 	com = lex(word);
3021237Skas 	if (com == NONE) {
3036226Skurt 		printf("Unknown command: \"%s\"\n", word);
30436554Sedward 		goto out;
3051237Skas 	}
3061237Skas 
3071237Skas 	/*
3081523Skas 	 * See if we should execute the command -- if a conditional
3091523Skas 	 * we always execute it, otherwise, check the state of cond.
3101523Skas 	 */
3111523Skas 
3122061Skas 	if ((com->c_argtype & F) == 0)
3131523Skas 		if (cond == CRCV && !rcvmode || cond == CSEND && rcvmode)
3141523Skas 			return(0);
3151523Skas 
3161523Skas 	/*
3171237Skas 	 * Process the arguments to the command, depending
3181237Skas 	 * on the type he expects.  Default to an error.
3191237Skas 	 * If we are sourcing an interactive command, it's
3201237Skas 	 * an error.
3211237Skas 	 */
3221237Skas 
3231237Skas 	if (!rcvmode && (com->c_argtype & M) == 0) {
3241237Skas 		printf("May not execute \"%s\" while sending\n",
3251237Skas 		    com->c_name);
32636554Sedward 		goto out;
3271237Skas 	}
3281237Skas 	if (sourcing && com->c_argtype & I) {
3291237Skas 		printf("May not execute \"%s\" while sourcing\n",
3301237Skas 		    com->c_name);
33136554Sedward 		goto out;
3321237Skas 	}
3331311Skas 	if (readonly && com->c_argtype & W) {
3341311Skas 		printf("May not execute \"%s\" -- message file is read only\n",
3351311Skas 		   com->c_name);
33636554Sedward 		goto out;
3371311Skas 	}
3382118Skas 	if (contxt && com->c_argtype & R) {
3392118Skas 		printf("Cannot recursively invoke \"%s\"\n", com->c_name);
34036554Sedward 		goto out;
3412118Skas 	}
3422118Skas 	switch (com->c_argtype & ~(F|P|I|M|T|W|R)) {
3431237Skas 	case MSGLIST:
3441237Skas 		/*
3451237Skas 		 * A message list defaulting to nearest forward
3461237Skas 		 * legal message.
3471237Skas 		 */
3486226Skurt 		if (msgvec == 0) {
3496226Skurt 			printf("Illegal use of \"message list\"\n");
35036554Sedward 			break;
3516226Skurt 		}
3521237Skas 		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
3531237Skas 			break;
3541237Skas 		if (c  == 0) {
3551237Skas 			*msgvec = first(com->c_msgflag,
3561237Skas 				com->c_msgmask);
3571237Skas 			msgvec[1] = NULL;
3581237Skas 		}
3591237Skas 		if (*msgvec == NULL) {
3601237Skas 			printf("No applicable messages\n");
3611237Skas 			break;
3621237Skas 		}
3631237Skas 		e = (*com->c_func)(msgvec);
3641237Skas 		break;
3651237Skas 
3661237Skas 	case NDMLIST:
3671237Skas 		/*
3681237Skas 		 * A message list with no defaults, but no error
3691237Skas 		 * if none exist.
3701237Skas 		 */
3716226Skurt 		if (msgvec == 0) {
3726226Skurt 			printf("Illegal use of \"message list\"\n");
37336554Sedward 			break;
3746226Skurt 		}
3751237Skas 		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
3761237Skas 			break;
3771237Skas 		e = (*com->c_func)(msgvec);
3781237Skas 		break;
3791237Skas 
3801237Skas 	case STRLIST:
3811237Skas 		/*
3821237Skas 		 * Just the straight string, with
3831237Skas 		 * leading blanks removed.
3841237Skas 		 */
38531142Sedward 		while (isspace(*cp))
3861237Skas 			cp++;
3871237Skas 		e = (*com->c_func)(cp);
3881237Skas 		break;
3891237Skas 
3901237Skas 	case RAWLIST:
3911237Skas 		/*
3921237Skas 		 * A vector of strings, in shell style.
3931237Skas 		 */
39425376Sserge 		if ((c = getrawlist(cp, arglist,
39525376Sserge 				sizeof arglist / sizeof *arglist)) < 0)
3961237Skas 			break;
3971237Skas 		if (c < com->c_minargs) {
3981237Skas 			printf("%s requires at least %d arg(s)\n",
3991237Skas 				com->c_name, com->c_minargs);
4001237Skas 			break;
4011237Skas 		}
4021237Skas 		if (c > com->c_maxargs) {
4031237Skas 			printf("%s takes no more than %d arg(s)\n",
4041237Skas 				com->c_name, com->c_maxargs);
4051237Skas 			break;
4061237Skas 		}
4071237Skas 		e = (*com->c_func)(arglist);
4081237Skas 		break;
4091237Skas 
4101237Skas 	case NOLIST:
4111237Skas 		/*
4121237Skas 		 * Just the constant zero, for exiting,
4131237Skas 		 * eg.
4141237Skas 		 */
4151237Skas 		e = (*com->c_func)(0);
4161237Skas 		break;
4171237Skas 
4181237Skas 	default:
4191237Skas 		panic("Unknown argtype");
4201237Skas 	}
4211237Skas 
42236554Sedward out:
4231237Skas 	/*
4241237Skas 	 * Exit the current source file on
4251237Skas 	 * error.
4261237Skas 	 */
42736554Sedward 	if (e) {
42836554Sedward 		if (e < 0)
42936554Sedward 			return 1;
43036554Sedward 		if (loading)
43136554Sedward 			return 1;
43236554Sedward 		if (sourcing)
43336554Sedward 			unstack();
43436554Sedward 		return 0;
43536554Sedward 	}
4361237Skas 	if (value("autoprint") != NOSTR && com->c_argtype & P)
4371311Skas 		if ((dot->m_flag & MDELETED) == 0) {
4381311Skas 			muvec[0] = dot - &message[0] + 1;
4391311Skas 			muvec[1] = 0;
4401311Skas 			type(muvec);
4411311Skas 		}
4421523Skas 	if (!sourcing && (com->c_argtype & T) == 0)
4431237Skas 		sawcom = 1;
4441237Skas 	return(0);
4451237Skas }
4461237Skas 
4471237Skas /*
4481311Skas  * Set the size of the message vector used to construct argument
4491311Skas  * lists to message list functions.
4501311Skas  */
45154505Sbostic void
setmsize(sz)4521311Skas setmsize(sz)
45354505Sbostic 	int sz;
4541311Skas {
4551311Skas 
45631142Sedward 	if (msgvec != 0)
45757659Sbostic 		free((char *) msgvec);
4581311Skas 	msgvec = (int *) calloc((unsigned) (sz + 1), sizeof *msgvec);
4591311Skas }
4601311Skas 
4611311Skas /*
4621237Skas  * Find the correct command in the command table corresponding
4631237Skas  * to the passed command "word"
4641237Skas  */
4651237Skas 
4661237Skas struct cmd *
lex(word)4671237Skas lex(word)
4681237Skas 	char word[];
4691237Skas {
4701237Skas 	register struct cmd *cp;
4711237Skas 	extern struct cmd cmdtab[];
4721237Skas 
4731237Skas 	for (cp = &cmdtab[0]; cp->c_name != NOSTR; cp++)
4741237Skas 		if (isprefix(word, cp->c_name))
4751237Skas 			return(cp);
4761237Skas 	return(NONE);
4771237Skas }
4781237Skas 
4791237Skas /*
4801237Skas  * Determine if as1 is a valid prefix of as2.
4811237Skas  * Return true if yep.
4821237Skas  */
48354505Sbostic int
isprefix(as1,as2)4841237Skas isprefix(as1, as2)
4851237Skas 	char *as1, *as2;
4861237Skas {
4871237Skas 	register char *s1, *s2;
4881237Skas 
4891237Skas 	s1 = as1;
4901237Skas 	s2 = as2;
4911237Skas 	while (*s1++ == *s2)
4921237Skas 		if (*s2++ == '\0')
4931237Skas 			return(1);
4941237Skas 	return(*--s1 == '\0');
4951237Skas }
4961237Skas 
4971237Skas /*
49831142Sedward  * The following gets called on receipt of an interrupt.  This is
4991237Skas  * to abort printout of a command, mainly.
5001237Skas  * Dispatching here when command() is inactive crashes rcv.
5011237Skas  * Close all open files except 0, 1, 2, and the temporary.
5021237Skas  * Also, unstack all source files.
5031237Skas  */
5041237Skas 
5053240Skas int	inithdr;			/* am printing startup headers */
5063240Skas 
50731142Sedward /*ARGSUSED*/
50847708Sbostic void
intr(s)50936554Sedward intr(s)
51054505Sbostic 	int s;
5111237Skas {
5121237Skas 
5131237Skas 	noreset = 0;
5143240Skas 	if (!inithdr)
5153240Skas 		sawcom++;
5163240Skas 	inithdr = 0;
5171237Skas 	while (sourcing)
5181237Skas 		unstack();
51918659Sserge 
52043865Sedward 	close_all_files();
52118659Sserge 
5221237Skas 	if (image >= 0) {
5231237Skas 		close(image);
5241237Skas 		image = -1;
5251237Skas 	}
52616857Sralph 	fprintf(stderr, "Interrupt\n");
5271237Skas 	reset(0);
5281237Skas }
5291237Skas 
5301237Skas /*
53136554Sedward  * When we wake up after ^Z, reprint the prompt.
53236554Sedward  */
53347708Sbostic void
stop(s)53436554Sedward stop(s)
53554505Sbostic 	int s;
53636554Sedward {
53739612Sedward 	sig_t old_action = signal(s, SIG_DFL);
53836554Sedward 
53936554Sedward 	sigsetmask(sigblock(0) & ~sigmask(s));
54036554Sedward 	kill(0, s);
54136554Sedward 	sigblock(sigmask(s));
54236554Sedward 	signal(s, old_action);
54336554Sedward 	if (reset_on_stop) {
54436554Sedward 		reset_on_stop = 0;
54536554Sedward 		reset(0);
54636554Sedward 	}
54736554Sedward }
54836554Sedward 
54936554Sedward /*
55036554Sedward  * Branch here on hangup signal and simulate "exit".
55136554Sedward  */
55236554Sedward /*ARGSUSED*/
55347708Sbostic void
hangup(s)55436554Sedward hangup(s)
55554505Sbostic 	int s;
55636554Sedward {
55736554Sedward 
55836554Sedward 	/* nothing to do? */
55936554Sedward 	exit(1);
56036554Sedward }
56136554Sedward 
56236554Sedward /*
5631237Skas  * Announce the presence of the current Mail version,
5641237Skas  * give the message count, and print a header listing.
5651237Skas  */
56654505Sbostic void
announce()56734754Sedward announce()
5681237Skas {
5691485Skas 	int vec[2], mdot;
5702130Skas 
571*68818Sdab 	mdot = newfileinfo(0);
5722130Skas 	vec[0] = mdot;
5732130Skas 	vec[1] = 0;
5742130Skas 	dot = &message[mdot - 1];
57534704Sedward 	if (msgCount > 0 && value("noheader") == NOSTR) {
5763240Skas 		inithdr++;
5772130Skas 		headers(vec);
5783240Skas 		inithdr = 0;
5793240Skas 	}
5802130Skas }
5812130Skas 
5822130Skas /*
5832130Skas  * Announce information about the file we are editing.
5842130Skas  * Return a likely place to set dot.
5852130Skas  */
58654505Sbostic int
newfileinfo(omsgCount)587*68818Sdab newfileinfo(omsgCount)
588*68818Sdab 	int omsgCount;
5892130Skas {
5901237Skas 	register struct message *mp;
5916302Skurt 	register int u, n, mdot, d, s;
5926226Skurt 	char fname[BUFSIZ], zname[BUFSIZ], *ename;
5931237Skas 
594*68818Sdab 	for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
5951475Skas 		if (mp->m_flag & MNEW)
5961475Skas 			break;
5971485Skas 	if (mp >= &message[msgCount])
598*68818Sdab 		for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
5991485Skas 			if ((mp->m_flag & MREAD) == 0)
6001485Skas 				break;
6011475Skas 	if (mp < &message[msgCount])
6021485Skas 		mdot = mp - &message[0] + 1;
6031475Skas 	else
604*68818Sdab 		mdot = omsgCount + 1;
6056302Skurt 	s = d = 0;
6061523Skas 	for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
6071523Skas 		if (mp->m_flag & MNEW)
6081523Skas 			n++;
6091523Skas 		if ((mp->m_flag & MREAD) == 0)
6101523Skas 			u++;
6116302Skurt 		if (mp->m_flag & MDELETED)
6126302Skurt 			d++;
6136302Skurt 		if (mp->m_flag & MSAVED)
6146302Skurt 			s++;
6151523Skas 	}
6166226Skurt 	ename = mailname;
6176226Skurt 	if (getfold(fname) >= 0) {
6186226Skurt 		strcat(fname, "/");
6196226Skurt 		if (strncmp(fname, mailname, strlen(fname)) == 0) {
6206226Skurt 			sprintf(zname, "+%s", mailname + strlen(fname));
6216226Skurt 			ename = zname;
6226226Skurt 		}
6236226Skurt 	}
6246226Skurt 	printf("\"%s\": ", ename);
6252130Skas 	if (msgCount == 1)
6262130Skas 		printf("1 message");
6272130Skas 	else
6282130Skas 		printf("%d messages", msgCount);
6291523Skas 	if (n > 0)
6301523Skas 		printf(" %d new", n);
6311523Skas 	if (u-n > 0)
6321523Skas 		printf(" %d unread", u);
6336302Skurt 	if (d > 0)
6346302Skurt 		printf(" %d deleted", d);
6356302Skurt 	if (s > 0)
6366302Skurt 		printf(" %d saved", s);
6372130Skas 	if (readonly)
6382130Skas 		printf(" [Read only]");
6391311Skas 	printf("\n");
6402130Skas 	return(mdot);
6411237Skas }
6421237Skas 
6431237Skas /*
6441237Skas  * Print the current version number.
6451237Skas  */
6461237Skas 
64731142Sedward /*ARGSUSED*/
64854505Sbostic int
pversion(e)6491237Skas pversion(e)
65054505Sbostic 	int e;
6511237Skas {
65233219Sbostic 	extern char *version;
65331142Sedward 
6544352Skurt 	printf("Version %s\n", version);
6551237Skas 	return(0);
6561237Skas }
6576226Skurt 
6586226Skurt /*
6596226Skurt  * Load a file of user definitions.
6606226Skurt  */
66154505Sbostic void
load(name)6626226Skurt load(name)
6636226Skurt 	char *name;
6646226Skurt {
6656226Skurt 	register FILE *in, *oldin;
6666226Skurt 
66743865Sedward 	if ((in = Fopen(name, "r")) == NULL)
6686226Skurt 		return;
6696226Skurt 	oldin = input;
6706226Skurt 	input = in;
6716226Skurt 	loading = 1;
6726226Skurt 	sourcing = 1;
6736226Skurt 	commands();
6746226Skurt 	loading = 0;
6756226Skurt 	sourcing = 0;
6766226Skurt 	input = oldin;
67743865Sedward 	Fclose(in);
6786226Skurt }
679