lex.c revision 88227
1139823Simp/*
2204591Sluigi * Copyright (c) 1980, 1993
355597Sluigi *	The Regents of the University of California.  All rights reserved.
455597Sluigi *
539119Sluigi * Redistribution and use in source and binary forms, with or without
655597Sluigi * modification, are permitted provided that the following conditions
755597Sluigi * are met:
855597Sluigi * 1. Redistributions of source code must retain the above copyright
955597Sluigi *    notice, this list of conditions and the following disclaimer.
1055597Sluigi * 2. Redistributions in binary form must reproduce the above copyright
1155597Sluigi *    notice, this list of conditions and the following disclaimer in the
1255597Sluigi *    documentation and/or other materials provided with the distribution.
1355597Sluigi * 3. All advertising materials mentioning features or use of this software
1439119Sluigi *    must display the following acknowledgement:
1555597Sluigi *	This product includes software developed by the University of
1655597Sluigi *	California, Berkeley and its contributors.
1755597Sluigi * 4. Neither the name of the University nor the names of its contributors
1855597Sluigi *    may be used to endorse or promote products derived from this software
1955597Sluigi *    without specific prior written permission.
2055597Sluigi *
2155597Sluigi * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2255597Sluigi * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2355597Sluigi * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2455597Sluigi * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2555597Sluigi * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2639119Sluigi * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2750477Speter * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2839119Sluigi * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2939119Sluigi * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3039119Sluigi * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3139119Sluigi * SUCH DAMAGE.
3239119Sluigi */
3339119Sluigi
34204591Sluigi#ifndef lint
3571137Sluigi#if 0
36204591Sluigistatic char sccsid[] = "@(#)lex.c	8.2 (Berkeley) 4/20/95";
37204591Sluigi#endif
38204591Sluigistatic const char rcsid[] =
39204591Sluigi  "$FreeBSD: head/usr.bin/mail/lex.c 88227 2001-12-19 21:50:22Z ache $";
40204591Sluigi#endif /* not lint */
41204591Sluigi
42204591Sluigi#include "rcv.h"
43204591Sluigi#include <errno.h>
4471137Sluigi#include <fcntl.h>
45204591Sluigi#include "extern.h"
46204591Sluigi
4761413Sluigi/*
4871137Sluigi * Mail -- a mail program
49204591Sluigi *
50204591Sluigi * Lexical processing of commands.
5161413Sluigi */
52204591Sluigi
53204591Sluigiconst char	*prompt = "& ";
54204591Sluigi
55204591Sluigiextern const struct cmd cmdtab[];
56204591Sluigiextern const char *version;
57204591Sluigi
5861413Sluigi/*
5971137Sluigi * Set up editing on the given file name.
60204591Sluigi * If the first character of name is %, we are considered to be
61204591Sluigi * editing the file, otherwise we are reading our mail which has
62204591Sluigi * signficance for mbox and so forth.
63104975Sseanc */
64204591Sluigiint
65204591Sluigisetfile(name)
66204591Sluigi	char *name;
67204591Sluigi{
68204591Sluigi	FILE *ibuf;
69204591Sluigi	int i, fd;
70204591Sluigi	struct stat stb;
71204591Sluigi	char isedit = *name != '%' || getuserid(myname) != getuid();
72204591Sluigi	char *who = name[1] ? name + 1 : myname;
73204591Sluigi	char tempname[PATHSIZE];
74204591Sluigi	static int shudclob;
75104975Sseanc
76204591Sluigi	if ((name = expand(name)) == NULL)
77204591Sluigi		return (-1);
78204591Sluigi
79204591Sluigi	if ((ibuf = Fopen(name, "r")) == NULL) {
80204591Sluigi		if (!isedit && errno == ENOENT)
81204591Sluigi			goto nomail;
82204591Sluigi		warn("%s", name);
83204591Sluigi		return (-1);
84204591Sluigi	}
85204591Sluigi
86204591Sluigi	if (fstat(fileno(ibuf), &stb) < 0) {
87204591Sluigi		warn("fstat");
88204591Sluigi		(void)Fclose(ibuf);
89204591Sluigi		return (-1);
90206845Sluigi	}
9155597Sluigi
92204591Sluigi	if (S_ISDIR(stb.st_mode) || !S_ISREG(stb.st_mode)) {
93204591Sluigi		(void)Fclose(ibuf);
94204591Sluigi		errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
95204591Sluigi		warn("%s", name);
96204591Sluigi		return (-1);
97206845Sluigi	}
9855597Sluigi
99204591Sluigi	/*
100204591Sluigi	 * Looks like all will be well.  We must now relinquish our
101204591Sluigi	 * hold on the current set of stuff.  Must hold signals
102204591Sluigi	 * while we are reading the new file, else we will ruin
103204591Sluigi	 * the message[] data structure.
104204591Sluigi	 */
105204591Sluigi
106204591Sluigi	holdsigs();
107204591Sluigi	if (shudclob)
10839119Sluigi		quit();
10939119Sluigi
11061413Sluigi	/*
111204591Sluigi	 * Copy the messages into /tmp
11261413Sluigi	 * and set pointers.
113204591Sluigi	 */
114204591Sluigi
11561413Sluigi	readonly = 0;
116206845Sluigi	if ((i = open(name, 1)) < 0)
117204591Sluigi		readonly++;
118204591Sluigi	else
119204591Sluigi		(void)close(i);
120206845Sluigi	if (shudclob) {
121204591Sluigi		(void)fclose(itf);
122204591Sluigi		(void)fclose(otf);
123204591Sluigi	}
124204591Sluigi	shudclob = 1;
125206845Sluigi	edit = isedit;
12655597Sluigi	strlcpy(prevfile, mailname, sizeof(prevfile));
12771137Sluigi	if (name != mailname)
128204591Sluigi		strlcpy(mailname, name, sizeof(mailname));
129204591Sluigi	mailsize = fsize(ibuf);
130204591Sluigi	(void)snprintf(tempname, sizeof(tempname),
131204591Sluigi	    "%s/mail.RxXXXXXXXXXX", tmpdir);
13271137Sluigi	if ((fd = mkstemp(tempname)) == -1 || (otf = fdopen(fd, "w")) == NULL)
133204591Sluigi		err(1, "%s", tempname);
134204591Sluigi	(void)fcntl(fileno(otf), F_SETFD, 1);
135206845Sluigi	if ((itf = fopen(tempname, "r")) == NULL)
136206845Sluigi		err(1, "%s", tempname);
137206845Sluigi	(void)fcntl(fileno(itf), F_SETFD, 1);
138206845Sluigi	(void)rm(tempname);
139204591Sluigi	setptr(ibuf, 0);
14061413Sluigi	setmsize(msgCount);
141206845Sluigi	/*
142204591Sluigi	 * New mail may have arrived while we were reading
143204591Sluigi	 * the mail file, so reset mailsize to be where
144204591Sluigi	 * we really are in the file...
145204591Sluigi	 */
146204591Sluigi	mailsize = ftello(ibuf);
14796077Sluigi	(void)Fclose(ibuf);
148204591Sluigi	relsesigs();
149204591Sluigi	sawcom = 0;
150204591Sluigi	if (!edit && msgCount == 0) {
151204591Sluiginomail:
152206845Sluigi		fprintf(stderr, "No mail for %s\n", who);
153206845Sluigi		return (-1);
154206845Sluigi	}
155206845Sluigi	return (0);
156206845Sluigi}
157206845Sluigi
158206845Sluigi/*
159206845Sluigi * Incorporate any new mail that has arrived since we first
160204591Sluigi * started reading mail.
161152910Sglebius */
16261413Sluigiint
16355597Sluigiincfile()
164204591Sluigi{
165204591Sluigi	off_t newsize;
166204591Sluigi	int omsgCount = msgCount;
167204591Sluigi	FILE *ibuf;
16855597Sluigi
169204591Sluigi	ibuf = Fopen(mailname, "r");
170204591Sluigi	if (ibuf == NULL)
171204591Sluigi		return (-1);
172204591Sluigi	holdsigs();
173204591Sluigi	newsize = fsize(ibuf);
174239124Sluigi	if (newsize == 0)
175239124Sluigi		return (-1);		/* mail box is now empty??? */
176204591Sluigi	if (newsize < mailsize)
177204591Sluigi		return (-1);		/* mail box has shrunk??? */
17839119Sluigi	if (newsize == mailsize)
17939119Sluigi		return (0);		/* no new mail */
180206845Sluigi	setptr(ibuf, mailsize);
181204591Sluigi	setmsize(msgCount);
182204591Sluigi	mailsize = ftello(ibuf);
183206845Sluigi	(void)Fclose(ibuf);
184204591Sluigi	relsesigs();
185204591Sluigi	return (msgCount - omsgCount);
186204591Sluigi}
187204591Sluigi
188204591Sluigiint	*msgvec;
18961413Sluigiint	reset_on_stop;			/* do a reset() if stopped */
190204591Sluigi
191204591Sluigi/*
192204591Sluigi * Interpret user commands one by one.  If standard input is not a tty,
193204591Sluigi * print no prompt.
194190865Sluigi */
195204591Sluigivoid
196204591Sluigicommands()
197204591Sluigi{
198204591Sluigi	int n, eofloop = 0;
199204591Sluigi	char linebuf[LINESIZE];
200204591Sluigi
201204591Sluigi	if (!sourcing) {
202206845Sluigi		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
203190865Sluigi			(void)signal(SIGINT, intr);
204206845Sluigi		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
205206845Sluigi			(void)signal(SIGHUP, hangup);
206206845Sluigi		(void)signal(SIGTSTP, stop);
207206845Sluigi		(void)signal(SIGTTOU, stop);
208206845Sluigi		(void)signal(SIGTTIN, stop);
209206845Sluigi	}
21039119Sluigi	setexit();
211190865Sluigi	for (;;) {
212204591Sluigi		/*
213204591Sluigi		 * Print the prompt, if needed.  Clear out
214204591Sluigi		 * string space, and flush the output.
215204591Sluigi		 */
216204591Sluigi		if (!sourcing && value("interactive") != NULL) {
217204591Sluigi			if ((value("autoinc") != NULL) && (incfile() > 0))
218204591Sluigi				printf("New mail has arrived.\n");
219204591Sluigi			reset_on_stop = 1;
220204591Sluigi			printf("%s", prompt);
221204591Sluigi		}
222204591Sluigi		(void)fflush(stdout);
223204591Sluigi		sreset();
224204591Sluigi		/*
225204591Sluigi		 * Read a line of commands from the current input
226204591Sluigi		 * and handle end of file specially.
227204591Sluigi		 */
228204591Sluigi		n = 0;
229204591Sluigi		for (;;) {
230204591Sluigi			if (readline(input, &linebuf[n], LINESIZE - n) < 0) {
231204591Sluigi				if (n == 0)
232204591Sluigi					n = -1;
233204591Sluigi				break;
234204591Sluigi			}
235204591Sluigi			if ((n = strlen(linebuf)) == 0)
236204591Sluigi				break;
237204591Sluigi			n--;
238204591Sluigi			if (linebuf[n] != '\\')
239204591Sluigi				break;
240204591Sluigi			linebuf[n++] = ' ';
241204591Sluigi		}
242204591Sluigi		reset_on_stop = 0;
243204591Sluigi		if (n < 0) {
244204591Sluigi				/* eof */
245204591Sluigi			if (loading)
246204591Sluigi				break;
247204591Sluigi			if (sourcing) {
248204591Sluigi				unstack();
249204591Sluigi				continue;
250204591Sluigi			}
251204591Sluigi			if (value("interactive") != NULL &&
252204591Sluigi			    value("ignoreeof") != NULL &&
253204591Sluigi			    ++eofloop < 25) {
254204591Sluigi				printf("Use \"quit\" to quit.\n");
255204591Sluigi				continue;
256204591Sluigi			}
257204591Sluigi			break;
258204591Sluigi		}
259204591Sluigi		eofloop = 0;
260204591Sluigi		if (execute(linebuf, 0))
261190865Sluigi			break;
262190865Sluigi	}
26339119Sluigi}
264
265/*
266 * Execute a single command.
267 * Command functions return 0 for success, 1 for error, and -1
268 * for abort.  A 1 or -1 aborts a load or source.  A -1 aborts
269 * the interactive command loop.
270 * Contxt is non-zero if called while composing mail.
271 */
272int
273execute(linebuf, contxt)
274	char linebuf[];
275	int contxt;
276{
277	char word[LINESIZE];
278	char *arglist[MAXARGC];
279	const struct cmd *com;
280	char *cp, *cp2;
281	int c, muvec[2];
282	int e = 1;
283
284	/*
285	 * Strip the white space away from the beginning
286	 * of the command, then scan out a word, which
287	 * consists of anything except digits and white space.
288	 *
289	 * Handle ! escapes differently to get the correct
290	 * lexical conventions.
291	 */
292
293	for (cp = linebuf; isspace((unsigned char)*cp); cp++)
294		;
295	if (*cp == '!') {
296		if (sourcing) {
297			printf("Can't \"!\" while sourcing\n");
298			goto out;
299		}
300		shell(cp+1);
301		return (0);
302	}
303	cp2 = word;
304	while (*cp != '\0' && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL)
305		*cp2++ = *cp++;
306	*cp2 = '\0';
307
308	/*
309	 * Look up the command; if not found, bitch.
310	 * Normally, a blank command would map to the
311	 * first command in the table; while sourcing,
312	 * however, we ignore blank lines to eliminate
313	 * confusion.
314	 */
315
316	if (sourcing && *word == '\0')
317		return (0);
318	com = lex(word);
319	if (com == NULL) {
320		printf("Unknown command: \"%s\"\n", word);
321		goto out;
322	}
323
324	/*
325	 * See if we should execute the command -- if a conditional
326	 * we always execute it, otherwise, check the state of cond.
327	 */
328
329	if ((com->c_argtype & F) == 0)
330		if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode))
331			return (0);
332
333	/*
334	 * Process the arguments to the command, depending
335	 * on the type he expects.  Default to an error.
336	 * If we are sourcing an interactive command, it's
337	 * an error.
338	 */
339
340	if (!rcvmode && (com->c_argtype & M) == 0) {
341		printf("May not execute \"%s\" while sending\n",
342		    com->c_name);
343		goto out;
344	}
345	if (sourcing && com->c_argtype & I) {
346		printf("May not execute \"%s\" while sourcing\n",
347		    com->c_name);
348		goto out;
349	}
350	if (readonly && com->c_argtype & W) {
351		printf("May not execute \"%s\" -- message file is read only\n",
352		   com->c_name);
353		goto out;
354	}
355	if (contxt && com->c_argtype & R) {
356		printf("Cannot recursively invoke \"%s\"\n", com->c_name);
357		goto out;
358	}
359	switch (com->c_argtype & ~(F|P|I|M|T|W|R)) {
360	case MSGLIST:
361		/*
362		 * A message list defaulting to nearest forward
363		 * legal message.
364		 */
365		if (msgvec == 0) {
366			printf("Illegal use of \"message list\"\n");
367			break;
368		}
369		if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
370			break;
371		if (c  == 0) {
372			*msgvec = first(com->c_msgflag, com->c_msgmask);
373			msgvec[1] = 0;
374		}
375		if (*msgvec == 0) {
376			printf("No applicable messages\n");
377			break;
378		}
379		e = (*com->c_func)(msgvec);
380		break;
381
382	case NDMLIST:
383		/*
384		 * A message list with no defaults, but no error
385		 * if none exist.
386		 */
387		if (msgvec == 0) {
388			printf("Illegal use of \"message list\"\n");
389			break;
390		}
391		if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
392			break;
393		e = (*com->c_func)(msgvec);
394		break;
395
396	case STRLIST:
397		/*
398		 * Just the straight string, with
399		 * leading blanks removed.
400		 */
401		while (isspace((unsigned char)*cp))
402			cp++;
403		e = (*com->c_func)(cp);
404		break;
405
406	case RAWLIST:
407		/*
408		 * A vector of strings, in shell style.
409		 */
410		if ((c = getrawlist(cp, arglist,
411		    sizeof(arglist) / sizeof(*arglist))) < 0)
412			break;
413		if (c < com->c_minargs) {
414			printf("%s requires at least %d arg(s)\n",
415			    com->c_name, com->c_minargs);
416			break;
417		}
418		if (c > com->c_maxargs) {
419			printf("%s takes no more than %d arg(s)\n",
420			    com->c_name, com->c_maxargs);
421			break;
422		}
423		e = (*com->c_func)(arglist);
424		break;
425
426	case NOLIST:
427		/*
428		 * Just the constant zero, for exiting,
429		 * eg.
430		 */
431		e = (*com->c_func)(0);
432		break;
433
434	default:
435		errx(1, "Unknown argtype");
436	}
437
438out:
439	/*
440	 * Exit the current source file on
441	 * error.
442	 */
443	if (e) {
444		if (e < 0)
445			return (1);
446		if (loading)
447			return (1);
448		if (sourcing)
449			unstack();
450		return (0);
451	}
452	if (com == NULL)
453		return (0);
454	if (value("autoprint") != NULL && com->c_argtype & P)
455		if ((dot->m_flag & MDELETED) == 0) {
456			muvec[0] = dot - &message[0] + 1;
457			muvec[1] = 0;
458			type(muvec);
459		}
460	if (!sourcing && (com->c_argtype & T) == 0)
461		sawcom = 1;
462	return (0);
463}
464
465/*
466 * Set the size of the message vector used to construct argument
467 * lists to message list functions.
468 */
469void
470setmsize(sz)
471	int sz;
472{
473
474	if (msgvec != NULL)
475		(void)free(msgvec);
476	msgvec = calloc((unsigned)(sz + 1), sizeof(*msgvec));
477}
478
479/*
480 * Find the correct command in the command table corresponding
481 * to the passed command "word"
482 */
483
484__const struct cmd *
485lex(word)
486	char word[];
487{
488	const struct cmd *cp;
489
490	/*
491	 * ignore trailing chars after `#'
492	 *
493	 * lines with beginning `#' are comments
494	 * spaces before `#' are ignored in execute()
495	 */
496
497	if (*word == '#')
498	    *(word+1) = '\0';
499
500
501	for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
502		if (isprefix(word, cp->c_name))
503			return (cp);
504	return (NULL);
505}
506
507/*
508 * Determine if as1 is a valid prefix of as2.
509 * Return true if yep.
510 */
511int
512isprefix(as1, as2)
513	const char *as1, *as2;
514{
515	const char *s1, *s2;
516
517	s1 = as1;
518	s2 = as2;
519	while (*s1++ == *s2)
520		if (*s2++ == '\0')
521			return (1);
522	return (*--s1 == '\0');
523}
524
525/*
526 * The following gets called on receipt of an interrupt.  This is
527 * to abort printout of a command, mainly.
528 * Dispatching here when command() is inactive crashes rcv.
529 * Close all open files except 0, 1, 2, and the temporary.
530 * Also, unstack all source files.
531 */
532
533int	inithdr;			/* am printing startup headers */
534
535/*ARGSUSED*/
536void
537intr(s)
538	int s;
539{
540
541	noreset = 0;
542	if (!inithdr)
543		sawcom++;
544	inithdr = 0;
545	while (sourcing)
546		unstack();
547
548	close_all_files();
549
550	if (image >= 0) {
551		(void)close(image);
552		image = -1;
553	}
554	fprintf(stderr, "Interrupt\n");
555	reset(0);
556}
557
558/*
559 * When we wake up after ^Z, reprint the prompt.
560 */
561void
562stop(s)
563	int s;
564{
565	sig_t old_action = signal(s, SIG_DFL);
566	sigset_t nset;
567
568	(void)sigemptyset(&nset);
569	(void)sigaddset(&nset, s);
570	(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
571	(void)kill(0, s);
572	(void)sigprocmask(SIG_BLOCK, &nset, NULL);
573	(void)signal(s, old_action);
574	if (reset_on_stop) {
575		reset_on_stop = 0;
576		reset(0);
577	}
578}
579
580/*
581 * Branch here on hangup signal and simulate "exit".
582 */
583/*ARGSUSED*/
584void
585hangup(s)
586	int s;
587{
588
589	/* nothing to do? */
590	exit(1);
591}
592
593/*
594 * Announce the presence of the current Mail version,
595 * give the message count, and print a header listing.
596 */
597void
598announce()
599{
600	int vec[2], mdot;
601
602	mdot = newfileinfo(0);
603	vec[0] = mdot;
604	vec[1] = 0;
605	dot = &message[mdot - 1];
606	if (msgCount > 0 && value("noheader") == NULL) {
607		inithdr++;
608		headers(vec);
609		inithdr = 0;
610	}
611}
612
613/*
614 * Announce information about the file we are editing.
615 * Return a likely place to set dot.
616 */
617int
618newfileinfo(omsgCount)
619	int omsgCount;
620{
621	struct message *mp;
622	int u, n, mdot, d, s;
623	char fname[PATHSIZE+1], zname[PATHSIZE+1], *ename;
624
625	for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
626		if (mp->m_flag & MNEW)
627			break;
628	if (mp >= &message[msgCount])
629		for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++)
630			if ((mp->m_flag & MREAD) == 0)
631				break;
632	if (mp < &message[msgCount])
633		mdot = mp - &message[0] + 1;
634	else
635		mdot = omsgCount + 1;
636	s = d = 0;
637	for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
638		if (mp->m_flag & MNEW)
639			n++;
640		if ((mp->m_flag & MREAD) == 0)
641			u++;
642		if (mp->m_flag & MDELETED)
643			d++;
644		if (mp->m_flag & MSAVED)
645			s++;
646	}
647	ename = mailname;
648	if (getfold(fname, sizeof(fname) - 1) >= 0) {
649		strcat(fname, "/");
650		if (strncmp(fname, mailname, strlen(fname)) == 0) {
651			(void)snprintf(zname, sizeof(zname), "+%s",
652			    mailname + strlen(fname));
653			ename = zname;
654		}
655	}
656	printf("\"%s\": ", ename);
657	if (msgCount == 1)
658		printf("1 message");
659	else
660		printf("%d messages", msgCount);
661	if (n > 0)
662		printf(" %d new", n);
663	if (u-n > 0)
664		printf(" %d unread", u);
665	if (d > 0)
666		printf(" %d deleted", d);
667	if (s > 0)
668		printf(" %d saved", s);
669	if (readonly)
670		printf(" [Read only]");
671	printf("\n");
672	return (mdot);
673}
674
675/*
676 * Print the current version number.
677 */
678
679/*ARGSUSED*/
680int
681pversion(e)
682	int e;
683{
684
685	printf("Version %s\n", version);
686	return (0);
687}
688
689/*
690 * Load a file of user definitions.
691 */
692void
693load(name)
694	char *name;
695{
696	FILE *in, *oldin;
697
698	if ((in = Fopen(name, "r")) == NULL)
699		return;
700	oldin = input;
701	input = in;
702	loading = 1;
703	sourcing = 1;
704	commands();
705	loading = 0;
706	sourcing = 0;
707	input = oldin;
708	(void)Fclose(in);
709}
710