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