README: nits
[s-mailx.git] / lex.c
blob135949045a7b3a288233c367431ceb72b5532dc2
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Lexical processing of commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include "rcv.h"
42 #include <sys/stat.h>
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <unistd.h>
47 #include "extern.h"
49 static int *_msgvec;
50 static int _reset_on_stop; /* do a reset() if stopped */
51 static sighandler_type _oldpipe;
53 /* Update mailname (if *name* != NULL) and displayname */
54 static void _update_mailname(char const *name);
55 #ifdef HAVE_MBLEN /* TODO unite __narrow_{pre,suf}fix() into one function! */
56 SINLINE size_t __narrow_prefix(char const *cp, size_t maxl);
57 SINLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
58 #endif
60 static const struct cmd *lex(char *Word);
61 static void stop(int s);
62 static void hangup(int s);
64 #ifdef HAVE_MBLEN
65 SINLINE size_t
66 __narrow_prefix(char const *cp, size_t maxl)
68 int err;
69 size_t i, ok;
71 for (err = ok = i = 0; i < maxl;) {
72 int ml = mblen(cp, maxl - i);
73 if (ml < 0) { /* XXX _narrow_prefix(): mblen() error; action? */
74 (void)mblen(NULL, 0);
75 err = 1;
76 ml = 1;
77 } else {
78 if (! err)
79 ok = i;
80 err = 0;
81 if (ml == 0)
82 break;
84 cp += ml;
85 i += ml;
87 return ok;
90 SINLINE size_t
91 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
93 int err;
94 size_t i, ok;
96 for (err = ok = i = 0; cpl > maxl || err;) {
97 int ml = mblen(cp, cpl);
98 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
99 (void)mblen(NULL, 0);
100 err = 1;
101 ml = 1;
102 } else {
103 if (! err)
104 ok = i;
105 err = 0;
106 if (ml == 0)
107 break;
109 cp += ml;
110 i += ml;
111 cpl -= ml;
113 return ok;
115 #endif /* HAVE_MBLEN */
117 static void
118 _update_mailname(char const *name)
120 char tbuf[MAXPATHLEN], *mailp, *dispp;
121 size_t i, j;
123 /* Don't realpath(3) if it's only an update request */
124 if (name != NULL) {
125 #ifdef HAVE_REALPATH
126 enum protocol p = which_protocol(name);
127 if (p == PROTO_FILE || p == PROTO_MAILDIR) {
128 if (realpath(name, mailname) == NULL) {
129 fprintf(stderr, tr(151,
130 "Can't canonicalize `%s'\n"), name);
131 goto jleave;
133 } else
134 #endif
135 (void)n_strlcpy(mailname, name, MAXPATHLEN);
138 mailp = mailname;
139 dispp = displayname;
141 /* Don't display an absolute path but "+FOLDER" if under *folder* */
142 if (getfold(tbuf, sizeof tbuf)) {
143 i = strlen(tbuf);
144 if (i < sizeof(tbuf) - 1)
145 tbuf[i++] = '/';
146 if (strncmp(tbuf, mailp, i) == 0) {
147 mailp += i;
148 *dispp++ = '+';
152 /* We want to see the name of the folder .. on the screen */
153 i = strlen(mailp);
154 if (i < sizeof(displayname) - 1)
155 memcpy(dispp, mailp, i + 1);
156 else {
157 /* Avoid disrupting multibyte sequences (if possible) */
158 #ifndef HAVE_MBLEN
159 j = sizeof(displayname) / 3 - 1;
160 i -= sizeof(displayname) - (1/* + */ + 3) - j;
161 #else
162 j = __narrow_prefix(mailp, sizeof(displayname) / 3);
163 i = j + __narrow_suffix(mailp + j, i - j,
164 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
165 #endif
166 (void)snprintf(dispp, sizeof(displayname), "%.*s...%s",
167 (int)j, mailp, mailp + i);
169 #ifdef HAVE_REALPATH
170 jleave: ;
171 #endif
175 * Set up editing on the given file name.
176 * If the first character of name is %, we are considered to be
177 * editing the file, otherwise we are reading our mail which has
178 * signficance for mbox and so forth.
180 * newmail: Check for new mail in the current folder only.
183 setfile(char const *name, int newmail)
185 FILE *ibuf;
186 int i, compressed = 0;
187 struct stat stb;
188 bool_t isedit;
189 char const *who = name[1] ? name + 1 : myname;
190 static int shudclob;
191 size_t offset;
192 int omsgCount = 0;
193 struct shortcut *sh;
194 struct flock flp;
196 isedit = (*name != '%' && ((sh = get_shortcut(name)) == NULL ||
197 *sh->sh_long != '%'));
198 if ((name = expand(name)) == NULL)
199 return (-1);
201 switch (which_protocol(name)) {
202 case PROTO_FILE:
203 break;
204 case PROTO_MAILDIR:
205 return (maildir_setfile(name, newmail, isedit));
206 #ifdef HAVE_POP3
207 case PROTO_POP3:
208 shudclob = 1;
209 return (pop3_setfile(name, newmail, isedit));
210 #endif
211 #ifdef HAVE_IMAP
212 case PROTO_IMAP:
213 shudclob = 1;
214 if (newmail) {
215 if (mb.mb_type == MB_CACHE)
216 return 1;
218 return imap_setfile(name, newmail, isedit);
219 #endif
220 default:
221 fprintf(stderr, tr(217, "Cannot handle protocol: %s\n"), name);
222 return (-1);
225 if ((ibuf = Zopen(name, "r", &compressed)) == NULL) {
226 if ((!isedit && errno == ENOENT) || newmail) {
227 if (newmail)
228 goto nonewmail;
229 goto nomail;
231 perror(name);
232 return(-1);
235 if (fstat(fileno(ibuf), &stb) < 0) {
236 Fclose(ibuf);
237 if (newmail)
238 goto nonewmail;
239 perror("fstat");
240 return (-1);
243 if (S_ISDIR(stb.st_mode)) {
244 Fclose(ibuf);
245 if (newmail)
246 goto nonewmail;
247 errno = EISDIR;
248 perror(name);
249 return (-1);
250 } else if (S_ISREG(stb.st_mode)) {
251 /*EMPTY*/
252 } else {
253 Fclose(ibuf);
254 if (newmail)
255 goto nonewmail;
256 errno = EINVAL;
257 perror(name);
258 return (-1);
262 * Looks like all will be well. We must now relinquish our
263 * hold on the current set of stuff. Must hold signals
264 * while we are reading the new file, else we will ruin
265 * the message[] data structure.
268 holdsigs();
269 if (shudclob && !newmail)
270 quit();
272 #ifdef HAVE_SOCKETS
273 if (!newmail && mb.mb_sock.s_fd >= 0)
274 sclose(&mb.mb_sock);
275 #endif /* HAVE_SOCKETS */
278 * Copy the messages into /tmp
279 * and set pointers.
282 flp.l_type = F_RDLCK;
283 flp.l_start = 0;
284 flp.l_whence = SEEK_SET;
285 if (!newmail) {
286 mb.mb_type = MB_FILE;
287 mb.mb_perm = (options & OPT_R_FLAG) ? 0 : MB_DELE|MB_EDIT;
288 mb.mb_compressed = compressed;
289 if (compressed) {
290 if (compressed & 0200)
291 mb.mb_perm = 0;
292 } else {
293 if ((i = open(name, O_WRONLY)) < 0)
294 mb.mb_perm = 0;
295 else
296 close(i);
298 if (shudclob) {
299 if (mb.mb_itf) {
300 fclose(mb.mb_itf);
301 mb.mb_itf = NULL;
303 if (mb.mb_otf) {
304 fclose(mb.mb_otf);
305 mb.mb_otf = NULL;
308 shudclob = 1;
309 edit = isedit;
310 initbox(name);
311 offset = 0;
312 flp.l_len = 0;
313 if (!edit && fcntl(fileno(ibuf), F_SETLKW, &flp) < 0) {
314 perror("Unable to lock mailbox");
315 Fclose(ibuf);
316 return -1;
318 } else /* newmail */{
319 fseek(mb.mb_otf, 0L, SEEK_END);
320 fseek(ibuf, mailsize, SEEK_SET);
321 offset = mailsize;
322 omsgCount = msgCount;
323 flp.l_len = offset;
324 if (!edit && fcntl(fileno(ibuf), F_SETLKW, &flp) < 0)
325 goto nonewmail;
327 mailsize = fsize(ibuf);
328 if (newmail && (size_t)mailsize <= offset) {
329 relsesigs();
330 goto nonewmail;
332 setptr(ibuf, offset);
333 setmsize(msgCount);
334 if (newmail && mb.mb_sorted) {
335 mb.mb_threaded = 0;
336 sort((void *)-1);
338 Fclose(ibuf);
339 relsesigs();
340 if (!newmail)
341 sawcom = FAL0;
342 if ((!edit || newmail) && msgCount == 0) {
343 nonewmail:
344 if (!newmail) {
345 if (value("emptystart") == NULL)
346 nomail: fprintf(stderr, catgets(catd, CATSET, 88,
347 "No mail for %s\n"), who);
349 return 1;
351 if (newmail) {
352 newmailinfo(omsgCount);
354 return(0);
358 newmailinfo(int omsgCount)
360 int mdot;
361 int i;
363 for (i = 0; i < omsgCount; i++)
364 message[i].m_flag &= ~MNEWEST;
365 if (msgCount > omsgCount) {
366 for (i = omsgCount; i < msgCount; i++)
367 message[i].m_flag |= MNEWEST;
368 printf(tr(158, "New mail has arrived.\n"));
369 if (msgCount - omsgCount == 1)
370 printf(tr(214, "Loaded 1 new message.\n"));
371 else
372 printf(tr(215, "Loaded %d new messages.\n"),
373 msgCount - omsgCount);
374 } else
375 printf(tr(224, "Loaded %d messages.\n"), msgCount);
376 callhook(mailname, 1);
377 mdot = getmdot(1);
378 if (value("header")) {
379 #ifdef HAVE_IMAP
380 if (mb.mb_type == MB_IMAP)
381 imap_getheaders(omsgCount+1, msgCount);
382 #endif
383 time_current_update(&time_current, FAL0);
384 while (++omsgCount <= msgCount)
385 if (visible(&message[omsgCount-1]))
386 printhead(omsgCount, stdout, 0);
388 return mdot;
392 * Interpret user commands one by one. If standard input is not a tty,
393 * print no prompt.
395 void
396 commands(void)
398 int eofloop = 0, n;
399 char *linebuf = NULL, *av, *nv;
400 size_t linesize = 0;
401 #ifdef HAVE_IMAP
402 int x;
403 #endif
405 if (!sourcing) {
406 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
407 safe_signal(SIGINT, onintr);
408 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
409 safe_signal(SIGHUP, hangup);
410 /* TODO We do a lot of redundant signal handling, especially
411 * TODO with the line editor(s); try to merge this */
412 safe_signal(SIGTSTP, stop);
413 safe_signal(SIGTTOU, stop);
414 safe_signal(SIGTTIN, stop);
416 _oldpipe = safe_signal(SIGPIPE, SIG_IGN);
417 safe_signal(SIGPIPE, _oldpipe);
418 setexit();
420 for (;;) {
421 interrupts = 0;
422 handlerstacktop = NULL;
424 * Print the prompt, if needed. Clear out
425 * string space, and flush the output.
427 if (! sourcing && (options & OPT_INTERACTIVE)) {
428 av = (av = value("autoinc")) ? savestr(av) : NULL;
429 nv = (nv = value("newmail")) ? savestr(nv) : NULL;
430 if ((options & OPT_TTYIN) &&
431 (av != NULL || nv != NULL ||
432 mb.mb_type == MB_IMAP)) {
433 struct stat st;
435 n = (av && strcmp(av, "noimap") &&
436 strcmp(av, "nopoll")) |
437 (nv && strcmp(nv, "noimap") &&
438 strcmp(nv, "nopoll"));
439 #ifdef HAVE_IMAP
440 x = !(av || nv);
441 #endif
442 if ((mb.mb_type == MB_FILE &&
443 stat(mailname, &st) == 0 &&
444 st.st_size > mailsize) ||
445 #ifdef HAVE_IMAP
446 (mb.mb_type == MB_IMAP &&
447 imap_newmail(n) > x) ||
448 #endif
449 (mb.mb_type == MB_MAILDIR &&
450 n != 0)) {
451 int odot = dot - &message[0];
452 bool_t odid = did_print_dot;
454 setfile(mailname, 1);
455 if (mb.mb_type != MB_IMAP) {
456 dot = &message[odot];
457 did_print_dot = odid;
461 _reset_on_stop = 1;
462 exit_status = 0;
465 if (! sourcing) {
466 sreset();
467 /* TODO Note: this buffer may contain a password
468 * TODO We should redefine the code flow which has
469 * TODO to do that */
470 if ((nv = termios_state.ts_linebuf) != NULL) {
471 termios_state.ts_linebuf = NULL;
472 termios_state.ts_linesize = 0;
473 free(nv); /* TODO pool give-back */
475 /* TODO Due to expand-on-tab of our line editor the
476 * TODO buffer may grow */
477 if (linesize > LINESIZE * 3) {
478 free(linebuf); /* TODO pool! but what? */
479 linebuf = NULL;
480 linesize = 0;
485 * Read a line of commands from the current input
486 * and handle end of file specially.
488 n = readline_input(LNED_LF_ESC | LNED_HIST_ADD, NULL,
489 &linebuf, &linesize);
490 _reset_on_stop = 0;
491 if (n < 0) {
492 /* eof */
493 if (loading)
494 break;
495 if (sourcing) {
496 unstack();
497 continue;
499 if ((options & OPT_INTERACTIVE) &&
500 value("ignoreeof") && ++eofloop < 25) {
501 printf(tr(89, "Use `quit' to quit.\n"));
502 continue;
504 break;
507 eofloop = 0;
508 inhook = 0;
509 if (execute(linebuf, 0, n))
510 break;
511 if (exit_status != EXIT_OK && (options & OPT_BATCH_FLAG) &&
512 boption("batch-exit-on-error"))
513 break;
516 if (linebuf != NULL)
517 free(linebuf);
518 if (sourcing)
519 sreset();
523 * Execute a single command.
524 * Command functions return 0 for success, 1 for error, and -1
525 * for abort. A 1 or -1 aborts a load or source. A -1 aborts
526 * the interactive command loop.
527 * Contxt is non-zero if called while composing mail.
530 execute(char *linebuf, int contxt, size_t linesize)
532 char _wordbuf[2], *arglist[MAXARGC], *cp, *word;
533 struct cmd const *com = (struct cmd*)NULL;
534 int muvec[2], c, e = 1;
536 /* Strip the white space away from the beginning of the command */
537 for (cp = linebuf; whitechar(*cp); ++cp)
540 /* Ignore comments */
541 if (*cp == '#')
542 goto jleave0;
544 /* Handle ! differently to get the correct lexical conventions */
545 if (*cp == '!') {
546 if (sourcing) {
547 fprintf(stderr, tr(90, "Can't `!' while sourcing\n"));
548 goto jleave;
550 shell(++cp);
551 goto jleave0;
554 /* Isolate the actual command; since it may not necessarily be
555 * separated from the arguments (as in `p1') we need to duplicate it to
556 * be able to create a NUL terminated version.
557 * We must be aware of special one letter commands here */
558 arglist[0] = cp;
559 switch (*cp) {
560 case '|':
561 case '~':
562 ++cp;
563 /* FALLTHRU */
564 case '\0':
565 break;
566 default:
567 while (*cp && strchr(" \t0123456789$^.:/-+*'\",;(`",
568 *cp) == NULL)
569 ++cp;
570 break;
572 c = (int)(cp - arglist[0]);
573 linesize -= c;
574 word = (c <= 1) ? _wordbuf : salloc(c + 1);
575 memcpy(word, arglist[0], c);
576 word[c] = '\0';
578 /* Look up the command; if not found, bitch.
579 * Normally, a blank command would map to the first command in the
580 * table; while sourcing, however, we ignore blank lines to eliminate
581 * confusion */
582 if (sourcing && *word == '\0')
583 goto jleave0;
585 com = lex(word);
586 if (com == NULL || com->c_func == &ccmdnotsupp) {
587 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), word);
588 if (com != NULL) {
589 ccmdnotsupp(NULL);
590 com = NULL;
592 goto jleave;
596 * See if we should execute the command -- if a conditional
597 * we always execute it, otherwise, check the state of cond.
599 if ((com->c_argtype & F) == 0) {
600 if ((cond == CRCV && (options & OPT_SENDMODE)) ||
601 (cond == CSEND && ! (options & OPT_SENDMODE)) ||
602 (cond == CTERM && ! (options & OPT_TTYIN)) ||
603 (cond == CNONTERM && (options & OPT_TTYIN)))
604 goto jleave0;
608 * Process the arguments to the command, depending
609 * on the type he expects. Default to an error.
610 * If we are sourcing an interactive command, it's
611 * an error.
613 if ((options & OPT_SENDMODE) && (com->c_argtype & M) == 0) {
614 fprintf(stderr, tr(92,
615 "May not execute `%s' while sending\n"),
616 com->c_name);
617 goto jleave;
619 if (sourcing && com->c_argtype & I) {
620 fprintf(stderr, tr(93,
621 "May not execute `%s' while sourcing\n"),
622 com->c_name);
623 goto jleave;
625 if ((mb.mb_perm & MB_DELE) == 0 && com->c_argtype & W) {
626 fprintf(stderr, tr(94, "May not execute `%s' -- "
627 "message file is read only\n"),
628 com->c_name);
629 goto jleave;
631 if (contxt && com->c_argtype & R) {
632 fprintf(stderr, tr(95,
633 "Cannot recursively invoke `%s'\n"), com->c_name);
634 goto jleave;
636 if (mb.mb_type == MB_VOID && com->c_argtype & A) {
637 fprintf(stderr, tr(257,
638 "Cannot execute `%s' without active mailbox\n"),
639 com->c_name);
640 goto jleave;
643 switch (com->c_argtype & ~(F|P|I|M|T|W|R|A)) {
644 case MSGLIST:
645 /* Message list defaulting to nearest forward legal message */
646 if (_msgvec == 0)
647 goto je96;
648 if ((c = getmsglist(cp, _msgvec, com->c_msgflag)) < 0)
649 break;
650 if (c == 0) {
651 *_msgvec = first(com->c_msgflag, com->c_msgmask);
652 if (*_msgvec != 0)
653 _msgvec[1] = 0;
655 if (*_msgvec == 0) {
656 if (! inhook)
657 printf(tr(97, "No applicable messages\n"));
658 break;
660 e = (*com->c_func)(_msgvec);
661 break;
663 case NDMLIST:
664 /* Message list with no defaults, but no error if none exist */
665 if (_msgvec == 0) {
666 je96:
667 fprintf(stderr, tr(96,
668 "Illegal use of `message list'\n"));
669 break;
671 if ((c = getmsglist(cp, _msgvec, com->c_msgflag)) < 0)
672 break;
673 e = (*com->c_func)(_msgvec);
674 break;
676 case STRLIST:
677 /* Just the straight string, with leading blanks removed */
678 while (whitechar(*cp))
679 cp++;
680 e = (*com->c_func)(cp);
681 break;
683 case RAWLIST:
684 case ECHOLIST:
685 /* A vector of strings, in shell style */
686 if ((c = getrawlist(cp, linesize, arglist,
687 sizeof arglist / sizeof *arglist,
688 (com->c_argtype&~(F|P|I|M|T|W|R|A)) == ECHOLIST)
689 ) < 0)
690 break;
691 if (c < com->c_minargs) {
692 fprintf(stderr, tr(99,
693 "`%s' requires at least %d arg(s)\n"),
694 com->c_name, com->c_minargs);
695 break;
697 if (c > com->c_maxargs) {
698 fprintf(stderr, tr(100,
699 "`%s' takes no more than %d arg(s)\n"),
700 com->c_name, com->c_maxargs);
701 break;
703 e = (*com->c_func)(arglist);
704 break;
706 case NOLIST:
707 /* Just the constant zero, for exiting, eg. */
708 e = (*com->c_func)(0);
709 break;
711 default:
712 panic(tr(101, "Unknown argument type"));
715 jleave:
716 /* Exit the current source file on error */
717 if ((exec_last_comm_error = (e != 0))) {
718 if (e < 0)
719 return 1;
720 if (loading)
721 return 1;
722 if (sourcing)
723 unstack();
724 return 0;
726 if (com == (struct cmd*)NULL )
727 return 0;
728 if (boption("autoprint") && com->c_argtype & P)
729 if (visible(dot)) {
730 muvec[0] = dot - &message[0] + 1;
731 muvec[1] = 0;
732 type(muvec);
734 if (! sourcing && ! inhook && (com->c_argtype & T) == 0)
735 sawcom = TRU1;
736 jleave0:
737 return 0;
741 * Set the size of the message vector used to construct argument
742 * lists to message list functions.
744 void
745 setmsize(int sz)
748 if (_msgvec != 0)
749 free(_msgvec);
750 _msgvec = (int*)scalloc(sz + 1, sizeof *_msgvec);
754 * Find the correct command in the command table corresponding
755 * to the passed command "word"
758 static const struct cmd *
759 lex(char *Word)
761 extern const struct cmd cmdtab[];
762 const struct cmd *cp;
764 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
765 if (is_prefix(Word, cp->c_name))
766 return(cp);
767 return(NULL);
771 * The following gets called on receipt of an interrupt. This is
772 * to abort printout of a command, mainly.
773 * Dispatching here when command() is inactive crashes rcv.
774 * Close all open files except 0, 1, 2, and the temporary.
775 * Also, unstack all source files.
778 static int inithdr; /* am printing startup headers */
780 /*ARGSUSED*/
781 void
782 onintr(int s)
784 if (handlerstacktop != NULL) {
785 handlerstacktop(s);
786 return;
788 safe_signal(SIGINT, onintr);
789 noreset = 0;
790 if (!inithdr)
791 sawcom = TRU1;
792 inithdr = 0;
793 while (sourcing)
794 unstack();
796 close_all_files();
798 if (image >= 0) {
799 close(image);
800 image = -1;
802 if (interrupts != 1)
803 fprintf(stderr, catgets(catd, CATSET, 102, "Interrupt\n"));
804 safe_signal(SIGPIPE, _oldpipe);
805 reset(0);
809 * When we wake up after ^Z, reprint the prompt.
811 static void
812 stop(int s)
814 sighandler_type old_action = safe_signal(s, SIG_DFL);
815 sigset_t nset;
817 sigemptyset(&nset);
818 sigaddset(&nset, s);
819 sigprocmask(SIG_UNBLOCK, &nset, (sigset_t *)NULL);
820 kill(0, s);
821 sigprocmask(SIG_BLOCK, &nset, (sigset_t *)NULL);
822 safe_signal(s, old_action);
823 if (_reset_on_stop) {
824 _reset_on_stop = 0;
825 reset(0);
830 * Branch here on hangup signal and simulate "exit".
832 /*ARGSUSED*/
833 static void
834 hangup(int s)
836 (void)s;
837 /* nothing to do? */
838 exit(1);
842 * Announce the presence of the current Mail version,
843 * give the message count, and print a header listing.
845 void
846 announce(int printheaders)
848 int vec[2], mdot;
850 mdot = newfileinfo();
851 vec[0] = mdot;
852 vec[1] = 0;
853 dot = &message[mdot - 1];
854 if (printheaders && msgCount > 0 && value("header") != NULL) {
855 inithdr++;
856 headers(vec);
857 inithdr = 0;
862 * Announce information about the file we are editing.
863 * Return a likely place to set dot.
865 int
866 newfileinfo(void)
868 struct message *mp;
869 int u, n, mdot, d, s, hidden, moved;
871 if (mb.mb_type == MB_VOID)
872 return 1;
873 mdot = getmdot(0);
874 s = d = hidden = moved =0;
875 for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) {
876 if (mp->m_flag & MNEW)
877 n++;
878 if ((mp->m_flag & MREAD) == 0)
879 u++;
880 if ((mp->m_flag & (MDELETED|MSAVED)) == (MDELETED|MSAVED))
881 moved++;
882 if ((mp->m_flag & (MDELETED|MSAVED)) == MDELETED)
883 d++;
884 if ((mp->m_flag & (MDELETED|MSAVED)) == MSAVED)
885 s++;
886 if (mp->m_flag & MHIDDEN)
887 hidden++;
889 _update_mailname(NULL);
890 printf(tr(103, "\"%s\": "), displayname);
891 if (msgCount == 1)
892 printf(tr(104, "1 message"));
893 else
894 printf(tr(105, "%d messages"), msgCount);
895 if (n > 0)
896 printf(tr(106, " %d new"), n);
897 if (u-n > 0)
898 printf(tr(107, " %d unread"), u);
899 if (d > 0)
900 printf(tr(108, " %d deleted"), d);
901 if (s > 0)
902 printf(tr(109, " %d saved"), s);
903 if (moved > 0)
904 printf(tr(136, " %d moved"), moved);
905 if (hidden > 0)
906 printf(tr(139, " %d hidden"), hidden);
907 if (mb.mb_type == MB_CACHE)
908 printf(" [Disconnected]");
909 else if (mb.mb_perm == 0)
910 printf(tr(110, " [Read only]"));
911 printf("\n");
912 return(mdot);
915 int
916 getmdot(int newmail)
918 struct message *mp;
919 char *cp;
920 int mdot;
921 enum mflag avoid = MHIDDEN|MDELETED;
923 if (!newmail) {
924 if (value("autothread"))
925 thread(NULL);
926 else if ((cp = value("autosort")) != NULL) {
927 free(mb.mb_sorted);
928 mb.mb_sorted = sstrdup(cp);
929 sort(NULL);
932 if (mb.mb_type == MB_VOID)
933 return 1;
934 if (newmail)
935 for (mp = &message[0]; mp < &message[msgCount]; mp++)
936 if ((mp->m_flag & (MNEWEST|avoid)) == MNEWEST)
937 break;
938 if (!newmail || mp >= &message[msgCount]) {
939 for (mp = mb.mb_threaded ? threadroot : &message[0];
940 mb.mb_threaded ?
941 mp != NULL : mp < &message[msgCount];
942 mb.mb_threaded ?
943 mp = next_in_thread(mp) : mp++)
944 if ((mp->m_flag & (MNEW|avoid)) == MNEW)
945 break;
947 if (mb.mb_threaded ? mp == NULL : mp >= &message[msgCount])
948 for (mp = mb.mb_threaded ? threadroot : &message[0];
949 mb.mb_threaded ? mp != NULL:
950 mp < &message[msgCount];
951 mb.mb_threaded ? mp = next_in_thread(mp) : mp++)
952 if (mp->m_flag & MFLAGGED)
953 break;
954 if (mb.mb_threaded ? mp == NULL : mp >= &message[msgCount])
955 for (mp = mb.mb_threaded ? threadroot : &message[0];
956 mb.mb_threaded ? mp != NULL:
957 mp < &message[msgCount];
958 mb.mb_threaded ? mp = next_in_thread(mp) : mp++)
959 if ((mp->m_flag & (MREAD|avoid)) == 0)
960 break;
961 if (mb.mb_threaded ? mp != NULL : mp < &message[msgCount])
962 mdot = mp - &message[0] + 1;
963 else if (value("showlast")) {
964 if (mb.mb_threaded) {
965 for (mp = this_in_thread(threadroot, -1); mp;
966 mp = prev_in_thread(mp))
967 if ((mp->m_flag & avoid) == 0)
968 break;
969 mdot = mp ? mp - &message[0] + 1 : msgCount;
970 } else {
971 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
972 if ((mp->m_flag & avoid) == 0)
973 break;
974 mdot = mp >= &message[0] ? mp-&message[0]+1 : msgCount;
976 } else if (mb.mb_threaded) {
977 for (mp = threadroot; mp; mp = next_in_thread(mp))
978 if ((mp->m_flag & avoid) == 0)
979 break;
980 mdot = mp ? mp - &message[0] + 1 : 1;
981 } else {
982 for (mp = &message[0]; mp < &message[msgCount]; mp++)
983 if ((mp->m_flag & avoid) == 0)
984 break;
985 mdot = mp < &message[msgCount] ? mp-&message[0]+1 : 1;
987 return mdot;
991 * Print the current version number.
994 /*ARGSUSED*/
995 int
996 pversion(void *v)
998 (void)v;
999 printf(catgets(catd, CATSET, 111, "Version %s\n"), version);
1000 return(0);
1003 void
1004 initbox(const char *name)
1006 char *tempMesg;
1007 int dummy;
1009 if (mb.mb_type != MB_VOID)
1010 (void)n_strlcpy(prevfile, mailname, MAXPATHLEN);
1011 _update_mailname(name != mailname ? name : NULL);
1012 if ((mb.mb_otf = Ftemp(&tempMesg, "tmpbox", "w", 0600, 0)) == NULL) {
1013 perror(tr(87, "temporary mail message file"));
1014 exit(1);
1016 (void)fcntl(fileno(mb.mb_otf), F_SETFD, FD_CLOEXEC);
1017 if ((mb.mb_itf = safe_fopen(tempMesg, "r", &dummy)) == NULL) {
1018 perror(tr(87, "temporary mail message file"));
1019 exit(1);
1021 (void)fcntl(fileno(mb.mb_itf), F_SETFD, FD_CLOEXEC);
1022 rm(tempMesg);
1023 Ftfree(&tempMesg);
1024 msgCount = 0;
1025 if (message) {
1026 free(message);
1027 message = NULL;
1028 msgspace = 0;
1030 mb.mb_threaded = 0;
1031 if (mb.mb_sorted != NULL) {
1032 free(mb.mb_sorted);
1033 mb.mb_sorted = NULL;
1035 #ifdef HAVE_IMAP
1036 mb.mb_flags = MB_NOFLAGS;
1037 #endif
1038 prevdot = NULL;
1039 dot = NULL;
1040 did_print_dot = FAL0;