lex.c revision 1.33.4.2 1 /* $NetBSD: lex.c,v 1.33.4.2 2008/01/09 02:00:46 matt Exp $ */
2
3 /*
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
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.
18 *
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.
30 */
31
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.33.4.2 2008/01/09 02:00:46 matt Exp $");
38 #endif
39 #endif /* not lint */
40
41 #include <assert.h>
42
43 #include "rcv.h"
44 #include <util.h>
45 #include "extern.h"
46 #ifdef USE_EDITLINE
47 #include "complete.h"
48 #endif
49 #include "format.h"
50 #include "thread.h"
51
52 /*
53 * Mail -- a mail program
54 *
55 * Lexical processing of commands.
56 */
57
58 static const char *prompt = DEFAULT_PROMPT;
59 static int *msgvec;
60 static int reset_on_stop; /* do a reset() if stopped */
61
62
63 /*
64 * Set the size of the message vector used to construct argument
65 * lists to message list functions.
66 */
67 static void
68 setmsize(int sz)
69 {
70 if (msgvec != 0)
71 free(msgvec);
72 msgvec = ecalloc((size_t) (sz + 1), sizeof(*msgvec));
73 }
74
75 /*
76 * Set up editing on the given file name.
77 * If the first character of name is %, we are considered to be
78 * editing the file, otherwise we are reading our mail which has
79 * signficance for mbox and so forth.
80 */
81 PUBLIC int
82 setfile(const char *name)
83 {
84 FILE *ibuf;
85 int i, fd;
86 struct stat stb;
87 char isedit = *name != '%' || getuserid(myname) != (int)getuid();
88 const char *who = name[1] ? name + 1 : myname;
89 static int shudclob;
90 char tempname[PATHSIZE];
91
92 if ((name = expand(name)) == NULL)
93 return -1;
94
95 if ((ibuf = Fopen(name, "r")) == NULL) {
96 if (!isedit && errno == ENOENT)
97 goto nomail;
98 warn("%s", name);
99 return -1;
100 }
101
102 if (fstat(fileno(ibuf), &stb) < 0) {
103 warn("fstat");
104 (void)Fclose(ibuf);
105 return -1;
106 }
107
108 switch (stb.st_mode & S_IFMT) {
109 case S_IFDIR:
110 (void)Fclose(ibuf);
111 errno = EISDIR;
112 warn("%s", name);
113 return -1;
114
115 case S_IFREG:
116 break;
117
118 default:
119 (void)Fclose(ibuf);
120 errno = EINVAL;
121 warn("%s", name);
122 return -1;
123 }
124
125 /*
126 * Looks like all will be well. We must now relinquish our
127 * hold on the current set of stuff. Must hold signals
128 * while we are reading the new file, else we will ruin
129 * the message[] data structure.
130 */
131
132 holdsigs();
133 if (shudclob)
134 quit();
135
136 /*
137 * Copy the messages into /tmp
138 * and set pointers.
139 */
140
141 readonly = 0;
142 if ((i = open(name, O_WRONLY)) < 0)
143 readonly++;
144 else
145 (void)close(i);
146 if (shudclob) {
147 (void)fclose(itf);
148 (void)fclose(otf);
149 }
150 shudclob = 1;
151 edit = isedit;
152 (void)strcpy(prevfile, mailname);
153 if (name != mailname)
154 (void)strcpy(mailname, name);
155 mailsize = fsize(ibuf);
156 (void)snprintf(tempname, sizeof(tempname),
157 "%s/mail.RxXXXXXXXXXX", tmpdir);
158 if ((fd = mkstemp(tempname)) == -1 ||
159 (otf = fdopen(fd, "w")) == NULL)
160 err(1, "%s", tempname);
161 (void)fcntl(fileno(otf), F_SETFD, FD_CLOEXEC);
162 if ((itf = fopen(tempname, "r")) == NULL)
163 err(1, "%s", tempname);
164 (void)fcntl(fileno(itf), F_SETFD, FD_CLOEXEC);
165 (void)rm(tempname);
166 setptr(ibuf, (off_t)0);
167 setmsize(get_abs_msgCount());
168 /*
169 * New mail may have arrived while we were reading
170 * the mail file, so reset mailsize to be where
171 * we really are in the file...
172 */
173 mailsize = ftell(ibuf);
174 (void)Fclose(ibuf);
175 relsesigs();
176 sawcom = 0;
177 if (!edit && get_abs_msgCount() == 0) {
178 nomail:
179 (void)fprintf(stderr, "No mail for %s\n", who);
180 return -1;
181 }
182 return 0;
183 }
184
185 /*
186 * Incorporate any new mail that has arrived since we first
187 * started reading mail.
188 */
189 PUBLIC int
190 incfile(void)
191 {
192 off_t newsize;
193 int omsgCount;
194 FILE *ibuf;
195 int rval;
196
197 omsgCount = get_abs_msgCount();
198
199 ibuf = Fopen(mailname, "r");
200 if (ibuf == NULL)
201 return -1;
202 holdsigs();
203 newsize = fsize(ibuf);
204 if (newsize == 0 || /* mail box is now empty??? */
205 newsize < mailsize) { /* mail box has shrunk??? */
206 rval = -1;
207 goto done;
208 }
209 if (newsize == mailsize) {
210 rval = 0; /* no new mail */
211 goto done;
212 }
213 setptr(ibuf, mailsize); /* read in new mail */
214 setmsize(get_abs_msgCount()); /* get the new message count */
215 mailsize = ftell(ibuf);
216 rval = get_abs_msgCount() - omsgCount;
217 done:
218 (void)Fclose(ibuf);
219 relsesigs();
220 return rval;
221 }
222
223 /*
224 * Return a pointer to the comment character, respecting quoting as
225 * done in getrawlist(). The comment character is ignored inside
226 * quotes.
227 */
228 static char *
229 comment_char(char *line)
230 {
231 char *p;
232 char quotec;
233 quotec = '\0';
234 for (p = line; *p; p++) {
235 if (quotec != '\0') {
236 if (*p == quotec)
237 quotec = '\0';
238 }
239 else if (*p == '"' || *p == '\'')
240 quotec = *p;
241 else if (*p == COMMENT_CHAR)
242 return p;
243 }
244 return NULL;
245 }
246
247 /*
248 * When we wake up after ^Z, reprint the prompt.
249 */
250 static void
251 stop(int s)
252 {
253 sig_t old_action = signal(s, SIG_DFL);
254 sigset_t nset;
255
256 (void)sigemptyset(&nset);
257 (void)sigaddset(&nset, s);
258 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
259 (void)kill(0, s);
260 (void)sigprocmask(SIG_BLOCK, &nset, NULL);
261 (void)signal(s, old_action);
262 if (reset_on_stop) {
263 reset_on_stop = 0;
264 reset(0);
265 }
266 }
267
268
269
270 /*
271 * Signal handler is hooked by setup_piping().
272 * Respond to a broken pipe signal --
273 * probably caused by quitting more.
274 */
275 static jmp_buf pipestop;
276
277 /*ARGSUSED*/
278 static void
279 brokpipe(int signo __unused)
280 {
281 longjmp(pipestop, 1);
282 }
283
284 /*
285 * Check the command line for any requested piping or redirection,
286 * depending on the value of 'c'. If "enable-pipes" is set, search
287 * the command line (cp) for the first occurrence of the character 'c'
288 * that is not in a quote or (parenthese) group.
289 */
290 PUBLIC char *
291 shellpr(char *cp)
292 {
293 int quotec;
294 int level;
295
296 if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL)
297 return NULL;
298
299 level = 0;
300 quotec = 0;
301 for (/*EMPTY*/; *cp != '\0'; cp++) {
302 if (quotec) {
303 if (*cp == quotec)
304 quotec = 0;
305 if (*cp == '\\' &&
306 (cp[1] == quotec || cp[1] == '\\'))
307 cp++;
308 }
309 else {
310 switch (*cp) {
311 case '|':
312 case '>':
313 if (level == 0)
314 return cp;
315 break;
316 case '(':
317 level++;
318 break;
319 case ')':
320 level--;
321 break;
322 case '"':
323 case '\'':
324 quotec = *cp;
325 break;
326 default:
327 break;
328 }
329 }
330 }
331 return NULL;
332 }
333
334 /*
335 * Setup any pipe or redirection that the command line indicates.
336 * If none, then setup the pager unless "pager-off" is defined.
337 */
338 static FILE *fp_stop = NULL;
339 static int oldfd1 = -1;
340 static int
341 setup_piping(char *cmdline, int c_pipe)
342 {
343 FILE *fout;
344 FILE *last_file;
345 char *cp;
346
347 last_file = last_registered_file(0);
348
349 fout = NULL;
350 if ((cp = shellpr(cmdline)) != NULL) {
351 char c;
352 c = *cp;
353 *cp = '\0';
354 cp++;
355
356 if (c == '|') {
357 if ((fout = Popen(cp, "w")) == NULL) {
358 warn("Popen: %s", cp);
359 return -1;
360 }
361 }
362 else {
363 const char *mode;
364 assert(c == '>');
365 mode = *cp == '>' ? "a" : "w";
366 if (*cp == '>')
367 cp++;
368
369 cp = skip_WSP(cp);
370 if ((fout = Fopen(cp, mode)) == NULL) {
371 warn("Fopen: %s", cp);
372 return -1;
373 }
374 }
375
376 }
377 else if (value(ENAME_PAGER_OFF) == NULL && (c_pipe & C_PIPE_PAGER ||
378 (c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL))) {
379 const char *pager;
380 pager = value(ENAME_PAGER);
381 if (pager == NULL || *pager == '\0')
382 pager = _PATH_MORE;
383
384 if ((fout = Popen(pager, "w")) == NULL) {
385 warn("Popen: %s", pager);
386 return -1;
387 }
388 }
389
390 if (fout) {
391 (void)signal(SIGPIPE, brokpipe);
392 (void)fflush(stdout);
393 if ((oldfd1 = dup(STDOUT_FILENO)) == -1)
394 err(EXIT_FAILURE, "dup failed");
395 if (dup2(fileno(fout), STDOUT_FILENO) == -1)
396 err(EXIT_FAILURE, "dup2 failed");
397 fp_stop = last_file;
398 }
399 return 0;
400 }
401
402 /*
403 * This will close any piping started by setup_piping().
404 */
405 static void
406 close_piping(void)
407 {
408 if (oldfd1 != -1) {
409 (void)fflush(stdout);
410 if (fileno(stdout) != oldfd1 &&
411 dup2(oldfd1, STDOUT_FILENO) == -1)
412 err(EXIT_FAILURE, "dup2 failed");
413
414 (void)signal(SIGPIPE, SIG_IGN);
415 close_top_files(fp_stop);
416 fp_stop = NULL;
417 (void)close(oldfd1);
418 oldfd1 = -1;
419 (void)signal(SIGPIPE, SIG_DFL);
420 }
421 }
422
423 /*
424 * Determine if as1 is a valid prefix of as2.
425 * Return true if yep.
426 */
427 static int
428 isprefix(char *as1, const char *as2)
429 {
430 char *s1;
431 const char *s2;
432
433 s1 = as1;
434 s2 = as2;
435 while (*s1++ == *s2)
436 if (*s2++ == '\0')
437 return 1;
438 return *--s1 == '\0';
439 }
440
441 /*
442 * Find the correct command in the command table corresponding
443 * to the passed command "word"
444 */
445 PUBLIC const struct cmd *
446 lex(char word[])
447 {
448 const struct cmd *cp;
449
450 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
451 if (isprefix(word, cp->c_name))
452 return cp;
453 return NULL;
454 }
455
456 PUBLIC char *
457 get_cmdname(char *buf)
458 {
459 char *cp;
460 char *cmd;
461 size_t len;
462
463 for (cp = buf; *cp; cp++)
464 if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
465 break;
466 /* XXX - Don't miss the pipe command! */
467 if (cp == buf && *cp == '|')
468 cp++;
469 len = cp - buf + 1;
470 cmd = salloc(len);
471 (void)strlcpy(cmd, buf, len);
472 return cmd;
473 }
474
475 /*
476 * Execute a single command.
477 * Command functions return 0 for success, 1 for error, and -1
478 * for abort. A 1 or -1 aborts a load or source. A -1 aborts
479 * the interactive command loop.
480 * execute_contxt_e is in extern.h.
481 */
482 PUBLIC int
483 execute(char linebuf[], enum execute_contxt_e contxt)
484 {
485 char *word;
486 char *arglist[MAXARGC];
487 const struct cmd *com = NULL;
488 char *volatile cp;
489 int c;
490 int e = 1;
491
492 /*
493 * Strip the white space away from the beginning
494 * of the command, then scan out a word, which
495 * consists of anything except digits and white space.
496 *
497 * Handle ! escapes differently to get the correct
498 * lexical conventions.
499 */
500
501 cp = skip_space(linebuf);
502 if (*cp == '!') {
503 if (sourcing) {
504 (void)printf("Can't \"!\" while sourcing\n");
505 goto out;
506 }
507 (void)shell(cp + 1);
508 return 0;
509 }
510
511 word = get_cmdname(cp);
512 cp += strlen(word);
513
514 /*
515 * Look up the command; if not found, bitch.
516 * Normally, a blank command would map to the
517 * first command in the table; while sourcing,
518 * however, we ignore blank lines to eliminate
519 * confusion.
520 */
521
522 if (sourcing && *word == '\0')
523 return 0;
524 com = lex(word);
525 if (com == NULL) {
526 (void)printf("Unknown command: \"%s\"\n", word);
527 goto out;
528 }
529
530 /*
531 * See if we should execute the command -- if a conditional
532 * we always execute it, otherwise, check the state of cond.
533 */
534
535 if ((com->c_argtype & F) == 0 && (cond & CSKIP))
536 return 0;
537
538 /*
539 * Process the arguments to the command, depending
540 * on the type he expects. Default to an error.
541 * If we are sourcing an interactive command, it's
542 * an error.
543 */
544
545 if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
546 (void)printf("May not execute \"%s\" while sending\n",
547 com->c_name);
548 goto out;
549 }
550 if (sourcing && com->c_argtype & I) {
551 (void)printf("May not execute \"%s\" while sourcing\n",
552 com->c_name);
553 goto out;
554 }
555 if (readonly && com->c_argtype & W) {
556 (void)printf("May not execute \"%s\" -- message file is read only\n",
557 com->c_name);
558 goto out;
559 }
560 if (contxt == ec_composing && com->c_argtype & R) {
561 (void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
562 goto out;
563 }
564
565 if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
566 if (setjmp(pipestop))
567 goto out;
568
569 if (setup_piping(cp, com->c_pipe) == -1)
570 goto out;
571 }
572 switch (com->c_argtype & ARGTYPE_MASK) {
573 case MSGLIST:
574 /*
575 * A message list defaulting to nearest forward
576 * legal message.
577 */
578 if (msgvec == 0) {
579 (void)printf("Illegal use of \"message list\"\n");
580 break;
581 }
582 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
583 break;
584 if (c == 0) {
585 *msgvec = first(com->c_msgflag, com->c_msgmask);
586 msgvec[1] = 0;
587 }
588 if (*msgvec == 0) {
589 (void)printf("No applicable messages\n");
590 break;
591 }
592 e = (*com->c_func)(msgvec);
593 break;
594
595 case NDMLIST:
596 /*
597 * A message list with no defaults, but no error
598 * if none exist.
599 */
600 if (msgvec == 0) {
601 (void)printf("Illegal use of \"message list\"\n");
602 break;
603 }
604 if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
605 break;
606 e = (*com->c_func)(msgvec);
607 break;
608
609 case STRLIST:
610 /*
611 * Just the straight string, with
612 * leading blanks removed.
613 */
614 cp = skip_space(cp);
615 e = (*com->c_func)(cp);
616 break;
617
618 case RAWLIST:
619 /*
620 * A vector of strings, in shell style.
621 */
622 if ((c = getrawlist(cp, arglist,
623 sizeof(arglist) / sizeof(*arglist))) < 0)
624 break;
625 if (c < com->c_minargs) {
626 (void)printf("%s requires at least %d arg(s)\n",
627 com->c_name, com->c_minargs);
628 break;
629 }
630 if (c > com->c_maxargs) {
631 (void)printf("%s takes no more than %d arg(s)\n",
632 com->c_name, com->c_maxargs);
633 break;
634 }
635 e = (*com->c_func)(arglist);
636 break;
637
638 case NOLIST:
639 /*
640 * Just the constant zero, for exiting,
641 * eg.
642 */
643 e = (*com->c_func)(0);
644 break;
645
646 default:
647 errx(1, "Unknown argtype");
648 }
649
650 out:
651 close_piping();
652
653 /*
654 * Exit the current source file on
655 * error.
656 */
657 if (e) {
658 if (e < 0)
659 return 1;
660 if (loading)
661 return 1;
662 if (sourcing)
663 (void)unstack();
664 return 0;
665 }
666 if (com == NULL)
667 return 0;
668 if (contxt != ec_autoprint && com->c_argtype & P &&
669 value(ENAME_AUTOPRINT) != NULL && (dot->m_flag & MDELETED) == 0)
670 (void)execute(__UNCONST("print ."), ec_autoprint);
671 if (!sourcing && (com->c_argtype & T) == 0)
672 sawcom = 1;
673 return 0;
674 }
675
676
677 /*
678 * The following gets called on receipt of an interrupt. This is
679 * to abort printout of a command, mainly.
680 * Dispatching here when command() is inactive crashes rcv.
681 * Close all open files except 0, 1, 2, and the temporary.
682 * Also, unstack all source files.
683 */
684 static int inithdr; /* am printing startup headers */
685
686 /*ARGSUSED*/
687 static void
688 intr(int s __unused)
689 {
690 noreset = 0;
691 if (!inithdr)
692 sawcom++;
693 inithdr = 0;
694 while (sourcing)
695 (void)unstack();
696
697 close_piping();
698 close_all_files();
699
700 if (image >= 0) {
701 (void)close(image);
702 image = -1;
703 }
704 (void)fprintf(stderr, "Interrupt\n");
705 reset(0);
706 }
707
708 /*
709 * Branch here on hangup signal and simulate "exit".
710 */
711 /*ARGSUSED*/
712 static void
713 hangup(int s __unused)
714 {
715 /* nothing to do? */
716 exit(1);
717 }
718
719 /*
720 * Interpret user commands one by one. If standard input is not a tty,
721 * print no prompt.
722 */
723 PUBLIC void
724 commands(void)
725 {
726 int n;
727 char linebuf[LINESIZE];
728 int eofloop;
729
730 if (!sourcing) {
731 if (signal(SIGINT, SIG_IGN) != SIG_IGN)
732 (void)signal(SIGINT, intr);
733 if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
734 (void)signal(SIGHUP, hangup);
735 (void)signal(SIGTSTP, stop);
736 (void)signal(SIGTTOU, stop);
737 (void)signal(SIGTTIN, stop);
738 }
739 setexit(); /* defined as (void)setjmp(srbuf) in def.h */
740 eofloop = 0; /* initialize this after a possible longjmp */
741 for (;;) {
742 (void)fflush(stdout);
743 sreset();
744 /*
745 * Print the prompt, if needed. Clear out
746 * string space, and flush the output.
747 */
748 if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
749 if ((prompt = value(ENAME_PROMPT)) == NULL)
750 prompt = DEFAULT_PROMPT;
751 prompt = smsgprintf(prompt, dot);
752 if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
753 (void)printf("New mail has arrived.\n");
754 reset_on_stop = 1;
755 #ifndef USE_EDITLINE
756 (void)printf("%s", prompt);
757 #endif
758 }
759 /*
760 * Read a line of commands from the current input
761 * and handle end of file specially.
762 */
763 n = 0;
764 for (;;) {
765 #ifdef USE_EDITLINE
766 if (!sourcing) {
767 char *line;
768 if ((line = my_gets(&elm.command, prompt, NULL)) == NULL) {
769 if (n == 0)
770 n = -1;
771 break;
772 }
773 (void)strlcpy(linebuf, line, sizeof(linebuf));
774 setscreensize(); /* so we can resize a window */
775 }
776 else {
777 if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
778 if (n == 0)
779 n = -1;
780 break;
781 }
782 }
783 #else /* USE_EDITLINE */
784 if (mail_readline(input, &linebuf[n], LINESIZE - n) < 0) {
785 if (n == 0)
786 n = -1;
787 break;
788 }
789 #endif /* USE_EDITLINE */
790
791 if (sourcing) { /* allow comments in source files */
792 char *ptr;
793 if ((ptr = comment_char(linebuf)) != NULL)
794 *ptr = '\0';
795 }
796 if ((n = strlen(linebuf)) == 0)
797 break;
798 n--;
799 if (linebuf[n] != '\\')
800 break;
801 linebuf[n++] = ' ';
802 }
803 reset_on_stop = 0;
804 if (n < 0) {
805 /* eof */
806 if (loading)
807 break;
808 if (sourcing) {
809 (void)unstack();
810 continue;
811 }
812 #ifdef USE_EDITLINE
813 {
814 char *p;
815 if (value(ENAME_INTERACTIVE) != NULL &&
816 (p = value(ENAME_IGNOREEOF)) != NULL &&
817 ++eofloop < (*p == '\0' ? 25 : atoi(p))) {
818 (void)printf("Use \"quit\" to quit.\n");
819 continue;
820 }
821 }
822 #else
823 if (value(ENAME_INTERACTIVE) != NULL &&
824 value(ENAME_IGNOREEOF) != NULL &&
825 ++eofloop < 25) {
826 (void)printf("Use \"quit\" to quit.\n");
827 continue;
828 }
829 #endif
830 break;
831 }
832 eofloop = 0;
833 if (execute(linebuf, ec_normal))
834 break;
835 }
836 }
837
838 /*
839 * Announce information about the file we are editing.
840 * Return a likely place to set dot.
841 */
842 PUBLIC int
843 newfileinfo(int omsgCount)
844 {
845 struct message *mp;
846 int d, n, s, t, u, mdot;
847 char fname[PATHSIZE];
848 char *ename;
849
850 /*
851 * Figure out where to set the 'dot'. Use the first new or
852 * unread message.
853 */
854 for (mp = get_abs_message(omsgCount + 1); mp;
855 mp = next_abs_message(mp))
856 if (mp->m_flag & MNEW)
857 break;
858
859 if (mp == NULL)
860 for (mp = get_abs_message(omsgCount + 1); mp;
861 mp = next_abs_message(mp))
862 if ((mp->m_flag & MREAD) == 0)
863 break;
864 if (mp != NULL)
865 mdot = get_msgnum(mp);
866 else
867 mdot = omsgCount + 1;
868 #ifdef THREAD_SUPPORT
869 /*
870 * See if the message is in the current thread.
871 */
872 if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
873 mdot = 0;
874 #endif
875 /*
876 * Scan the message array counting the new, unread, deleted,
877 * and saved messages.
878 */
879 d = n = s = t = u = 0;
880 for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
881 if (mp->m_flag & MNEW)
882 n++;
883 if ((mp->m_flag & MREAD) == 0)
884 u++;
885 if (mp->m_flag & MDELETED)
886 d++;
887 if (mp->m_flag & MSAVED)
888 s++;
889 if (mp->m_flag & MTAGGED)
890 t++;
891 }
892 ename = mailname;
893 if (getfold(fname, sizeof(fname)) >= 0) {
894 char zname[PATHSIZE];
895 size_t l;
896 l = strlen(fname);
897 if (l < sizeof(fname) - 1)
898 fname[l++] = '/';
899 if (strncmp(fname, mailname, l) == 0) {
900 (void)snprintf(zname, sizeof(zname), "+%s",
901 mailname + l);
902 ename = zname;
903 }
904 }
905 /*
906 * Display the statistics.
907 */
908 (void)printf("\"%s\": ", ename);
909 {
910 int cnt = get_abs_msgCount();
911 (void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
912 }
913 if (n > 0)
914 (void)printf(" %d new", n);
915 if (u-n > 0)
916 (void)printf(" %d unread", u);
917 if (t > 0)
918 (void)printf(" %d tagged", t);
919 if (d > 0)
920 (void)printf(" %d deleted", d);
921 if (s > 0)
922 (void)printf(" %d saved", s);
923 if (readonly)
924 (void)printf(" [Read only]");
925 (void)printf("\n");
926
927 return mdot;
928 }
929
930 /*
931 * Announce the presence of the current Mail version,
932 * give the message count, and print a header listing.
933 */
934 PUBLIC void
935 announce(void)
936 {
937 int vec[2], mdot;
938
939 mdot = newfileinfo(0);
940 vec[0] = mdot;
941 vec[1] = 0;
942 if ((dot = get_message(mdot)) == NULL)
943 dot = get_abs_message(1); /* make sure we get something! */
944 if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
945 inithdr++;
946 (void)headers(vec);
947 inithdr = 0;
948 }
949 }
950
951 /*
952 * Print the current version number.
953 */
954
955 /*ARGSUSED*/
956 PUBLIC int
957 pversion(void *v __unused)
958 {
959 (void)printf("Version %s\n", version);
960 return 0;
961 }
962
963 /*
964 * Load a file of user definitions.
965 */
966 PUBLIC void
967 load(const char *name)
968 {
969 FILE *in, *oldin;
970
971 if ((in = Fopen(name, "r")) == NULL)
972 return;
973 oldin = input;
974 input = in;
975 loading = 1;
976 sourcing = 1;
977 commands();
978 loading = 0;
979 sourcing = 0;
980 input = oldin;
981 (void)Fclose(in);
982 }
983