Fix up mix of man(7)/mdoc(7).
[netbsd-mini2440.git] / usr.bin / mail / lex.c
blobae39f1e723b16f2ca885881ebfbd6a324ab1db33
1 /* $NetBSD: lex.c,v 1.37 2009/04/10 13:08:25 christos Exp $ */
3 /*
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
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.
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.
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)lex.c 8.2 (Berkeley) 4/20/95";
36 #else
37 __RCSID("$NetBSD: lex.c,v 1.37 2009/04/10 13:08:25 christos Exp $");
38 #endif
39 #endif /* not lint */
41 #include <assert.h>
42 #include <util.h>
44 #include "rcv.h"
45 #include "extern.h"
46 #ifdef USE_EDITLINE
47 #include "complete.h"
48 #endif
49 #include "format.h"
50 #include "sig.h"
51 #include "thread.h"
54 * Mail -- a mail program
56 * Lexical processing of commands.
59 static const char *prompt = DEFAULT_PROMPT;
60 static int *msgvec;
61 static int inithdr; /* Am printing startup headers. */
62 static jmp_buf jmpbuf; /* The reset jmpbuf */
63 static int reset_on_stop; /* To do job control longjmp. */
65 #ifdef DEBUG_FILE_LEAK
66 struct glue {
67 struct glue *next;
68 int niobs;
69 FILE *iobs;
71 extern struct glue __sglue;
73 static int open_fd_cnt;
74 static int open_fp_cnt;
76 static int
77 file_count(void)
79 struct glue *gp;
80 FILE *fp;
81 int n;
82 int cnt;
84 cnt = 0;
85 for (gp = &__sglue; gp; gp = gp->next) {
86 for (fp = gp->iobs, n = gp->niobs; --n >= 0; fp++)
87 if (fp->_flags)
88 cnt++;
90 return cnt;
93 static int
94 fds_count(void)
96 int maxfd;
97 int cnt;
98 int fd;
100 maxfd = fcntl(0, F_MAXFD);
101 if (maxfd == -1) {
102 warn("fcntl");
103 return -1;
106 cnt = 0;
107 for (fd = 0; fd <= maxfd; fd++) {
108 struct stat sb;
110 if (fstat(fd, &sb) != -1)
111 cnt++;
112 else if (errno != EBADF
113 #ifdef BROKEN_CLONE_STAT /* see PRs 37878 and 37550 */
114 && errno != EOPNOTSUPP
115 #endif
117 warn("fstat(%d): errno=%d", fd, errno);
119 return cnt;
122 static void
123 file_leak_init(void)
125 open_fd_cnt = fds_count();
126 open_fp_cnt = file_count();
129 static void
130 file_leak_check(void)
132 if (open_fp_cnt != file_count() ||
133 open_fd_cnt != fds_count()) {
134 (void)printf("FILE LEAK WARNING: "
135 "fp-count: %d (%d) "
136 "fd-count: %d (%d) max-fd: %d\n",
137 file_count(), open_fp_cnt,
138 fds_count(), open_fd_cnt,
139 fcntl(0, F_MAXFD));
142 #endif /* DEBUG_FILE_LEAK */
145 * Set the size of the message vector used to construct argument
146 * lists to message list functions.
148 static void
149 setmsize(int sz)
151 if (msgvec != 0)
152 free(msgvec);
153 msgvec = ecalloc((size_t) (sz + 1), sizeof(*msgvec));
157 * Set up editing on the given file name.
158 * If the first character of name is %, we are considered to be
159 * editing the file, otherwise we are reading our mail which has
160 * signficance for mbox and so forth.
162 PUBLIC int
163 setfile(const char *name)
165 FILE *ibuf;
166 int i, fd;
167 struct stat stb;
168 char isedit = *name != '%' || getuserid(myname) != (int)getuid();
169 const char *who = name[1] ? name + 1 : myname;
170 static int shudclob;
171 char tempname[PATHSIZE];
173 if ((name = expand(name)) == NULL)
174 return -1;
176 if ((ibuf = Fopen(name, "r")) == NULL) {
177 if (!isedit && errno == ENOENT)
178 goto nomail;
179 warn("%s", name);
180 return -1;
183 if (fstat(fileno(ibuf), &stb) < 0) {
184 warn("fstat");
185 (void)Fclose(ibuf);
186 return -1;
189 switch (stb.st_mode & S_IFMT) {
190 case S_IFDIR:
191 (void)Fclose(ibuf);
192 errno = EISDIR;
193 warn("%s", name);
194 return -1;
196 case S_IFREG:
197 break;
199 default:
200 (void)Fclose(ibuf);
201 errno = EINVAL;
202 warn("%s", name);
203 return -1;
207 * Looks like all will be well. We must now relinquish our
208 * hold on the current set of stuff. Must hold signals
209 * while we are reading the new file, else we will ruin
210 * the message[] data structure.
213 sig_check();
214 sig_hold();
215 if (shudclob)
216 quit(jmpbuf);
219 * Copy the messages into /tmp
220 * and set pointers.
223 readonly = 0;
224 if ((i = open(name, O_WRONLY)) < 0)
225 readonly++;
226 else
227 (void)close(i);
228 if (shudclob) {
229 (void)fclose(itf);
230 (void)fclose(otf);
232 shudclob = 1;
233 edit = isedit;
234 (void)strcpy(prevfile, mailname);
235 if (name != mailname)
236 (void)strcpy(mailname, name);
237 mailsize = fsize(ibuf);
238 (void)snprintf(tempname, sizeof(tempname),
239 "%s/mail.RxXXXXXXXXXX", tmpdir);
240 if ((fd = mkstemp(tempname)) == -1 ||
241 (otf = fdopen(fd, "w")) == NULL)
242 err(1, "%s", tempname);
243 (void)fcntl(fileno(otf), F_SETFD, FD_CLOEXEC);
244 if ((itf = fopen(tempname, "r")) == NULL)
245 err(1, "%s", tempname);
246 (void)fcntl(fileno(itf), F_SETFD, FD_CLOEXEC);
247 (void)rm(tempname);
248 setptr(ibuf, (off_t)0);
249 setmsize(get_abs_msgCount());
251 * New mail may have arrived while we were reading
252 * the mail file, so reset mailsize to be where
253 * we really are in the file...
255 mailsize = ftell(ibuf);
256 (void)Fclose(ibuf);
257 sig_release();
258 sig_check();
259 sawcom = 0;
260 if (!edit && get_abs_msgCount() == 0) {
261 nomail:
262 (void)fprintf(stderr, "No mail for %s\n", who);
263 return -1;
265 return 0;
269 * Incorporate any new mail that has arrived since we first
270 * started reading mail.
272 PUBLIC int
273 incfile(void)
275 off_t newsize;
276 int omsgCount;
277 FILE *ibuf;
278 int rval;
280 omsgCount = get_abs_msgCount();
282 ibuf = Fopen(mailname, "r");
283 if (ibuf == NULL)
284 return -1;
285 sig_check();
286 sig_hold();
287 newsize = fsize(ibuf);
288 if (newsize == 0 || /* mail box is now empty??? */
289 newsize < mailsize) { /* mail box has shrunk??? */
290 rval = -1;
291 goto done;
293 if (newsize == mailsize) {
294 rval = 0; /* no new mail */
295 goto done;
297 setptr(ibuf, mailsize); /* read in new mail */
298 setmsize(get_abs_msgCount()); /* get the new message count */
299 mailsize = ftell(ibuf);
300 rval = get_abs_msgCount() - omsgCount;
301 done:
302 (void)Fclose(ibuf);
303 sig_release();
304 sig_check();
305 return rval;
309 * Return a pointer to the comment character, respecting quoting as
310 * done in getrawlist(). The comment character is ignored inside
311 * quotes.
313 static char *
314 comment_char(char *line)
316 char *p;
317 char quotec;
318 quotec = '\0';
319 for (p = line; *p; p++) {
320 if (quotec != '\0') {
321 if (*p == quotec)
322 quotec = '\0';
324 else if (*p == '"' || *p == '\'')
325 quotec = *p;
326 else if (*p == COMMENT_CHAR)
327 return p;
329 return NULL;
333 * Signal handler is hooked by setup_piping().
334 * Respond to a broken pipe signal --
335 * probably caused by quitting more.
337 static jmp_buf pipestop;
339 /*ARGSUSED*/
340 static void
341 lex_brokpipe(int signo)
344 longjmp(pipestop, signo);
348 * Check the command line for any requested piping or redirection,
349 * depending on the value of 'c'. If "enable-pipes" is set, search
350 * the command line (cp) for the first occurrence of the character 'c'
351 * that is not in a quote or (parenthese) group.
353 PUBLIC char *
354 shellpr(char *cp)
356 int quotec;
357 int level;
359 if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL)
360 return NULL;
362 level = 0;
363 quotec = 0;
364 for (/*EMPTY*/; *cp != '\0'; cp++) {
365 if (quotec) {
366 if (*cp == quotec)
367 quotec = 0;
368 if (*cp == '\\' &&
369 (cp[1] == quotec || cp[1] == '\\'))
370 cp++;
372 else {
373 switch (*cp) {
374 case '|':
375 case '>':
376 if (level == 0)
377 return cp;
378 break;
379 case '(':
380 level++;
381 break;
382 case ')':
383 level--;
384 break;
385 case '"':
386 case '\'':
387 quotec = *cp;
388 break;
389 default:
390 break;
394 return NULL;
397 static int
398 do_paging(const char *cmd, int c_pipe)
400 char *cp, *p;
402 if (value(ENAME_PAGER_OFF) != NULL)
403 return 0;
405 if (c_pipe & C_PIPE_PAGER)
406 return 1;
408 if (c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL)
409 return 1;
411 if ((cp = value(ENAME_PAGE_ALSO)) == NULL)
412 return 0;
414 if ((p = strcasestr(cp, cmd)) == NULL)
415 return 0;
417 if (p != cp && p[-1] != ',' && !is_WSP(p[-1]))
418 return 0;
420 p += strlen(cmd);
422 return (*p == '\0' || *p == ',' || is_WSP(*p));
426 * Setup any pipe or redirection that the command line indicates.
427 * If none, then setup the pager unless "pager-off" is defined.
429 static FILE *fp_stop = NULL;
430 static int oldfd1 = -1;
431 static sig_t old_sigpipe;
433 static int
434 setup_piping(const char *cmd, char *cmdline, int c_pipe)
436 FILE *fout;
437 FILE *last_file;
438 char *cp;
440 sig_check();
442 last_file = last_registered_file(0);
444 fout = NULL;
445 if ((cp = shellpr(cmdline)) != NULL) {
446 char c;
447 c = *cp;
448 *cp = '\0';
449 cp++;
451 if (c == '|') {
452 if ((fout = Popen(cp, "w")) == NULL) {
453 warn("Popen: %s", cp);
454 return -1;
457 else {
458 const char *mode;
459 assert(c == '>');
460 mode = *cp == '>' ? "a" : "w";
461 if (*cp == '>')
462 cp++;
464 cp = skip_WSP(cp);
465 if ((fout = Fopen(cp, mode)) == NULL) {
466 warn("Fopen: %s", cp);
467 return -1;
472 else if (do_paging(cmd, c_pipe)) {
473 const char *pager;
474 pager = value(ENAME_PAGER);
475 if (pager == NULL || *pager == '\0')
476 pager = _PATH_MORE;
478 if ((fout = Popen(pager, "w")) == NULL) {
479 warn("Popen: %s", pager);
480 return -1;
484 if (fout) {
485 old_sigpipe = sig_signal(SIGPIPE, lex_brokpipe);
486 (void)fflush(stdout);
487 if ((oldfd1 = dup(STDOUT_FILENO)) == -1)
488 err(EXIT_FAILURE, "dup failed");
489 if (dup2(fileno(fout), STDOUT_FILENO) == -1)
490 err(EXIT_FAILURE, "dup2 failed");
491 fp_stop = last_file;
493 return 0;
497 * This will close any piping started by setup_piping().
499 static void
500 close_piping(void)
502 sigset_t oset;
503 struct sigaction osa;
505 if (oldfd1 != -1) {
506 (void)fflush(stdout);
507 if (fileno(stdout) != oldfd1 &&
508 dup2(oldfd1, STDOUT_FILENO) == -1)
509 err(EXIT_FAILURE, "dup2 failed");
511 (void)sig_ignore(SIGPIPE, &osa, &oset);
513 close_top_files(fp_stop);
514 fp_stop = NULL;
515 (void)close(oldfd1);
516 oldfd1 = -1;
518 (void)sig_signal(SIGPIPE, old_sigpipe);
519 (void)sig_restore(SIGPIPE, &osa, &oset);
521 sig_check();
525 * Determine if as1 is a valid prefix of as2.
526 * Return true if yep.
528 static int
529 isprefix(char *as1, const char *as2)
531 char *s1;
532 const char *s2;
534 s1 = as1;
535 s2 = as2;
536 while (*s1++ == *s2)
537 if (*s2++ == '\0')
538 return 1;
539 return *--s1 == '\0';
543 * Find the correct command in the command table corresponding
544 * to the passed command "word"
546 PUBLIC const struct cmd *
547 lex(char word[])
549 const struct cmd *cp;
551 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
552 if (isprefix(word, cp->c_name))
553 return cp;
554 return NULL;
557 PUBLIC char *
558 get_cmdname(char *buf)
560 char *cp;
561 char *cmd;
562 size_t len;
564 for (cp = buf; *cp; cp++)
565 if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
566 break;
567 /* XXX - Don't miss the pipe command! */
568 if (cp == buf && *cp == '|')
569 cp++;
570 len = cp - buf + 1;
571 cmd = salloc(len);
572 (void)strlcpy(cmd, buf, len);
573 return cmd;
577 * Execute a single command.
578 * Command functions return 0 for success, 1 for error, and -1
579 * for abort. A 1 or -1 aborts a load or source. A -1 aborts
580 * the interactive command loop.
581 * execute_contxt_e is in extern.h.
583 PUBLIC int
584 execute(char linebuf[], enum execute_contxt_e contxt)
586 char *word;
587 char *arglist[MAXARGC];
588 const struct cmd * volatile com = NULL;
589 char *volatile cp;
590 int retval;
591 int c;
592 int e = 1;
595 * Strip the white space away from the beginning
596 * of the command, then scan out a word, which
597 * consists of anything except digits and white space.
599 * Handle ! escapes differently to get the correct
600 * lexical conventions.
603 cp = skip_space(linebuf);
604 if (*cp == '!') {
605 if (sourcing) {
606 (void)printf("Can't \"!\" while sourcing\n");
607 goto out;
609 (void)shell(cp + 1);
610 return 0;
613 word = get_cmdname(cp);
614 cp += strlen(word);
617 * Look up the command; if not found, bitch.
618 * Normally, a blank command would map to the
619 * first command in the table; while sourcing,
620 * however, we ignore blank lines to eliminate
621 * confusion.
624 if (sourcing && *word == '\0')
625 return 0;
626 com = lex(word);
627 if (com == NULL) {
628 (void)printf("Unknown command: \"%s\"\n", word);
629 goto out;
633 * See if we should execute the command -- if a conditional
634 * we always execute it, otherwise, check the state of cond.
637 if ((com->c_argtype & F) == 0 && (cond & CSKIP))
638 return 0;
641 * Process the arguments to the command, depending
642 * on the type he expects. Default to an error.
643 * If we are sourcing an interactive command, it's
644 * an error.
647 if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
648 (void)printf("May not execute \"%s\" while sending\n",
649 com->c_name);
650 goto out;
652 if (sourcing && com->c_argtype & I) {
653 (void)printf("May not execute \"%s\" while sourcing\n",
654 com->c_name);
655 goto out;
657 if (readonly && com->c_argtype & W) {
658 (void)printf("May not execute \"%s\" -- message file is read only\n",
659 com->c_name);
660 goto out;
662 if (contxt == ec_composing && com->c_argtype & R) {
663 (void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
664 goto out;
667 if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
669 sig_check();
670 if (setjmp(pipestop))
671 goto out;
673 if (setup_piping(com->c_name, cp, com->c_pipe) == -1)
674 goto out;
676 switch (com->c_argtype & ARGTYPE_MASK) {
677 case MSGLIST:
679 * A message list defaulting to nearest forward
680 * legal message.
682 if (msgvec == 0) {
683 (void)printf("Illegal use of \"message list\"\n");
684 break;
686 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
687 break;
688 if (c == 0) {
689 *msgvec = first(com->c_msgflag, com->c_msgmask);
690 msgvec[1] = 0;
692 if (*msgvec == 0) {
693 (void)printf("No applicable messages\n");
694 break;
696 e = (*com->c_func)(msgvec);
697 break;
699 case NDMLIST:
701 * A message list with no defaults, but no error
702 * if none exist.
704 if (msgvec == 0) {
705 (void)printf("Illegal use of \"message list\"\n");
706 break;
708 if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
709 break;
710 e = (*com->c_func)(msgvec);
711 break;
713 case STRLIST:
715 * Just the straight string, with
716 * leading blanks removed.
718 cp = skip_space(cp);
719 e = (*com->c_func)(cp);
720 break;
722 case RAWLIST:
724 * A vector of strings, in shell style.
726 if ((c = getrawlist(cp, arglist, (int)__arraycount(arglist))) < 0)
727 break;
728 if (c < com->c_minargs) {
729 (void)printf("%s requires at least %d arg(s)\n",
730 com->c_name, com->c_minargs);
731 break;
733 if (c > com->c_maxargs) {
734 (void)printf("%s takes no more than %d arg(s)\n",
735 com->c_name, com->c_maxargs);
736 break;
738 e = (*com->c_func)(arglist);
739 break;
741 case NOLIST:
743 * Just the constant zero, for exiting,
744 * eg.
746 e = (*com->c_func)(0);
747 break;
749 default:
750 errx(1, "Unknown argtype");
753 out:
754 close_piping();
757 * Exit the current source file on
758 * error.
760 retval = 0;
761 if (e) {
762 if (e < 0)
763 retval = 1;
764 else if (loading)
765 retval = 1;
766 else if (sourcing)
767 (void)unstack();
769 else if (com != NULL) {
770 if (contxt != ec_autoprint && com->c_argtype & P &&
771 value(ENAME_AUTOPRINT) != NULL &&
772 (dot->m_flag & MDELETED) == 0)
773 (void)execute(__UNCONST("print ."), ec_autoprint);
774 if (!sourcing && (com->c_argtype & T) == 0)
775 sawcom = 1;
777 sig_check();
778 return retval;
782 * The following gets called on receipt of an interrupt. This is
783 * to abort printout of a command, mainly.
784 * Dispatching here when commands() is inactive crashes rcv.
785 * Close all open files except 0, 1, 2, and the temporary.
786 * Also, unstack all source files.
788 static void
789 lex_intr(int signo)
792 noreset = 0;
793 if (!inithdr)
794 sawcom++;
795 inithdr = 0;
796 while (sourcing)
797 (void)unstack();
799 close_piping();
800 close_all_files();
802 if (image >= 0) {
803 (void)close(image);
804 image = -1;
806 (void)fprintf(stderr, "Interrupt\n");
807 longjmp(jmpbuf, signo);
811 * Branch here on hangup signal and simulate "exit".
813 /*ARGSUSED*/
814 static void
815 lex_hangup(int s __unused)
818 /* nothing to do? */
819 exit(EXIT_FAILURE);
823 * When we wake up after ^Z, reprint the prompt.
825 * NOTE: EditLine deals with the prompt and job control, so with it
826 * this does nothing, i.e., reset_on_stop == 0.
828 static void
829 lex_stop(int signo)
832 if (reset_on_stop) {
833 reset_on_stop = 0;
834 longjmp(jmpbuf, signo);
839 * Interpret user commands one by one. If standard input is not a tty,
840 * print no prompt.
842 PUBLIC void
843 commands(void)
845 int n;
846 char linebuf[LINESIZE];
847 int eofloop;
849 #ifdef DEBUG_FILE_LEAK
850 file_leak_init();
851 #endif
853 if (!sourcing) {
854 sig_check();
856 sig_hold();
857 (void)sig_signal(SIGINT, lex_intr);
858 (void)sig_signal(SIGHUP, lex_hangup);
859 (void)sig_signal(SIGTSTP, lex_stop);
860 (void)sig_signal(SIGTTOU, lex_stop);
861 (void)sig_signal(SIGTTIN, lex_stop);
862 sig_release();
865 (void)setjmp(jmpbuf); /* "reset" location if we got an interrupt */
867 eofloop = 0; /* initialize this after a possible longjmp */
868 for (;;) {
869 sig_check();
870 (void)fflush(stdout);
871 sreset();
873 * Print the prompt, if needed. Clear out
874 * string space, and flush the output.
876 if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
877 if ((prompt = value(ENAME_PROMPT)) == NULL)
878 prompt = DEFAULT_PROMPT;
879 prompt = smsgprintf(prompt, dot);
880 if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
881 (void)printf("New mail has arrived.\n");
883 #ifndef USE_EDITLINE
884 reset_on_stop = 1; /* enable job control longjmp */
885 (void)printf("%s", prompt);
886 #endif
888 #ifdef DEBUG_FILE_LEAK
889 file_leak_check();
890 #endif
892 * Read a line of commands from the current input
893 * and handle end of file specially.
895 n = 0;
896 for (;;) {
897 sig_check();
898 #ifdef USE_EDITLINE
899 if (!sourcing) {
900 char *line;
902 line = my_gets(&elm.command, prompt, NULL);
903 if (line == NULL) {
904 if (n == 0)
905 n = -1;
906 break;
908 (void)strlcpy(linebuf, line, sizeof(linebuf));
910 else {
911 if (readline(input, &linebuf[n], LINESIZE - n, 0) < 0) {
912 if (n == 0)
913 n = -1;
914 break;
917 #else /* USE_EDITLINE */
918 if (readline(input, &linebuf[n], LINESIZE - n, reset_on_stop) < 0) {
919 if (n == 0)
920 n = -1;
921 break;
923 #endif /* USE_EDITLINE */
924 if (!sourcing)
925 setscreensize(); /* so we can resize window */
927 if (sourcing) { /* allow comments in source files */
928 char *ptr;
929 if ((ptr = comment_char(linebuf)) != NULL)
930 *ptr = '\0';
932 if ((n = (int)strlen(linebuf)) == 0)
933 break;
934 n--;
935 if (linebuf[n] != '\\')
936 break;
937 linebuf[n++] = ' ';
939 #ifndef USE_EDITLINE
940 sig_check();
941 reset_on_stop = 0; /* disable job control longjmp */
942 #endif
943 if (n < 0) {
944 char *p;
946 /* eof */
947 if (loading)
948 break;
949 if (sourcing) {
950 (void)unstack();
951 continue;
953 if (value(ENAME_INTERACTIVE) != NULL &&
954 (p = value(ENAME_IGNOREEOF)) != NULL &&
955 ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
956 (void)printf("Use \"quit\" to quit.\n");
957 continue;
959 break;
961 eofloop = 0;
962 if (execute(linebuf, ec_normal))
963 break;
968 * Announce information about the file we are editing.
969 * Return a likely place to set dot.
971 PUBLIC int
972 newfileinfo(int omsgCount)
974 struct message *mp;
975 int d, n, s, t, u, mdot;
976 char fname[PATHSIZE];
977 char *ename;
980 * Figure out where to set the 'dot'. Use the first new or
981 * unread message.
983 for (mp = get_abs_message(omsgCount + 1); mp;
984 mp = next_abs_message(mp))
985 if (mp->m_flag & MNEW)
986 break;
988 if (mp == NULL)
989 for (mp = get_abs_message(omsgCount + 1); mp;
990 mp = next_abs_message(mp))
991 if ((mp->m_flag & MREAD) == 0)
992 break;
993 if (mp != NULL)
994 mdot = get_msgnum(mp);
995 else
996 mdot = omsgCount + 1;
997 #ifdef THREAD_SUPPORT
999 * See if the message is in the current thread.
1001 if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
1002 mdot = 0;
1003 #endif
1005 * Scan the message array counting the new, unread, deleted,
1006 * and saved messages.
1008 d = n = s = t = u = 0;
1009 for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
1010 if (mp->m_flag & MNEW)
1011 n++;
1012 if ((mp->m_flag & MREAD) == 0)
1013 u++;
1014 if (mp->m_flag & MDELETED)
1015 d++;
1016 if (mp->m_flag & MSAVED)
1017 s++;
1018 if (mp->m_flag & MTAGGED)
1019 t++;
1021 ename = mailname;
1022 if (getfold(fname, sizeof(fname)) >= 0) {
1023 char zname[PATHSIZE];
1024 size_t l;
1025 l = strlen(fname);
1026 if (l < sizeof(fname) - 1)
1027 fname[l++] = '/';
1028 if (strncmp(fname, mailname, l) == 0) {
1029 (void)snprintf(zname, sizeof(zname), "+%s",
1030 mailname + l);
1031 ename = zname;
1035 * Display the statistics.
1037 (void)printf("\"%s\": ", ename);
1039 int cnt = get_abs_msgCount();
1040 (void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
1042 if (n > 0)
1043 (void)printf(" %d new", n);
1044 if (u-n > 0)
1045 (void)printf(" %d unread", u);
1046 if (t > 0)
1047 (void)printf(" %d tagged", t);
1048 if (d > 0)
1049 (void)printf(" %d deleted", d);
1050 if (s > 0)
1051 (void)printf(" %d saved", s);
1052 if (readonly)
1053 (void)printf(" [Read only]");
1054 (void)printf("\n");
1056 return mdot;
1060 * Announce the presence of the current Mail version,
1061 * give the message count, and print a header listing.
1063 PUBLIC void
1064 announce(void)
1066 int vec[2], mdot;
1068 mdot = newfileinfo(0);
1069 vec[0] = mdot;
1070 vec[1] = 0;
1071 if ((dot = get_message(mdot)) == NULL)
1072 dot = get_abs_message(1); /* make sure we get something! */
1073 if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
1074 inithdr++;
1075 (void)headers(vec);
1076 inithdr = 0;
1081 * Print the current version number.
1084 /*ARGSUSED*/
1085 PUBLIC int
1086 pversion(void *v __unused)
1088 (void)printf("Version %s\n", version);
1089 return 0;
1093 * Load a file of user definitions.
1095 PUBLIC void
1096 load(const char *name)
1098 FILE *in, *oldin;
1100 if ((in = Fopen(name, "r")) == NULL)
1101 return;
1102 oldin = input;
1103 input = in;
1104 loading = 1;
1105 sourcing = 1;
1106 commands();
1107 loading = 0;
1108 sourcing = 0;
1109 input = oldin;
1110 (void)Fclose(in);