lex.c revision 81586
155714Skris/*
255714Skris * Copyright (c) 1980, 1993
355714Skris *	The Regents of the University of California.  All rights reserved.
455714Skris *
555714Skris * Redistribution and use in source and binary forms, with or without
655714Skris * modification, are permitted provided that the following conditions
755714Skris * are met:
855714Skris * 1. Redistributions of source code must retain the above copyright
955714Skris *    notice, this list of conditions and the following disclaimer.
1055714Skris * 2. Redistributions in binary form must reproduce the above copyright
1155714Skris *    notice, this list of conditions and the following disclaimer in the
1255714Skris *    documentation and/or other materials provided with the distribution.
1355714Skris * 3. All advertising materials mentioning features or use of this software
1455714Skris *    must display the following acknowledgement:
1555714Skris *	This product includes software developed by the University of
1655714Skris *	California, Berkeley and its contributors.
1755714Skris * 4. Neither the name of the University nor the names of its contributors
1855714Skris *    may be used to endorse or promote products derived from this software
1955714Skris *    without specific prior written permission.
2055714Skris *
2155714Skris * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2255714Skris * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2355714Skris * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2455714Skris * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2555714Skris * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2655714Skris * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2755714Skris * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2855714Skris * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2955714Skris * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3055714Skris * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3155714Skris * SUCH DAMAGE.
3255714Skris */
3355714Skris
3455714Skris#ifndef lint
3555714Skris#if 0
3655714Skrisstatic char sccsid[] = "@(#)lex.c	8.1 (Berkeley) 6/6/93";
3755714Skris#endif
3855714Skrisstatic const char rcsid[] =
3955714Skris  "$FreeBSD: head/usr.bin/mail/lex.c 81586 2001-08-13 14:06:34Z ru $";
4055714Skris#endif /* not lint */
4155714Skris
4255714Skris#include "rcv.h"
4355714Skris#include <errno.h>
4455714Skris#include <fcntl.h>
4555714Skris#include "extern.h"
4655714Skris
4755714Skris/*
4855714Skris * Mail -- a mail program
4955714Skris *
5055714Skris * Lexical processing of commands.
5155714Skris */
5255714Skris
5355714Skrisconst char	*prompt = "& ";
5455714Skris
5555714Skrisextern const struct cmd cmdtab[];
5655714Skrisextern const char *version;
5755714Skris
5872613Skris/*
59238405Sjkim * Set up editing on the given file name.
6072613Skris * If the first character of name is %, we are considered to be
6172613Skris * editing the file, otherwise we are reading our mail which has
6272613Skris * signficance for mbox and so forth.
6372613Skris */
6472613Skrisint
6572613Skrissetfile(name)
6672613Skris	char *name;
6772613Skris{
6872613Skris	FILE *ibuf;
6972613Skris	int i, fd;
7072613Skris	struct stat stb;
7172613Skris	char isedit = *name != '%';
7272613Skris	char *who = name[1] ? name + 1 : myname;
7372613Skris	char tempname[PATHSIZE];
7472613Skris	static int shudclob;
7572613Skris
7672613Skris	if ((name = expand(name)) == NULL)
7772613Skris		return (-1);
7872613Skris
7972613Skris	if ((ibuf = Fopen(name, "r")) == NULL) {
8072613Skris		if (!isedit && errno == ENOENT)
8172613Skris			goto nomail;
8272613Skris		warn("%s", name);
8372613Skris		return (-1);
8472613Skris	}
8572613Skris
8672613Skris	if (fstat(fileno(ibuf), &stb) < 0) {
8772613Skris		warn("fstat");
8872613Skris		(void)Fclose(ibuf);
8972613Skris		return (-1);
9072613Skris	}
9172613Skris
9272613Skris	if (S_ISDIR(stb.st_mode) || !S_ISREG(stb.st_mode)) {
9372613Skris		(void)Fclose(ibuf);
9472613Skris		errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
9572613Skris		warn("%s", name);
9672613Skris		return (-1);
9772613Skris	}
9872613Skris
9972613Skris	/*
10072613Skris	 * Looks like all will be well.  We must now relinquish our
10172613Skris	 * hold on the current set of stuff.  Must hold signals
10272613Skris	 * while we are reading the new file, else we will ruin
10372613Skris	 * the message[] data structure.
10472613Skris	 */
10572613Skris
10672613Skris	holdsigs();
10772613Skris	if (shudclob)
10872613Skris		quit();
10972613Skris
11072613Skris	/*
111160814Ssimon	 * Copy the messages into /tmp
112160814Ssimon	 * and set pointers.
113160814Ssimon	 */
114160814Ssimon
115160814Ssimon	readonly = 0;
116160814Ssimon	if ((i = open(name, 1)) < 0)
117160814Ssimon		readonly++;
118160814Ssimon	else
119160814Ssimon		(void)close(i);
120160814Ssimon	if (shudclob) {
121160814Ssimon		(void)fclose(itf);
122160814Ssimon		(void)fclose(otf);
123160814Ssimon	}
124238405Sjkim	shudclob = 1;
125238405Sjkim	edit = isedit;
126238405Sjkim	strlcpy(prevfile, mailname, sizeof(prevfile));
127238405Sjkim	if (name != mailname)
128238405Sjkim		strlcpy(mailname, name, sizeof(mailname));
129238405Sjkim	mailsize = fsize(ibuf);
130238405Sjkim	(void)snprintf(tempname, sizeof(tempname),
131238405Sjkim	    "%s/mail.RxXXXXXXXXXX", tmpdir);
132238405Sjkim	if ((fd = mkstemp(tempname)) == -1 || (otf = fdopen(fd, "w")) == NULL)
133238405Sjkim		err(1, "%s", tempname);
134238405Sjkim	(void)fcntl(fileno(otf), F_SETFD, 1);
135238405Sjkim	if ((itf = fopen(tempname, "r")) == NULL)
136238405Sjkim		err(1, "%s", tempname);
137238405Sjkim	(void)fcntl(fileno(itf), F_SETFD, 1);
138238405Sjkim	(void)rm(tempname);
139238405Sjkim	setptr(ibuf);
140238405Sjkim	setmsize(msgCount);
141238405Sjkim	(void)Fclose(ibuf);
142238405Sjkim	relsesigs();
143238405Sjkim	sawcom = 0;
144238405Sjkim	if (!edit && msgCount == 0) {
145238405Sjkimnomail:
146238405Sjkim		fprintf(stderr, "No mail for %s\n", who);
147238405Sjkim		return (-1);
148238405Sjkim	}
149238405Sjkim	return (0);
15055714Skris}
15155714Skris
15255714Skrisint	*msgvec;
15355714Skrisint	reset_on_stop;			/* do a reset() if stopped */
154109998Smarkm
155238405Sjkim/*
156238405Sjkim * Interpret user commands one by one.  If standard input is not a tty,
157238405Sjkim * print no prompt.
158238405Sjkim */
159238405Sjkimvoid
160109998Smarkmcommands()
161160814Ssimon{
162160814Ssimon	int n, eofloop = 0;
163160814Ssimon	char linebuf[LINESIZE];
16455714Skris
165167612Ssimon	if (!sourcing) {
16655714Skris		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
16755714Skris			(void)signal(SIGINT, intr);
16855714Skris		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
169160814Ssimon			(void)signal(SIGHUP, hangup);
17055714Skris		(void)signal(SIGTSTP, stop);
171238405Sjkim		(void)signal(SIGTTOU, stop);
17255714Skris		(void)signal(SIGTTIN, stop);
17355714Skris	}
17455714Skris	setexit();
17555714Skris	for (;;) {
17655714Skris		/*
17755714Skris		 * Print the prompt, if needed.  Clear out
178238405Sjkim		 * string space, and flush the output.
179238405Sjkim		 */
180238405Sjkim		if (!sourcing && value("interactive") != NULL) {
181238405Sjkim			reset_on_stop = 1;
182238405Sjkim			printf("%s", prompt);
183100936Snectar		}
184238405Sjkim		(void)fflush(stdout);
18555714Skris		sreset();
18659191Skris		/*
18755714Skris		 * Read a line of commands from the current input
188238405Sjkim		 * and handle end of file specially.
18955714Skris		 */
19055714Skris		n = 0;
19155714Skris		for (;;) {
19255714Skris			if (readline(input, &linebuf[n], LINESIZE - n) < 0) {
19355714Skris				if (n == 0)
194238405Sjkim					n = -1;
195238405Sjkim				break;
196238405Sjkim			}
197238405Sjkim			if ((n = strlen(linebuf)) == 0)
198238405Sjkim				break;
199194206Ssimon			n--;
200238405Sjkim			if (linebuf[n] != '\\')
20155714Skris				break;
20259191Skris			linebuf[n++] = ' ';
20355714Skris		}
204238405Sjkim		reset_on_stop = 0;
20555714Skris		if (n < 0) {
20655714Skris				/* eof */
20755714Skris			if (loading)
20855714Skris				break;
20955714Skris			if (sourcing) {
210238405Sjkim				unstack();
211238405Sjkim				continue;
212238405Sjkim			}
213238405Sjkim			if (value("interactive") != NULL &&
214238405Sjkim			    value("ignoreeof") != NULL &&
21559191Skris			    ++eofloop < 25) {
216238405Sjkim				printf("Use \"quit\" to quit.\n");
21759191Skris				continue;
21859191Skris			}
21955714Skris			break;
220238405Sjkim		}
22155714Skris		eofloop = 0;
22255714Skris		if (execute(linebuf, 0))
22355714Skris			break;
22455714Skris	}
22555714Skris}
226238405Sjkim
227238405Sjkim/*
228238405Sjkim * Execute a single command.
229238405Sjkim * Command functions return 0 for success, 1 for error, and -1
230238405Sjkim * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
23159191Skris * the interactive command loop.
232238405Sjkim * Contxt is non-zero if called while composing mail.
23359191Skris */
23459191Skrisint
23555714Skrisexecute(linebuf, contxt)
236238405Sjkim	char linebuf[];
23755714Skris	int contxt;
23855714Skris{
23955714Skris	char word[LINESIZE];
24055714Skris	char *arglist[MAXARGC];
24155714Skris	const struct cmd *com;
242238405Sjkim	char *cp, *cp2;
243238405Sjkim	int c, muvec[2];
244238405Sjkim	int e = 1;
245238405Sjkim
246238405Sjkim	/*
24759191Skris	 * Strip the white space away from the beginning
248238405Sjkim	 * of the command, then scan out a word, which
24959191Skris	 * consists of anything except digits and white space.
25059191Skris	 *
25155714Skris	 * Handle ! escapes differently to get the correct
252238405Sjkim	 * lexical conventions.
25355714Skris	 */
25455714Skris
25555714Skris	for (cp = linebuf; isspace(*cp); cp++)
25655714Skris		;
25755714Skris	if (*cp == '!') {
258238405Sjkim		if (sourcing) {
259238405Sjkim			printf("Can't \"!\" while sourcing\n");
260238405Sjkim			goto out;
261238405Sjkim		}
262238405Sjkim		shell(cp+1);
26359191Skris		return (0);
264238405Sjkim	}
26559191Skris	cp2 = word;
26659191Skris	while (*cp != '\0' && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL)
26755714Skris		*cp2++ = *cp++;
268238405Sjkim	*cp2 = '\0';
26955714Skris
270127128Snectar	/*
27155714Skris	 * Look up the command; if not found, bitch.
27255714Skris	 * Normally, a blank command would map to the
27355714Skris	 * first command in the table; while sourcing,
27455714Skris	 * however, we ignore blank lines to eliminate
275238405Sjkim	 * confusion.
276238405Sjkim	 */
277238405Sjkim
278238405Sjkim	if (sourcing && *word == '\0')
279238405Sjkim		return (0);
28059191Skris	com = lex(word);
281238405Sjkim	if (com == NULL) {
28259191Skris		printf("Unknown command: \"%s\"\n", word);
28359191Skris		goto out;
28455714Skris	}
285127128Snectar
286238405Sjkim	/*
28755714Skris	 * See if we should execute the command -- if a conditional
28855714Skris	 * we always execute it, otherwise, check the state of cond.
28955714Skris	 */
29055714Skris
29155714Skris	if ((com->c_argtype & F) == 0)
292238405Sjkim		if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode))
293238405Sjkim			return (0);
294238405Sjkim
295238405Sjkim	/*
296238405Sjkim	 * Process the arguments to the command, depending
297160814Ssimon	 * on the type he expects.  Default to an error.
298238405Sjkim	 * If we are sourcing an interactive command, it's
29959191Skris	 * an error.
30059191Skris	 */
30155714Skris
302238405Sjkim	if (!rcvmode && (com->c_argtype & M) == 0) {
30355714Skris		printf("May not execute \"%s\" while sending\n",
30455714Skris		    com->c_name);
30555714Skris		goto out;
30655714Skris	}
30755714Skris	if (sourcing && com->c_argtype & I) {
308238405Sjkim		printf("May not execute \"%s\" while sourcing\n",
309238405Sjkim		    com->c_name);
310238405Sjkim		goto out;
311238405Sjkim	}
312238405Sjkim	if (readonly && com->c_argtype & W) {
313160814Ssimon		printf("May not execute \"%s\" -- message file is read only\n",
314238405Sjkim		   com->c_name);
31559191Skris		goto out;
31659191Skris	}
31755714Skris	if (contxt && com->c_argtype & R) {
318238405Sjkim		printf("Cannot recursively invoke \"%s\"\n", com->c_name);
31955714Skris		goto out;
32055714Skris	}
32155714Skris	switch (com->c_argtype & ~(F|P|I|M|T|W|R)) {
32255714Skris	case MSGLIST:
32355714Skris		/*
324238405Sjkim		 * A message list defaulting to nearest forward
325238405Sjkim		 * legal message.
326238405Sjkim		 */
327238405Sjkim		if (msgvec == 0) {
328238405Sjkim			printf("Illegal use of \"message list\"\n");
329194206Ssimon			break;
330238405Sjkim		}
331279264Sdelphij		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
33259191Skris			break;
33355714Skris		if (c  == 0) {
334238405Sjkim			*msgvec = first(com->c_msgflag, com->c_msgmask);
335160814Ssimon			msgvec[1] = 0;
33655714Skris		}
33755714Skris		if (*msgvec == 0) {
33855714Skris			printf("No applicable messages\n");
33955714Skris			break;
34055714Skris		}
341238405Sjkim		e = (*com->c_func)(msgvec);
342238405Sjkim		break;
343238405Sjkim
344238405Sjkim	case NDMLIST:
345238405Sjkim		/*
346160814Ssimon		 * A message list with no defaults, but no error
347238405Sjkim		 * if none exist.
34859191Skris		 */
34959191Skris		if (msgvec == 0) {
35055714Skris			printf("Illegal use of \"message list\"\n");
351238405Sjkim			break;
35255714Skris		}
35355714Skris		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
354238405Sjkim			break;
35555714Skris		e = (*com->c_func)(msgvec);
35655714Skris		break;
357238405Sjkim
358238405Sjkim	case STRLIST:
359238405Sjkim		/*
360238405Sjkim		 * Just the straight string, with
361238405Sjkim		 * leading blanks removed.
362160814Ssimon		 */
363238405Sjkim		while (isspace(*cp))
36459191Skris			cp++;
36559191Skris		e = (*com->c_func)(cp);
36655714Skris		break;
367238405Sjkim
36855714Skris	case RAWLIST:
36955714Skris		/*
370238405Sjkim		 * A vector of strings, in shell style.
37155714Skris		 */
37255714Skris		if ((c = getrawlist(cp, arglist,
373238405Sjkim		    sizeof(arglist) / sizeof(*arglist))) < 0)
374238405Sjkim			break;
375238405Sjkim		if (c < com->c_minargs) {
376238405Sjkim			printf("%s requires at least %d arg(s)\n",
377238405Sjkim			    com->c_name, com->c_minargs);
378194206Ssimon			break;
379238405Sjkim		}
380279264Sdelphij		if (c > com->c_maxargs) {
38159191Skris			printf("%s takes no more than %d arg(s)\n",
38255714Skris			    com->c_name, com->c_maxargs);
383238405Sjkim			break;
38455714Skris		}
38555714Skris		e = (*com->c_func)(arglist);
386238405Sjkim		break;
38755714Skris
38855714Skris	case NOLIST:
389238405Sjkim		/*
390238405Sjkim		 * Just the constant zero, for exiting,
391238405Sjkim		 * eg.
392238405Sjkim		 */
393238405Sjkim		e = (*com->c_func)(0);
394160814Ssimon		break;
395238405Sjkim
39659191Skris	default:
39759191Skris		errx(1, "Unknown argtype");
39855714Skris	}
399238405Sjkim
40055714Skrisout:
40155714Skris	/*
402238405Sjkim	 * Exit the current source file on
40355714Skris	 * error.
40455714Skris	 */
405238405Sjkim	if (e) {
406238405Sjkim		if (e < 0)
407238405Sjkim			return (1);
408238405Sjkim		if (loading)
409238405Sjkim			return (1);
410160814Ssimon		if (sourcing)
411238405Sjkim			unstack();
41259191Skris		return (0);
41359191Skris	}
41455714Skris	if (value("autoprint") != NULL && com->c_argtype & P)
415238405Sjkim		if ((dot->m_flag & MDELETED) == 0) {
41655714Skris			muvec[0] = dot - &message[0] + 1;
41755714Skris			muvec[1] = 0;
418238405Sjkim			type(muvec);
41955714Skris		}
42055714Skris	if (!sourcing && (com->c_argtype & T) == 0)
421238405Sjkim		sawcom = 1;
422238405Sjkim	return (0);
423238405Sjkim}
424238405Sjkim
425238405Sjkim/*
426194206Ssimon * Set the size of the message vector used to construct argument
427238405Sjkim * lists to message list functions.
428279264Sdelphij */
42959191Skrisvoid
43055714Skrissetmsize(sz)
43155714Skris	int sz;
43255714Skris{
43355714Skris
43455714Skris	if (msgvec != 0)
43555714Skris		(void)free(msgvec);
43655714Skris	msgvec = calloc((unsigned)(sz + 1), sizeof(*msgvec));
43755714Skris}
438238405Sjkim
439238405Sjkim/*
440238405Sjkim * Find the correct command in the command table corresponding
441238405Sjkim * to the passed command "word"
442238405Sjkim */
443160814Ssimon
444238405Sjkim__const struct cmd *
44559191Skrislex(word)
44659191Skris	char word[];
44755714Skris{
448238405Sjkim	const struct cmd *cp;
44955714Skris
45055714Skris	/*
45155714Skris	 * ignore trailing chars after `#'
45255714Skris	 *
45355714Skris	 * lines with beginning `#' are comments
454238405Sjkim	 * spaces before `#' are ignored in execute()
455238405Sjkim	 */
456238405Sjkim
457238405Sjkim	if (*word == '#')
458238405Sjkim	    *(word+1) = '\0';
459160814Ssimon
460238405Sjkim
46159191Skris	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
46259191Skris		if (isprefix(word, cp->c_name))
46355714Skris			return (cp);
464238405Sjkim	return (NULL);
46555714Skris}
46655714Skris
46755714Skris/*
46855714Skris * Determine if as1 is a valid prefix of as2.
46955714Skris * Return true if yep.
470238405Sjkim */
471238405Sjkimint
472238405Sjkimisprefix(as1, as2)
473238405Sjkim	const char *as1, *as2;
474238405Sjkim{
475194206Ssimon	const char *s1, *s2;
476238405Sjkim
477279264Sdelphij	s1 = as1;
47859191Skris	s2 = as2;
47955714Skris	while (*s1++ == *s2)
480238405Sjkim		if (*s2++ == '\0')
48155714Skris			return (1);
48255714Skris	return (*--s1 == '\0');
48355714Skris}
48455714Skris
48555714Skris/*
486238405Sjkim * The following gets called on receipt of an interrupt.  This is
487238405Sjkim * to abort printout of a command, mainly.
488238405Sjkim * Dispatching here when command() is inactive crashes rcv.
489238405Sjkim * Close all open files except 0, 1, 2, and the temporary.
490238405Sjkim * Also, unstack all source files.
491160814Ssimon */
492238405Sjkim
49359191Skrisint	inithdr;			/* am printing startup headers */
49459191Skris
49555714Skris/*ARGSUSED*/
496238405Sjkimvoid
49755714Skrisintr(s)
49855714Skris	int s;
49955714Skris{
50055714Skris
50155714Skris	noreset = 0;
502238405Sjkim	if (!inithdr)
503238405Sjkim		sawcom++;
504238405Sjkim	inithdr = 0;
505238405Sjkim	while (sourcing)
506238405Sjkim		unstack();
507160814Ssimon
508238405Sjkim	close_all_files();
50959191Skris
51059191Skris	if (image >= 0) {
51155714Skris		(void)close(image);
512238405Sjkim		image = -1;
51355714Skris	}
51455714Skris	fprintf(stderr, "Interrupt\n");
51555714Skris	reset(0);
51655714Skris}
51755714Skris
518238405Sjkim/*
519238405Sjkim * When we wake up after ^Z, reprint the prompt.
520238405Sjkim */
521238405Sjkimvoid
522238405Sjkimstop(s)
523194206Ssimon	int s;
524238405Sjkim{
525279264Sdelphij	sig_t old_action = signal(s, SIG_DFL);
52659191Skris
52755714Skris	(void)sigsetmask(sigblock(0) & ~sigmask(s));
528238405Sjkim	(void)kill(0, s);
529160814Ssimon	(void)sigblock(sigmask(s));
530160814Ssimon	(void)signal(s, old_action);
531160814Ssimon	if (reset_on_stop) {
532160814Ssimon		reset_on_stop = 0;
533160814Ssimon		reset(0);
534238405Sjkim	}
535238405Sjkim}
536238405Sjkim
537238405Sjkim/*
538238405Sjkim * Branch here on hangup signal and simulate "exit".
539160814Ssimon */
540238405Sjkim/*ARGSUSED*/
541160814Ssimonvoid
542160814Ssimonhangup(s)
543160814Ssimon	int s;
544238405Sjkim{
545160814Ssimon
546160814Ssimon	/* nothing to do? */
547160814Ssimon	exit(1);
548160814Ssimon}
549160814Ssimon
550238405Sjkim/*
551238405Sjkim * Announce the presence of the current Mail version,
552238405Sjkim * give the message count, and print a header listing.
553238405Sjkim */
554238405Sjkimvoid
555160814Ssimonannounce()
556238405Sjkim{
557160814Ssimon	int vec[2], mdot;
558160814Ssimon
559160814Ssimon	mdot = newfileinfo();
560238405Sjkim	vec[0] = mdot;
561160814Ssimon	vec[1] = 0;
562160814Ssimon	dot = &message[mdot - 1];
563160814Ssimon	if (msgCount > 0 && value("noheader") == NULL) {
564160814Ssimon		inithdr++;
565160814Ssimon		headers(vec);
566238405Sjkim		inithdr = 0;
567238405Sjkim	}
568238405Sjkim}
569238405Sjkim
570238405Sjkim/*
571160814Ssimon * Announce information about the file we are editing.
572238405Sjkim * Return a likely place to set dot.
573160814Ssimon */
574160814Ssimonint
575160814Ssimonnewfileinfo()
576238405Sjkim{
577160814Ssimon	struct message *mp;
578160814Ssimon	int u, n, mdot, d, s;
579160814Ssimon	char fname[PATHSIZE+1], zname[PATHSIZE+1], *ename;
580160814Ssimon
581160814Ssimon	for (mp = &message[0]; mp < &message[msgCount]; mp++)
582238405Sjkim		if (mp->m_flag & MNEW)
583238405Sjkim			break;
584238405Sjkim	if (mp >= &message[msgCount])
585238405Sjkim		for (mp = &message[0]; mp < &message[msgCount]; mp++)
586238405Sjkim			if ((mp->m_flag & MREAD) == 0)
587160814Ssimon				break;
588238405Sjkim	if (mp < &message[msgCount])
589160814Ssimon		mdot = mp - &message[0] + 1;
590160814Ssimon	else
591160814Ssimon		mdot = 1;
592238405Sjkim	s = d = 0;
593160814Ssimon	for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
594160814Ssimon		if (mp->m_flag & MNEW)
595160814Ssimon			n++;
596160814Ssimon		if ((mp->m_flag & MREAD) == 0)
597160814Ssimon			u++;
598238405Sjkim		if (mp->m_flag & MDELETED)
599238405Sjkim			d++;
600238405Sjkim		if (mp->m_flag & MSAVED)
601238405Sjkim			s++;
602238405Sjkim	}
603194206Ssimon	ename = mailname;
604238405Sjkim	if (getfold(fname, sizeof(fname) - 1) >= 0) {
605279264Sdelphij		strcat(fname, "/");
606160814Ssimon		if (strncmp(fname, mailname, strlen(fname)) == 0) {
607160814Ssimon			(void)snprintf(zname, sizeof(zname), "+%s",
60855714Skris			    mailname + strlen(fname));
609238405Sjkim			ename = zname;
610238405Sjkim		}
61155714Skris	}
61255714Skris	printf("\"%s\": ", ename);
61355714Skris	if (msgCount == 1)
61455714Skris		printf("1 message");
61555714Skris	else
616238405Sjkim		printf("%d messages", msgCount);
617238405Sjkim	if (n > 0)
618238405Sjkim		printf(" %d new", n);
619238405Sjkim	if (u-n > 0)
620238405Sjkim		printf(" %d unread", u);
621100936Snectar	if (d > 0)
622238405Sjkim		printf(" %d deleted", d);
62355714Skris	if (s > 0)
62459191Skris		printf(" %d saved", s);
62555714Skris	if (readonly)
62655714Skris		printf(" [Read only]");
62755714Skris	printf("\n");
62855714Skris	return (mdot);
62955714Skris}
63055714Skris
63155714Skris/*
632238405Sjkim * Print the current version number.
633238405Sjkim */
634238405Sjkim
635238405Sjkim/*ARGSUSED*/
636238405Sjkimint
637100936Snectarpversion(e)
638238405Sjkim	int e;
63955714Skris{
64059191Skris
64155714Skris	printf("Version %s\n", version);
64255714Skris	return (0);
64355714Skris}
64455714Skris
64555714Skris/*
64655714Skris * Load a file of user definitions.
64755714Skris */
648238405Sjkimvoid
649238405Sjkimload(name)
650238405Sjkim	char *name;
651238405Sjkim{
652238405Sjkim	FILE *in, *oldin;
653100928Snectar
654238405Sjkim	if ((in = Fopen(name, "r")) == NULL)
65559191Skris		return;
65659191Skris	oldin = input;
65755714Skris	input = in;
658109998Smarkm	loading = 1;
65955714Skris	sourcing = 1;
660109998Smarkm	commands();
661238405Sjkim	loading = 0;
662194206Ssimon	sourcing = 0;
663109998Smarkm	input = oldin;
664109998Smarkm	(void)Fclose(in);
665109998Smarkm}
666109998Smarkm