Completely rework colour: add `colour' and `mono' commands..
[s-mailx.git] / lex.c
blob07c5cecb0685d1c23914bc7cf4b6233519bffbea
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ (Lexical processing of) Commands, and the (blocking) "event mainloop".
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE lex
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <pwd.h>
44 struct cmd {
45 char const *name; /* Name of command */
46 int (*func)(void*); /* Implementor of command */
47 enum argtype argtype; /* Arglist type (see below) */
48 short msgflag; /* Required flags of msgs */
49 short msgmask; /* Relevant flags of msgs */
50 #ifdef HAVE_DOCSTRINGS
51 char const *doc; /* One line doc for command */
52 #endif
54 /* Yechh, can't initialize unions */
55 #define minargs msgflag /* Minimum argcount for RAWLIST */
56 #define maxargs msgmask /* Max argcount for RAWLIST */
58 struct cmd_ghost {
59 struct cmd_ghost *next;
60 struct str cmd; /* Data follows after .name */
61 char name[VFIELD_SIZE(0)];
64 static int *_msgvec;
65 static int _reset_on_stop; /* do a reset() if stopped */
66 static sighandler_type _oldpipe;
67 static struct cmd_ghost *_cmd_ghosts;
68 /* _cmd_tab[] after fun protos */
69 static int _lex_inithdr; /* am printing startup headers */
71 /* Update mailname (if name != NULL) and displayname, return wether displayname
72 * was large enough to swallow mailname */
73 static bool_t _update_mailname(char const *name);
74 #ifdef HAVE_C90AMEND1 /* TODO unite __narrow_suffix() into one fun! */
75 SINLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
76 #endif
78 /* Isolate the command from the arguments */
79 static char * _lex_isolate(char const *comm);
81 /* Get first-fit, or NULL */
82 static struct cmd const * _lex(char const *comm);
84 /* Command ghost handling */
85 static int _c_ghost(void *v);
86 static int _c_unghost(void *v);
88 /* Print a list of all commands */
89 static int _c_list(void *v);
91 static int __pcmd_cmp(void const *s1, void const *s2);
93 /* `quit' command */
94 static int _c_quit(void *v);
96 /* Print the binaries compiled-in features */
97 static int _c_features(void *v);
99 /* Print the binaries version number */
100 static int _c_version(void *v);
102 /* When we wake up after ^Z, reprint the prompt */
103 static void stop(int s);
105 /* Branch here on hangup signal and simulate "exit" */
106 static void hangup(int s);
108 /* List of all commands, and list of commands which are specially treated
109 * and deduced in execute(), but we need a list for _c_list() and
110 * print_comm_docstr() */
111 #ifdef HAVE_DOCSTRINGS
112 # define DS(S) , S
113 #else
114 # define DS(S)
115 #endif
116 static struct cmd const _cmd_tab[] = {
117 #include "cmd_tab.h"
119 _special_cmd_tab[] = {
120 { "#", NULL, 0, 0, 0
121 DS(N_("\"Comment command\": ignore remaining (continuable) line")) },
122 { "-", NULL, 0, 0, 0
123 DS(N_("Print out the preceding message")) }
125 #undef DS
127 #ifdef HAVE_C90AMEND1
128 SINLINE size_t
129 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
131 int err;
132 size_t i, ok;
133 NYD_ENTER;
135 for (err = ok = i = 0; cpl > maxl || err;) {
136 int ml = mblen(cp, cpl);
137 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
138 (void)mblen(NULL, 0);
139 err = 1;
140 ml = 1;
141 } else {
142 if (!err)
143 ok = i;
144 err = 0;
145 if (ml == 0)
146 break;
148 cp += ml;
149 i += ml;
150 cpl -= ml;
152 NYD_LEAVE;
153 return ok;
155 #endif /* HAVE_C90AMEND1 */
157 static bool_t
158 _update_mailname(char const *name)
160 char tbuf[PATH_MAX], *mailp, *dispp;
161 size_t i, j;
162 bool_t rv = TRU1;
163 NYD_ENTER;
165 /* Don't realpath(3) if it's only an update request */
166 if (name != NULL) {
167 #ifdef HAVE_REALPATH
168 enum protocol p = which_protocol(name);
169 if (p == PROTO_FILE || p == PROTO_MAILDIR) {
170 if (realpath(name, mailname) == NULL) {
171 n_err(_("Can't canonicalize \"%s\"\n"), name);
172 rv = FAL0;
173 goto jdocopy;
175 } else
176 jdocopy:
177 #endif
178 n_strlcpy(mailname, name, sizeof(mailname));
181 mailp = mailname;
182 dispp = displayname;
184 /* Don't display an absolute path but "+FOLDER" if under *folder* */
185 if (getfold(tbuf, sizeof tbuf)) {
186 i = strlen(tbuf);
187 if (i < sizeof(tbuf) -1)
188 tbuf[i++] = '/';
189 if (!strncmp(tbuf, mailp, i)) {
190 mailp += i;
191 *dispp++ = '+';
195 /* We want to see the name of the folder .. on the screen */
196 i = strlen(mailp);
197 if (i < sizeof(displayname) -1)
198 memcpy(dispp, mailp, i +1);
199 else {
200 rv = FAL0;
201 /* Avoid disrupting multibyte sequences (if possible) */
202 #ifndef HAVE_C90AMEND1
203 j = sizeof(displayname) / 3 - 1;
204 i -= sizeof(displayname) - (1/* + */ + 3) - j;
205 #else
206 j = field_detect_clip(sizeof(displayname) / 3, mailp, i);
207 i = j + __narrow_suffix(mailp + j, i - j,
208 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
209 #endif
210 snprintf(dispp, sizeof(displayname), "%.*s...%s",
211 (int)j, mailp, mailp + i);
213 NYD_LEAVE;
214 return rv;
217 static char *
218 _lex_isolate(char const *comm)
220 NYD_ENTER;
221 while (*comm != '\0' &&
222 strchr("~|? \t0123456789&%@$^.:/-+*'\",;(`", *comm) == NULL)
223 ++comm;
224 NYD_LEAVE;
225 return UNCONST(comm);
228 static struct cmd const *
229 _lex(char const *comm) /* TODO **command hashtable**! linear list search!!! */
231 struct cmd const *cp, *cpmax;
232 NYD_ENTER;
234 for (cp = cpmax = _cmd_tab, cpmax += NELEM(_cmd_tab); cp != cpmax; ++cp)
235 if (*comm == *cp->name && is_prefix(comm, cp->name))
236 goto jleave;
237 cp = NULL;
238 jleave:
239 NYD_LEAVE;
240 return cp;
243 static int
244 _c_ghost(void *v)
246 char const **argv = v;
247 struct cmd_ghost *lcg, *cg;
248 size_t i, cl, nl;
249 char *cp;
250 NYD_ENTER;
252 /* Show the list? */
253 if (*argv == NULL) {
254 FILE *fp;
256 if ((fp = Ftmp(NULL, "ghost", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
257 fp = stdout;
258 for (i = 0, cg = _cmd_ghosts; cg != NULL; cg = cg->next)
259 fprintf(fp, "ghost %s \"%s\"\n", cg->name, string_quote(cg->cmd.s));
260 if (fp != stdout) {
261 page_or_print(fp, i);
262 Fclose(fp);
264 goto jleave;
267 /* Verify the ghost name is a valid one */
268 if (*argv[0] == '\0' || *_lex_isolate(argv[0]) != '\0') {
269 n_err(_("`ghost': can't canonicalize \"%s\"\n"), argv[0]);
270 v = NULL;
271 goto jleave;
274 /* Show command of single ghost? */
275 if (argv[1] == NULL) {
276 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
277 if (!strcmp(argv[0], cg->name)) {
278 printf("ghost %s \"%s\"\n", cg->name, string_quote(cg->cmd.s));
279 goto jleave;
281 n_err(_("`ghost': no such alias: \"%s\"\n"), argv[0]);
282 v = NULL;
283 goto jleave;
286 /* Define command for ghost: verify command content */
287 for (cl = 0, i = 1; (cp = UNCONST(argv[i])) != NULL; ++i)
288 if (*cp != '\0')
289 cl += strlen(cp) + 1; /* SP or NUL */
290 if (cl == 0) {
291 n_err(_("`ghost': empty command arguments after \"%s\"\n"), argv[0]);
292 v = NULL;
293 goto jleave;
296 /* If the ghost already exists, recreate */
297 for (lcg = NULL, cg = _cmd_ghosts; cg != NULL; lcg = cg, cg = cg->next)
298 if (!strcmp(cg->name, argv[0])) {
299 if (lcg != NULL)
300 lcg->next = cg->next;
301 else
302 _cmd_ghosts = cg->next;
303 free(cg);
304 break;
307 nl = strlen(argv[0]) +1;
308 cg = smalloc(sizeof(*cg) - VFIELD_SIZEOF(struct cmd_ghost, name) + nl + cl);
309 cg->next = _cmd_ghosts;
310 _cmd_ghosts = cg;
311 memcpy(cg->name, argv[0], nl);
312 cp = cg->cmd.s = cg->name + nl;
313 cg->cmd.l = --cl;
314 while (*++argv != NULL) {
315 i = strlen(*argv);
316 if (i > 0) {
317 memcpy(cp, *argv, i);
318 cp += i;
319 *cp++ = ' ';
322 *--cp = '\0';
323 jleave:
324 NYD_LEAVE;
325 return (v == NULL);
328 static int
329 _c_unghost(void *v)
331 int rv = 0;
332 char const **argv = v, *cp;
333 struct cmd_ghost *lcg, *cg;
334 NYD_ENTER;
336 while ((cp = *argv++) != NULL) {
337 if (cp[0] == '*' && cp[1] == '\0') {
338 while ((cg = _cmd_ghosts) != NULL) {
339 _cmd_ghosts = cg->next;
340 free(cg);
342 } else {
343 for (lcg = NULL, cg = _cmd_ghosts; cg != NULL; lcg = cg, cg = cg->next)
344 if (!strcmp(cg->name, cp)) {
345 if (lcg != NULL)
346 lcg->next = cg->next;
347 else
348 _cmd_ghosts = cg->next;
349 free(cg);
350 goto jouter;
352 n_err(_("`unghost': no such alias: \"%s\"\n"), cp);
353 rv = 1;
354 jouter:
358 NYD_LEAVE;
359 return rv;
362 static int
363 __pcmd_cmp(void const *s1, void const *s2)
365 struct cmd const * const *c1 = s1, * const *c2 = s2;
366 int rv;
367 NYD_ENTER;
369 rv = strcmp((*c1)->name, (*c2)->name);
370 NYD_LEAVE;
371 return rv;
374 static int
375 _c_list(void *v)
377 struct cmd const **cpa, *cp, **cursor;
378 size_t i;
379 NYD_ENTER;
380 UNUSED(v);
382 i = NELEM(_cmd_tab) + NELEM(_special_cmd_tab) + 1;
383 cpa = ac_alloc(sizeof(cp) * i);
385 for (i = 0; i < NELEM(_cmd_tab); ++i)
386 cpa[i] = _cmd_tab + i;
388 size_t j;
390 for (j = 0; j < NELEM(_special_cmd_tab); ++i, ++j)
391 cpa[i] = _special_cmd_tab + j;
393 cpa[i] = NULL;
395 qsort(cpa, i, sizeof(cp), &__pcmd_cmp);
397 printf(_("Commands are:\n"));
398 for (i = 0, cursor = cpa; (cp = *cursor++) != NULL;) {
399 size_t j;
400 if (cp->func == &c_cmdnotsupp)
401 continue;
402 j = strlen(cp->name) + 2;
403 if ((i += j) > 72) {
404 i = j;
405 printf("\n");
407 printf((*cursor != NULL ? "%s, " : "%s\n"), cp->name);
410 ac_free(cpa);
411 NYD_LEAVE;
412 return 0;
415 static int
416 _c_quit(void *v)
418 int rv;
419 NYD_ENTER;
420 UNUSED(v);
422 /* If we are PS_SOURCING, then return 1 so evaluate() can handle it.
423 * Otherwise return -1 to abort command loop */
424 rv = (pstate & PS_SOURCING) ? 1 : -1;
425 NYD_LEAVE;
426 return rv;
429 static int
430 _c_features(void *v)
432 NYD_ENTER;
433 UNUSED(v);
434 printf(_("Features: %s\n"), ok_vlook(features));
435 NYD_LEAVE;
436 return 0;
439 static int
440 _c_version(void *v)
442 NYD_ENTER;
443 UNUSED(v);
444 printf(_("Version %s\n"), ok_vlook(version));
445 NYD_LEAVE;
446 return 0;
449 static void
450 stop(int s)
452 sighandler_type old_action;
453 sigset_t nset;
454 NYD_X; /* Signal handler */
456 old_action = safe_signal(s, SIG_DFL);
458 sigemptyset(&nset);
459 sigaddset(&nset, s);
460 sigprocmask(SIG_UNBLOCK, &nset, NULL);
461 n_raise(s);
462 sigprocmask(SIG_BLOCK, &nset, NULL);
463 safe_signal(s, old_action);
464 if (_reset_on_stop) {
465 _reset_on_stop = 0;
466 reset(0);
470 static void
471 hangup(int s)
473 NYD_X; /* Signal handler */
474 UNUSED(s);
475 /* nothing to do? */
476 exit(EXIT_ERR);
479 FL int
480 setfile(char const *name, enum fedit_mode fm) /* TODO oh my god */
482 /* Note we don't 'userid(myname) != getuid()', preliminary steps are usually
483 * necessary to make a mailbox accessible by a different user, and if that
484 * has happened, let's just let the usual file perms decide */
485 static int shudclob;
487 struct stat stb;
488 size_t offset;
489 char const *who;
490 int rv, omsgCount = 0;
491 FILE *ibuf = NULL, *lckfp = NULL;
492 bool_t isdevnull = FAL0;
493 NYD_ENTER;
495 pstate &= ~PS_SETFILE_OPENED;
497 if (name[0] == '%' || ((who = shortcut_expand(name)) != NULL && *who == '%'))
498 fm |= FEDIT_SYSBOX; /* TODO fexpand() needs to tell is-valid-user! */
500 who = (name[1] != '\0') ? name + 1 : myname;
502 if ((name = expand(name)) == NULL)
503 goto jem1;
505 switch (which_protocol(name)) {
506 case PROTO_FILE:
507 if (temporary_protocol_ext != NULL)
508 name = savecat(name, temporary_protocol_ext);
509 isdevnull = ((options & OPT_BATCH_FLAG) && !strcmp(name, "/dev/null"));
510 #ifdef HAVE_REALPATH
511 do { /* TODO we need objects, goes away then */
512 char ebuf[PATH_MAX];
513 if (realpath(name, ebuf) != NULL)
514 name = savestr(ebuf);
515 } while (0);
516 #endif
517 rv = 1;
518 break;
519 case PROTO_MAILDIR:
520 shudclob = 1;
521 rv = maildir_setfile(name, fm);
522 goto jleave;
523 #ifdef HAVE_POP3
524 case PROTO_POP3:
525 shudclob = 1;
526 rv = pop3_setfile(name, fm);
527 goto jleave;
528 #endif
529 #ifdef HAVE_IMAP
530 case PROTO_IMAP:
531 shudclob = 1;
532 if ((fm & FEDIT_NEWMAIL) && mb.mb_type == MB_CACHE)
533 rv = 1;
534 else
535 rv = imap_setfile(name, fm);
536 goto jleave;
537 #endif
538 default:
539 n_err(_("Cannot handle protocol: %s\n"), name);
540 goto jem1;
543 if ((ibuf = Zopen(name, "r")) == NULL) {
544 if ((fm & FEDIT_SYSBOX) && errno == ENOENT) {
545 if (strcmp(who, myname) && getpwnam(who) == NULL) {
546 n_err(_("\"%s\" is not a user of this system\n"), who);
547 goto jem2;
549 if (!(options & OPT_QUICKRUN_MASK) && ok_blook(bsdcompat))
550 n_err(_("No mail for %s\n"), who);
551 if (fm & FEDIT_NEWMAIL)
552 goto jleave;
553 if (ok_blook(emptystart)) {
554 if (!(options & OPT_QUICKRUN_MASK) && !ok_blook(bsdcompat))
555 n_perr(name, 0);
556 /* We must avoid returning -1 and causing program exit */
557 mb.mb_type = MB_VOID;
558 rv = 1;
559 goto jleave;
561 goto jem2;
562 } else if (fm & FEDIT_NEWMAIL)
563 goto jleave;
564 n_perr(name, 0);
565 goto jem1;
568 if (fstat(fileno(ibuf), &stb) == -1) {
569 if (fm & FEDIT_NEWMAIL)
570 goto jleave;
571 n_perr(_("fstat"), 0);
572 goto jem1;
575 if (S_ISREG(stb.st_mode) || isdevnull) {
576 /* EMPTY */
577 } else {
578 if (fm & FEDIT_NEWMAIL)
579 goto jleave;
580 errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
581 n_perr(name, 0);
582 goto jem1;
585 /* Looks like all will be well. We must now relinquish our hold on the
586 * current set of stuff. Must hold signals while we are reading the new
587 * file, else we will ruin the message[] data structure */
589 hold_sigs(); /* TODO note on this one in quit.c:quit() */
590 if (shudclob && !(fm & FEDIT_NEWMAIL))
591 quit();
592 #ifdef HAVE_SOCKETS
593 if (!(fm & FEDIT_NEWMAIL) && mb.mb_sock.s_fd >= 0)
594 sclose(&mb.mb_sock); /* TODO sorry? VMAILFS->close(), thank you */
595 #endif
597 /* TODO There is no intermediate VOID box we've switched to: name may
598 * TODO point to the same box that we just have written, so any updates
599 * TODO we won't see! Reopen again in this case. RACY! Goes with VOID! */
600 /* TODO In addition: in case of compressed/hook boxes we lock a temporary! */
601 /* TODO We may uselessly open twice but quit() doesn't say wether we were
602 * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
603 if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !strcmp(name, mailname)) {
604 name = mailname;
605 Fclose(ibuf);
607 if ((ibuf = Zopen(name, "r")) == NULL ||
608 fstat(fileno(ibuf), &stb) == -1 ||
609 (!S_ISREG(stb.st_mode) && !isdevnull)) {
610 n_perr(name, 0);
611 rele_sigs();
612 goto jem2;
616 /* Copy the messages into /tmp and set pointers */
617 if (!(fm & FEDIT_NEWMAIL)) {
618 mb.mb_type = MB_FILE;
619 mb.mb_perm = (((options & OPT_R_FLAG) || (fm & FEDIT_RDONLY) ||
620 access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
621 if (shudclob) {
622 if (mb.mb_itf) {
623 fclose(mb.mb_itf);
624 mb.mb_itf = NULL;
626 if (mb.mb_otf) {
627 fclose(mb.mb_otf);
628 mb.mb_otf = NULL;
631 shudclob = 1;
632 if (fm & FEDIT_SYSBOX)
633 pstate &= ~PS_EDIT;
634 else
635 pstate |= PS_EDIT;
636 initbox(name);
637 offset = 0;
638 } else {
639 fseek(mb.mb_otf, 0L, SEEK_END);
640 /* TODO Doing this without holding a lock is.. And not err checking.. */
641 fseek(ibuf, mailsize, SEEK_SET);
642 offset = mailsize;
643 omsgCount = msgCount;
646 if (isdevnull)
647 lckfp = (FILE*)-1;
648 else if (!(pstate & PS_EDIT))
649 lckfp = dot_lock(name, fileno(ibuf), FLT_READ, offset,0,
650 (fm & FEDIT_NEWMAIL ? 0 : 1));
651 else if (file_lock(fileno(ibuf), FLT_READ, offset,0,
652 (fm & FEDIT_NEWMAIL ? 0 : 1)))
653 lckfp = (FILE*)-1;
655 if (lckfp == NULL) {
656 if (!(fm & FEDIT_NEWMAIL)) {
657 char const *emsg = (pstate & PS_EDIT)
658 ? N_("Unable to lock mailbox, aborting operation")
659 : N_("Unable to (dot) lock mailbox, aborting operation");
660 n_perr(V_(emsg), 0);
662 rele_sigs();
663 if (!(fm & FEDIT_NEWMAIL))
664 goto jem1;
665 goto jleave;
668 mailsize = fsize(ibuf);
670 /* TODO This is too simple minded? We should regenerate an index file
671 * TODO to be able to truly tell wether *anything* has changed! */
672 if ((fm & FEDIT_NEWMAIL) && UICMP(z, mailsize, <=, offset)) {
673 rele_sigs();
674 goto jleave;
676 setptr(ibuf, offset);
677 setmsize(msgCount);
678 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
679 mb.mb_threaded = 0;
680 c_sort((void*)-1);
683 Fclose(ibuf);
684 ibuf = NULL;
685 if (lckfp != NULL && lckfp != (FILE*)-1) {
686 Pclose(lckfp, FAL0);
687 /*lckfp = NULL;*/
689 rele_sigs();
691 if (!(fm & FEDIT_NEWMAIL)) {
692 pstate &= ~PS_SAW_COMMAND;
693 pstate |= PS_SETFILE_OPENED;
696 if ((options & OPT_EXISTONLY) && !(options & OPT_HEADERLIST)) {
697 rv = (msgCount == 0);
698 goto jleave;
701 if ((!(pstate & PS_EDIT) || (fm & FEDIT_NEWMAIL)) && msgCount == 0) {
702 if (!(fm & FEDIT_NEWMAIL)) {
703 if (!ok_blook(emptystart))
704 n_err(_("No mail for %s\n"), who);
706 goto jleave;
709 if (fm & FEDIT_NEWMAIL)
710 newmailinfo(omsgCount);
712 rv = 0;
713 jleave:
714 if (ibuf != NULL) {
715 Fclose(ibuf);
716 if (lckfp != NULL && lckfp != (FILE*)-1)
717 Pclose(lckfp, FAL0);
719 NYD_LEAVE;
720 return rv;
721 jem2:
722 mb.mb_type = MB_VOID;
723 jem1:
724 rv = -1;
725 goto jleave;
728 FL int
729 newmailinfo(int omsgCount)
731 int mdot, i;
732 NYD_ENTER;
734 for (i = 0; i < omsgCount; ++i)
735 message[i].m_flag &= ~MNEWEST;
737 if (msgCount > omsgCount) {
738 for (i = omsgCount; i < msgCount; ++i)
739 message[i].m_flag |= MNEWEST;
740 printf(_("New mail has arrived.\n"));
741 if ((i = msgCount - omsgCount) == 1)
742 printf(_("Loaded 1 new message.\n"));
743 else
744 printf(_("Loaded %d new messages.\n"), i);
745 } else
746 printf(_("Loaded %d messages.\n"), msgCount);
748 check_folder_hook(TRU1);
750 mdot = getmdot(1);
752 if (ok_blook(header))
753 print_headers(omsgCount + 1, msgCount, FAL0);
754 NYD_LEAVE;
755 return mdot;
758 FL bool_t
759 commands(void)
761 struct eval_ctx ev;
762 int n;
763 bool_t volatile rv = TRU1;
764 NYD_ENTER;
766 if (!(pstate & PS_SOURCING)) {
767 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
768 safe_signal(SIGINT, onintr);
769 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
770 safe_signal(SIGHUP, hangup);
771 /* TODO We do a lot of redundant signal handling, especially
772 * TODO with the command line editor(s); try to merge this */
773 safe_signal(SIGTSTP, stop);
774 safe_signal(SIGTTOU, stop);
775 safe_signal(SIGTTIN, stop);
777 _oldpipe = safe_signal(SIGPIPE, SIG_IGN);
778 safe_signal(SIGPIPE, _oldpipe);
780 memset(&ev, 0, sizeof ev);
782 setexit();
783 for (;;) {
784 char *temporary_orig_line; /* XXX eval_ctx.ev_line not yet constant */
786 n_COLOUR( n_colour_env_pop(TRU1); )
788 /* TODO Unless we have our signal manager (or however we do it) child
789 * TODO processes may have time slots where their execution isn't
790 * TODO protected by signal handlers (in between start and setup
791 * TODO completed). close_all_files() is only called from onintr()
792 * TODO so those may linger possibly forever */
793 if (!(pstate & PS_SOURCING))
794 close_all_files();
796 interrupts = 0;
797 handlerstacktop = NULL;
799 if (temporary_localopts_store != NULL) /* XXX intermediate hack */
800 temporary_localopts_free(); /* XXX intermediate hack */
801 sreset((pstate & PS_SOURCING) != 0);
802 if (!(pstate & PS_SOURCING)) {
803 char *cp;
805 /* TODO Note: this buffer may contain a password. We should redefine
806 * TODO the code flow which has to do that */
807 if ((cp = termios_state.ts_linebuf) != NULL) {
808 termios_state.ts_linebuf = NULL;
809 termios_state.ts_linesize = 0;
810 free(cp); /* TODO pool give-back */
812 /* TODO Due to expand-on-tab of NCL the buffer may grow */
813 if (ev.ev_line.l > LINESIZE * 3) {
814 free(ev.ev_line.s); /* TODO pool! but what? */
815 ev.ev_line.s = NULL;
816 ev.ev_line.l = ev.ev_line_size = 0;
820 if (!(pstate & PS_SOURCING) && (options & OPT_INTERACTIVE)) {
821 char *cp;
823 cp = ok_vlook(newmail);
824 if ((options & OPT_TTYIN) && (cp != NULL || mb.mb_type == MB_IMAP)) {
825 struct stat st;
827 n = (cp != NULL && strcmp(cp, "noimap") && strcmp(cp, "nopoll"));
828 if ((mb.mb_type == MB_FILE && !stat(mailname, &st) &&
829 st.st_size > mailsize) ||
830 #ifdef HAVE_IMAP
831 (mb.mb_type == MB_IMAP && imap_newmail(n) > (cp == NULL)) ||
832 #endif
833 (mb.mb_type == MB_MAILDIR && n != 0)) {
834 size_t odot = PTR2SIZE(dot - message);
835 ui32_t odid = (pstate & PS_DID_PRINT_DOT);
837 if (setfile(mailname,
838 FEDIT_NEWMAIL |
839 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)) < 0) {
840 exit_status |= EXIT_ERR;
841 rv = FAL0;
842 break;
844 if (mb.mb_type != MB_IMAP) {
845 dot = message + odot;
846 pstate |= odid;
851 _reset_on_stop = 1;
852 exit_status = EXIT_OK;
855 /* Read a line of commands and handle end of file specially */
856 jreadline:
857 ev.ev_line.l = ev.ev_line_size;
858 n = readline_input(NULL, TRU1, &ev.ev_line.s, &ev.ev_line.l,
859 ev.ev_new_content);
860 ev.ev_line_size = (ui32_t)ev.ev_line.l;
861 ev.ev_line.l = (ui32_t)n;
862 _reset_on_stop = 0;
863 if (n < 0) {
864 /* EOF */
865 if (pstate & PS_LOADING)
866 break;
867 if (pstate & PS_SOURCING) {
868 unstack();
869 continue;
871 if ((options & OPT_INTERACTIVE) && ok_blook(ignoreeof)) {
872 printf(_("*ignoreeof* set, use `quit' to quit.\n"));
873 continue;
875 break;
878 temporary_orig_line = ((pstate & PS_SOURCING) ||
879 !(options & OPT_INTERACTIVE)) ? NULL
880 : savestrbuf(ev.ev_line.s, ev.ev_line.l);
881 pstate &= ~PS_HOOK_MASK;
882 if (evaluate(&ev)) {
883 if (pstate & PS_LOADING) /* TODO mess; join PS_EVAL_ERROR.. */
884 rv = FAL0;
885 break;
887 if ((options & OPT_BATCH_FLAG) && ok_blook(batch_exit_on_error)) {
888 if (exit_status != EXIT_OK)
889 break;
890 if ((pstate & (PS_IN_LOAD | PS_EVAL_ERROR)) == PS_EVAL_ERROR) {
891 exit_status = EXIT_ERR;
892 break;
895 if (!(pstate & PS_SOURCING) && (options & OPT_INTERACTIVE)) {
896 if (ev.ev_new_content != NULL)
897 goto jreadline;
898 /* That *can* happen since evaluate() unstack()s on error! */
899 if (temporary_orig_line != NULL)
900 n_tty_addhist(temporary_orig_line, !ev.ev_add_history);
904 if (ev.ev_line.s != NULL)
905 free(ev.ev_line.s);
906 if (pstate & PS_SOURCING)
907 sreset(FAL0);
908 NYD_LEAVE;
909 return rv;
912 FL int
913 execute(char *linebuf, size_t linesize) /* XXX LEGACY */
915 struct eval_ctx ev;
916 int rv;
917 NYD_ENTER;
919 memset(&ev, 0, sizeof ev);
920 ev.ev_line.s = linebuf;
921 ev.ev_line.l = linesize;
922 ev.ev_is_recursive = TRU1;
923 n_COLOUR( n_colour_env_push(); )
924 rv = evaluate(&ev);
925 n_COLOUR( n_colour_env_pop(FAL0); )
927 NYD_LEAVE;
928 return rv;
931 FL int
932 evaluate(struct eval_ctx *evp)
934 struct str line;
935 char _wordbuf[2], *arglist[MAXARGC], *cp, *word;
936 struct cmd_ghost *cg = NULL;
937 struct cmd const *com = NULL;
938 int muvec[2], c, e = 1;
939 NYD_ENTER;
941 line = evp->ev_line; /* XXX don't change original (buffer pointer) */
942 evp->ev_add_history = FAL0;
943 evp->ev_new_content = NULL;
945 /* Command ghosts that refer to shell commands or macro expansion restart */
946 jrestart:
948 /* Strip the white space away from the beginning of the command */
949 for (cp = line.s; whitechar(*cp); ++cp)
951 line.l -= PTR2SIZE(cp - line.s);
953 /* Ignore comments */
954 if (*cp == '#')
955 goto jleave0;
957 /* Handle ! differently to get the correct lexical conventions */
958 if (*cp == '!') {
959 if (pstate & PS_SOURCING) {
960 n_err(_("Can't `!' while `source'ing\n"));
961 goto jleave;
963 c_shell(++cp);
964 evp->ev_add_history = TRU1;
965 goto jleave0;
968 /* Isolate the actual command; since it may not necessarily be
969 * separated from the arguments (as in `p1') we need to duplicate it to
970 * be able to create a NUL terminated version.
971 * We must be aware of several special one letter commands here */
972 arglist[0] = cp;
973 if ((cp = _lex_isolate(cp)) == arglist[0] &&
974 (*cp == '|' || *cp == '~' || *cp == '?'))
975 ++cp;
976 c = (int)PTR2SIZE(cp - arglist[0]);
977 line.l -= c;
978 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : salloc(c +1);
979 memcpy(word, arglist[0], c);
980 word[c] = '\0';
982 /* Look up the command; if not found, bitch.
983 * Normally, a blank command would map to the first command in the
984 * table; while PS_SOURCING, however, we ignore blank lines to eliminate
985 * confusion; act just the same for ghosts */
986 if (*word == '\0') {
987 if ((pstate & PS_SOURCING) || cg != NULL)
988 goto jleave0;
989 com = _cmd_tab + 0;
990 goto jexec;
993 /* If this is the first evaluation, check command ghosts */
994 if (cg == NULL) {
995 /* TODO relink list head, so it's sorted on usage over time?
996 * TODO in fact, there should be one hashmap over all commands and ghosts
997 * TODO so that the lookup could be made much more efficient than it is
998 * TODO now (two adjacent list searches! */
999 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
1000 if (!strcmp(word, cg->name)) {
1001 if (line.l > 0) {
1002 size_t i = cg->cmd.l;
1003 line.s = salloc(i + line.l +1);
1004 memcpy(line.s, cg->cmd.s, i);
1005 memcpy(line.s + i, cp, line.l);
1006 line.s[i += line.l] = '\0';
1007 line.l = i;
1008 } else {
1009 line.s = cg->cmd.s;
1010 line.l = cg->cmd.l;
1012 goto jrestart;
1016 if ((com = _lex(word)) == NULL || com->func == &c_cmdnotsupp) {
1017 bool_t s = condstack_isskip();
1018 if (!s || (options & OPT_D_V))
1019 n_err(_("Unknown command%s: `%s'\n"),
1020 (s ? _(" (conditionally ignored)") : ""), word);
1021 if (s)
1022 goto jleave0;
1023 if (com != NULL) {
1024 c_cmdnotsupp(NULL);
1025 com = NULL;
1027 goto jleave;
1030 /* See if we should execute the command -- if a conditional we always
1031 * execute it, otherwise, check the state of cond */
1032 jexec:
1033 if (!(com->argtype & ARG_F) && condstack_isskip())
1034 goto jleave0;
1036 /* Process the arguments to the command, depending on the type it expects,
1037 * default to error. If we're PS_SOURCING an interactive command: error */
1038 if ((options & OPT_SENDMODE) && !(com->argtype & ARG_M)) {
1039 n_err(_("May not execute `%s' while sending\n"), com->name);
1040 goto jleave;
1042 if ((pstate & PS_SOURCING) && (com->argtype & ARG_I)) {
1043 n_err(_("May not execute `%s' while `source'ing\n"), com->name);
1044 goto jleave;
1046 if (!(mb.mb_perm & MB_DELE) && (com->argtype & ARG_W)) {
1047 n_err(_("May not execute `%s' -- message file is read only\n"),
1048 com->name);
1049 goto jleave;
1051 if (evp->ev_is_recursive && (com->argtype & ARG_R)) {
1052 n_err(_("Cannot recursively invoke `%s'\n"), com->name);
1053 goto jleave;
1055 if (mb.mb_type == MB_VOID && (com->argtype & ARG_A)) {
1056 n_err(_("Cannot execute `%s' without active mailbox\n"), com->name);
1057 goto jleave;
1059 if (com->argtype & ARG_O)
1060 OBSOLETE2(_("this command will be removed"), com->name);
1062 if (com->argtype & ARG_V)
1063 temporary_arg_v_store = NULL;
1065 switch (com->argtype & ARG_ARGMASK) {
1066 case ARG_MSGLIST:
1067 /* Message list defaulting to nearest forward legal message */
1068 if (_msgvec == NULL)
1069 goto je96;
1070 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
1071 break;
1072 if (c == 0) {
1073 *_msgvec = first(com->msgflag, com->msgmask);
1074 if (*_msgvec != 0)
1075 _msgvec[1] = 0;
1077 if (*_msgvec == 0) {
1078 if (!(pstate & PS_HOOK_MASK))
1079 printf(_("No applicable messages\n"));
1080 break;
1082 e = (*com->func)(_msgvec);
1083 break;
1085 case ARG_NDMLIST:
1086 /* Message list with no defaults, but no error if none exist */
1087 if (_msgvec == NULL) {
1088 je96:
1089 n_err(_("Invalid use of \"message list\"\n"));
1090 break;
1092 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
1093 break;
1094 e = (*com->func)(_msgvec);
1095 break;
1097 case ARG_STRLIST:
1098 /* Just the straight string, with leading blanks removed */
1099 while (whitechar(*cp))
1100 ++cp;
1101 e = (*com->func)(cp);
1102 break;
1104 case ARG_RAWLIST:
1105 case ARG_ECHOLIST:
1106 /* A vector of strings, in shell style */
1107 if ((c = getrawlist(cp, line.l, arglist, NELEM(arglist),
1108 ((com->argtype & ARG_ARGMASK) == ARG_ECHOLIST))) < 0)
1109 break;
1110 if (c < com->minargs) {
1111 n_err(_("`%s' requires at least %d arg(s)\n"),
1112 com->name, com->minargs);
1113 break;
1115 if (c > com->maxargs) {
1116 n_err(_("`%s' takes no more than %d arg(s)\n"),
1117 com->name, com->maxargs);
1118 break;
1120 e = (*com->func)(arglist);
1121 break;
1123 case ARG_NOLIST:
1124 /* Just the constant zero, for exiting, eg. */
1125 e = (*com->func)(0);
1126 break;
1128 default:
1129 n_panic(_("Unknown argument type"));
1132 if (e == 0 && (com->argtype & ARG_V) &&
1133 (cp = temporary_arg_v_store) != NULL) {
1134 temporary_arg_v_store = NULL;
1135 evp->ev_new_content = cp;
1136 goto jleave0;
1138 if (!(com->argtype & ARG_H) && !(pstate & PS_MSGLIST_SAW_NO))
1139 evp->ev_add_history = TRU1;
1141 jleave:
1142 /* Exit the current source file on error TODO what a mess! */
1143 if (e == 0)
1144 pstate &= ~PS_EVAL_ERROR;
1145 else {
1146 pstate |= PS_EVAL_ERROR;
1147 if (e < 0 || (pstate & PS_LOADING)) {
1148 e = 1;
1149 goto jret;
1151 if (pstate & PS_SOURCING)
1152 unstack();
1153 goto jret0;
1155 if (com == NULL)
1156 goto jret0;
1157 if ((com->argtype & ARG_P) && ok_blook(autoprint))
1158 if (visible(dot)) {
1159 muvec[0] = (int)PTR2SIZE(dot - message + 1);
1160 muvec[1] = 0;
1161 c_type(muvec); /* TODO what if error? re-eval! */
1163 if (!(pstate & (PS_SOURCING | PS_HOOK_MASK)) && !(com->argtype & ARG_T))
1164 pstate |= PS_SAW_COMMAND;
1165 jleave0:
1166 pstate &= ~PS_EVAL_ERROR;
1167 jret0:
1168 e = 0;
1169 jret:
1170 NYD_LEAVE;
1171 return e;
1174 FL void
1175 setmsize(int sz)
1177 NYD_ENTER;
1178 if (_msgvec != NULL)
1179 free(_msgvec);
1180 _msgvec = scalloc(sz + 1, sizeof *_msgvec);
1181 NYD_LEAVE;
1184 FL void
1185 print_header_summary(char const *Larg)
1187 size_t bot, top, i, j;
1188 NYD_ENTER;
1190 if (Larg != NULL) {
1191 /* Avoid any messages XXX add a make_mua_silent() and use it? */
1192 if ((options & (OPT_VERB | OPT_EXISTONLY)) == OPT_EXISTONLY) {
1193 freopen("/dev/null", "w", stdout);
1194 freopen("/dev/null", "w", stderr);
1196 assert(_msgvec != NULL);
1197 i = (getmsglist(/*TODO make arg const */UNCONST(Larg), _msgvec, 0) <= 0);
1198 if (options & OPT_EXISTONLY) {
1199 exit_status = (int)i;
1200 goto jleave;
1202 if (i)
1203 goto jleave;
1204 for (bot = msgCount, top = 0, i = 0; (j = _msgvec[i]) != 0; ++i) {
1205 if (bot > j)
1206 bot = j;
1207 if (top < j)
1208 top = j;
1210 } else
1211 bot = 1, top = msgCount;
1212 print_headers(bot, top, (Larg != NULL)); /* TODO should take iterator!! */
1213 jleave:
1214 NYD_LEAVE;
1217 FL void
1218 onintr(int s)
1220 NYD_X; /* Signal handler */
1222 if (handlerstacktop != NULL) {
1223 handlerstacktop(s);
1224 return;
1226 safe_signal(SIGINT, onintr);
1227 noreset = 0;
1228 if (!_lex_inithdr)
1229 pstate |= PS_SAW_COMMAND;
1230 _lex_inithdr = 0;
1231 while (pstate & PS_SOURCING)
1232 unstack();
1234 termios_state_reset();
1235 close_all_files();
1237 if (image >= 0) {
1238 close(image);
1239 image = -1;
1241 if (interrupts != 1)
1242 n_err_sighdl(_("Interrupt\n"));
1243 safe_signal(SIGPIPE, _oldpipe);
1244 reset(0);
1247 FL void
1248 announce(int printheaders)
1250 int vec[2], mdot;
1251 NYD_ENTER;
1253 mdot = newfileinfo();
1254 vec[0] = mdot;
1255 vec[1] = 0;
1256 dot = message + mdot - 1;
1257 if (printheaders && msgCount > 0 && ok_blook(header)) {
1258 ++_lex_inithdr;
1259 print_header_group(vec); /* XXX errors? */
1260 _lex_inithdr = 0;
1262 NYD_LEAVE;
1265 FL int
1266 newfileinfo(void)
1268 struct message *mp;
1269 int u, n, mdot, d, s, hidden, moved;
1270 NYD_ENTER;
1272 if (mb.mb_type == MB_VOID) {
1273 mdot = 1;
1274 goto jleave;
1277 mdot = getmdot(0);
1278 s = d = hidden = moved =0;
1279 for (mp = message, n = 0, u = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
1280 if (mp->m_flag & MNEW)
1281 ++n;
1282 if ((mp->m_flag & MREAD) == 0)
1283 ++u;
1284 if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
1285 ++moved;
1286 if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
1287 ++d;
1288 if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
1289 ++s;
1290 if (mp->m_flag & MHIDDEN)
1291 ++hidden;
1294 /* If displayname gets truncated the user effectively has no option to see
1295 * the full pathname of the mailbox, so print it at least for '? fi' */
1296 printf(_("\"%s\": "),
1297 (_update_mailname(NULL) ? displayname : mailname));
1298 if (msgCount == 1)
1299 printf(_("1 message"));
1300 else
1301 printf(_("%d messages"), msgCount);
1302 if (n > 0)
1303 printf(_(" %d new"), n);
1304 if (u-n > 0)
1305 printf(_(" %d unread"), u);
1306 if (d > 0)
1307 printf(_(" %d deleted"), d);
1308 if (s > 0)
1309 printf(_(" %d saved"), s);
1310 if (moved > 0)
1311 printf(_(" %d moved"), moved);
1312 if (hidden > 0)
1313 printf(_(" %d hidden"), hidden);
1314 if (mb.mb_type == MB_CACHE)
1315 printf(" [Disconnected]");
1316 else if (mb.mb_perm == 0)
1317 printf(_(" [Read only]"));
1318 printf("\n");
1319 jleave:
1320 NYD_LEAVE;
1321 return mdot;
1324 FL int
1325 getmdot(int nmail)
1327 struct message *mp;
1328 char *cp;
1329 int mdot;
1330 enum mflag avoid = MHIDDEN | MDELETED;
1331 NYD_ENTER;
1333 if (!nmail) {
1334 if (ok_blook(autothread)) {
1335 OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
1336 c_thread(NULL);
1337 } else if ((cp = ok_vlook(autosort)) != NULL) {
1338 if (mb.mb_sorted != NULL)
1339 free(mb.mb_sorted);
1340 mb.mb_sorted = sstrdup(cp);
1341 c_sort(NULL);
1344 if (mb.mb_type == MB_VOID) {
1345 mdot = 1;
1346 goto jleave;
1349 if (nmail)
1350 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1351 if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
1352 break;
1354 if (!nmail || PTRCMP(mp, >=, message + msgCount)) {
1355 if (mb.mb_threaded) {
1356 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1357 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
1358 break;
1359 } else {
1360 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1361 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
1362 break;
1366 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
1367 if (mb.mb_threaded) {
1368 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1369 if (mp->m_flag & MFLAGGED)
1370 break;
1371 } else {
1372 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1373 if (mp->m_flag & MFLAGGED)
1374 break;
1378 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
1379 if (mb.mb_threaded) {
1380 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1381 if (!(mp->m_flag & (MREAD | avoid)))
1382 break;
1383 } else {
1384 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1385 if (!(mp->m_flag & (MREAD | avoid)))
1386 break;
1390 if (nmail &&
1391 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
1392 mdot = (int)PTR2SIZE(mp - message + 1);
1393 else if (ok_blook(showlast)) {
1394 if (mb.mb_threaded) {
1395 for (mp = this_in_thread(threadroot, -1); mp;
1396 mp = prev_in_thread(mp))
1397 if (!(mp->m_flag & avoid))
1398 break;
1399 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
1400 } else {
1401 for (mp = message + msgCount - 1; mp >= message; --mp)
1402 if (!(mp->m_flag & avoid))
1403 break;
1404 mdot = (mp >= message) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
1406 } else if (!nmail &&
1407 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
1408 mdot = (int)PTR2SIZE(mp - message + 1);
1409 else if (mb.mb_threaded) {
1410 for (mp = threadroot; mp; mp = next_in_thread(mp))
1411 if (!(mp->m_flag & avoid))
1412 break;
1413 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : 1;
1414 } else {
1415 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1416 if (!(mp->m_flag & avoid))
1417 break;
1418 mdot = PTRCMP(mp, <, message + msgCount)
1419 ? (int)PTR2SIZE(mp - message + 1) : 1;
1421 jleave:
1422 NYD_LEAVE;
1423 return mdot;
1426 FL void
1427 initbox(char const *name)
1429 char *tempMesg;
1430 NYD_ENTER;
1432 if (mb.mb_type != MB_VOID)
1433 n_strlcpy(prevfile, mailname, PATH_MAX);
1435 /* TODO name always NE mailname (but goes away for objects anyway)
1436 * TODO Well, not true no more except that in parens */
1437 _update_mailname((name != mailname) ? name : NULL);
1439 if ((mb.mb_otf = Ftmp(&tempMesg, "tmpmbox", OF_WRONLY | OF_HOLDSIGS)) ==
1440 NULL) {
1441 n_perr(_("temporary mail message file"), 0);
1442 exit(EXIT_ERR);
1444 if ((mb.mb_itf = safe_fopen(tempMesg, "r", NULL)) == NULL) {
1445 n_perr(_("temporary mail message file"), 0);
1446 exit(EXIT_ERR);
1448 Ftmp_release(&tempMesg);
1450 message_reset();
1451 mb.mb_threaded = 0;
1452 if (mb.mb_sorted != NULL) {
1453 free(mb.mb_sorted);
1454 mb.mb_sorted = NULL;
1456 #ifdef HAVE_IMAP
1457 mb.mb_flags = MB_NOFLAGS;
1458 #endif
1459 dot = prevdot = threadroot = NULL;
1460 pstate &= ~PS_DID_PRINT_DOT;
1461 NYD_LEAVE;
1464 #ifdef HAVE_DOCSTRINGS
1465 FL bool_t
1466 print_comm_docstr(char const *comm)
1468 struct cmd_ghost const *cg;
1469 struct cmd const *cp, *cpmax;
1470 bool_t rv = FAL0;
1471 NYD_ENTER;
1473 /* Ghosts take precedence */
1474 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
1475 if (!strcmp(comm, cg->name)) {
1476 printf("%s -> ", comm);
1477 comm = cg->cmd.s;
1478 break;
1481 cpmax = (cp = _cmd_tab) + NELEM(_cmd_tab);
1482 jredo:
1483 for (; cp < cpmax; ++cp) {
1484 if (cp->func == &c_cmdnotsupp)
1485 continue;
1486 if (!strcmp(comm, cp->name))
1487 printf("%s: %s\n", comm, V_(cp->doc));
1488 else if (is_prefix(comm, cp->name))
1489 printf("%s (%s): %s\n", comm, cp->name, V_(cp->doc));
1490 else
1491 continue;
1492 rv = TRU1;
1493 break;
1495 if (!rv && cpmax == _cmd_tab + NELEM(_cmd_tab)) {
1496 cpmax = (cp = _special_cmd_tab) + NELEM(_special_cmd_tab);
1497 goto jredo;
1500 if (!rv && cg != NULL) {
1501 printf("\"%s\"\n", comm);
1502 rv = TRU1;
1504 NYD_LEAVE;
1505 return rv;
1507 #endif
1509 /* s-it-mode */