Add *on-compose-{enter,leave}* hooks (Jens Schleusener, Rudolf Sykora)..
[s-mailx.git] / lex.c
blob4f0018059cf8d0e4c939d83cf2f0f7255e041518
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_strscpy(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 default:
530 n_err(_("Cannot handle protocol: %s\n"), name);
531 goto jem1;
534 if ((ibuf = Zopen(name, "r")) == NULL) {
535 if ((fm & FEDIT_SYSBOX) && errno == ENOENT) {
536 if (strcmp(who, myname) && getpwnam(who) == NULL) {
537 n_err(_("\"%s\" is not a user of this system\n"), who);
538 goto jem2;
540 if (!(options & OPT_QUICKRUN_MASK) && ok_blook(bsdcompat))
541 n_err(_("No mail for %s\n"), who);
542 if (fm & FEDIT_NEWMAIL)
543 goto jleave;
544 if (ok_blook(emptystart)) {
545 if (!(options & OPT_QUICKRUN_MASK) && !ok_blook(bsdcompat))
546 n_perr(name, 0);
547 /* We must avoid returning -1 and causing program exit */
548 mb.mb_type = MB_VOID;
549 rv = 1;
550 goto jleave;
552 goto jem2;
553 } else if (fm & FEDIT_NEWMAIL)
554 goto jleave;
555 n_perr(name, 0);
556 goto jem1;
559 if (fstat(fileno(ibuf), &stb) == -1) {
560 if (fm & FEDIT_NEWMAIL)
561 goto jleave;
562 n_perr(_("fstat"), 0);
563 goto jem1;
566 if (S_ISREG(stb.st_mode) || isdevnull) {
567 /* EMPTY */
568 } else {
569 if (fm & FEDIT_NEWMAIL)
570 goto jleave;
571 errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
572 n_perr(name, 0);
573 goto jem1;
576 /* Looks like all will be well. We must now relinquish our hold on the
577 * current set of stuff. Must hold signals while we are reading the new
578 * file, else we will ruin the message[] data structure */
580 hold_sigs(); /* TODO note on this one in quit.c:quit() */
581 if (shudclob && !(fm & FEDIT_NEWMAIL))
582 quit();
583 #ifdef HAVE_SOCKETS
584 if (!(fm & FEDIT_NEWMAIL) && mb.mb_sock.s_fd >= 0)
585 sclose(&mb.mb_sock); /* TODO sorry? VMAILFS->close(), thank you */
586 #endif
588 /* TODO There is no intermediate VOID box we've switched to: name may
589 * TODO point to the same box that we just have written, so any updates
590 * TODO we won't see! Reopen again in this case. RACY! Goes with VOID! */
591 /* TODO In addition: in case of compressed/hook boxes we lock a temporary! */
592 /* TODO We may uselessly open twice but quit() doesn't say wether we were
593 * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
594 if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !strcmp(name, mailname)) {
595 name = mailname;
596 Fclose(ibuf);
598 if ((ibuf = Zopen(name, "r")) == NULL ||
599 fstat(fileno(ibuf), &stb) == -1 ||
600 (!S_ISREG(stb.st_mode) && !isdevnull)) {
601 n_perr(name, 0);
602 rele_sigs();
603 goto jem2;
607 /* Copy the messages into /tmp and set pointers */
608 if (!(fm & FEDIT_NEWMAIL)) {
609 mb.mb_type = MB_FILE;
610 mb.mb_perm = (((options & OPT_R_FLAG) || (fm & FEDIT_RDONLY) ||
611 access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
612 if (shudclob) {
613 if (mb.mb_itf) {
614 fclose(mb.mb_itf);
615 mb.mb_itf = NULL;
617 if (mb.mb_otf) {
618 fclose(mb.mb_otf);
619 mb.mb_otf = NULL;
622 shudclob = 1;
623 if (fm & FEDIT_SYSBOX)
624 pstate &= ~PS_EDIT;
625 else
626 pstate |= PS_EDIT;
627 initbox(name);
628 offset = 0;
629 } else {
630 fseek(mb.mb_otf, 0L, SEEK_END);
631 /* TODO Doing this without holding a lock is.. And not err checking.. */
632 fseek(ibuf, mailsize, SEEK_SET);
633 offset = mailsize;
634 omsgCount = msgCount;
637 if (isdevnull)
638 lckfp = (FILE*)-1;
639 else if (!(pstate & PS_EDIT))
640 lckfp = dot_lock(name, fileno(ibuf), FLT_READ, offset,0,
641 (fm & FEDIT_NEWMAIL ? 0 : 1));
642 else if (file_lock(fileno(ibuf), FLT_READ, offset,0,
643 (fm & FEDIT_NEWMAIL ? 0 : 1)))
644 lckfp = (FILE*)-1;
646 if (lckfp == NULL) {
647 if (!(fm & FEDIT_NEWMAIL)) {
648 char const *emsg = (pstate & PS_EDIT)
649 ? N_("Unable to lock mailbox, aborting operation")
650 : N_("Unable to (dot) lock mailbox, aborting operation");
651 n_perr(V_(emsg), 0);
653 rele_sigs();
654 if (!(fm & FEDIT_NEWMAIL))
655 goto jem1;
656 goto jleave;
659 mailsize = fsize(ibuf);
661 /* TODO This is too simple minded? We should regenerate an index file
662 * TODO to be able to truly tell wether *anything* has changed! */
663 if ((fm & FEDIT_NEWMAIL) && UICMP(z, mailsize, <=, offset)) {
664 rele_sigs();
665 goto jleave;
667 setptr(ibuf, offset);
668 setmsize(msgCount);
669 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
670 mb.mb_threaded = 0;
671 c_sort((void*)-1);
674 Fclose(ibuf);
675 ibuf = NULL;
676 if (lckfp != NULL && lckfp != (FILE*)-1) {
677 Pclose(lckfp, FAL0);
678 /*lckfp = NULL;*/
680 rele_sigs();
682 if (!(fm & FEDIT_NEWMAIL)) {
683 pstate &= ~PS_SAW_COMMAND;
684 pstate |= PS_SETFILE_OPENED;
687 if ((options & OPT_EXISTONLY) && !(options & OPT_HEADERLIST)) {
688 rv = (msgCount == 0);
689 goto jleave;
692 if ((!(pstate & PS_EDIT) || (fm & FEDIT_NEWMAIL)) && msgCount == 0) {
693 if (!(fm & FEDIT_NEWMAIL)) {
694 if (!ok_blook(emptystart))
695 n_err(_("No mail for %s\n"), who);
697 goto jleave;
700 if (fm & FEDIT_NEWMAIL)
701 newmailinfo(omsgCount);
703 rv = 0;
704 jleave:
705 if (ibuf != NULL) {
706 Fclose(ibuf);
707 if (lckfp != NULL && lckfp != (FILE*)-1)
708 Pclose(lckfp, FAL0);
710 NYD_LEAVE;
711 return rv;
712 jem2:
713 mb.mb_type = MB_VOID;
714 jem1:
715 rv = -1;
716 goto jleave;
719 FL int
720 newmailinfo(int omsgCount)
722 int mdot, i;
723 NYD_ENTER;
725 for (i = 0; i < omsgCount; ++i)
726 message[i].m_flag &= ~MNEWEST;
728 if (msgCount > omsgCount) {
729 for (i = omsgCount; i < msgCount; ++i)
730 message[i].m_flag |= MNEWEST;
731 printf(_("New mail has arrived.\n"));
732 if ((i = msgCount - omsgCount) == 1)
733 printf(_("Loaded 1 new message.\n"));
734 else
735 printf(_("Loaded %d new messages.\n"), i);
736 } else
737 printf(_("Loaded %d messages.\n"), msgCount);
739 check_folder_hook(TRU1);
741 mdot = getmdot(1);
743 if (ok_blook(header))
744 print_headers(omsgCount + 1, msgCount, FAL0);
745 NYD_LEAVE;
746 return mdot;
749 FL bool_t
750 commands(void)
752 struct eval_ctx ev;
753 int n;
754 bool_t volatile rv = TRU1;
755 NYD_ENTER;
757 if (!(pstate & PS_SOURCING)) {
758 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
759 safe_signal(SIGINT, onintr);
760 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
761 safe_signal(SIGHUP, hangup);
762 /* TODO We do a lot of redundant signal handling, especially
763 * TODO with the command line editor(s); try to merge this */
764 safe_signal(SIGTSTP, stop);
765 safe_signal(SIGTTOU, stop);
766 safe_signal(SIGTTIN, stop);
768 _oldpipe = safe_signal(SIGPIPE, SIG_IGN);
769 safe_signal(SIGPIPE, _oldpipe);
771 memset(&ev, 0, sizeof ev);
773 setexit();
774 for (;;) {
775 char *temporary_orig_line; /* XXX eval_ctx.ev_line not yet constant */
777 n_COLOUR( n_colour_env_pop(TRU1); )
779 /* TODO Unless we have our signal manager (or however we do it) child
780 * TODO processes may have time slots where their execution isn't
781 * TODO protected by signal handlers (in between start and setup
782 * TODO completed). close_all_files() is only called from onintr()
783 * TODO so those may linger possibly forever */
784 if (!(pstate & PS_SOURCING))
785 close_all_files();
787 interrupts = 0;
788 handlerstacktop = NULL;
790 temporary_localopts_free(); /* XXX intermediate hack */
791 sreset((pstate & PS_SOURCING) != 0);
792 if (!(pstate & PS_SOURCING)) {
793 char *cp;
795 /* TODO Note: this buffer may contain a password. We should redefine
796 * TODO the code flow which has to do that */
797 if ((cp = termios_state.ts_linebuf) != NULL) {
798 termios_state.ts_linebuf = NULL;
799 termios_state.ts_linesize = 0;
800 free(cp); /* TODO pool give-back */
802 /* TODO Due to expand-on-tab of NCL the buffer may grow */
803 if (ev.ev_line.l > LINESIZE * 3) {
804 free(ev.ev_line.s); /* TODO pool! but what? */
805 ev.ev_line.s = NULL;
806 ev.ev_line.l = ev.ev_line_size = 0;
810 if (!(pstate & PS_SOURCING) && (options & OPT_INTERACTIVE)) {
811 char *cp;
813 cp = ok_vlook(newmail);
814 if ((options & OPT_TTYIN) && cp != NULL) {
815 struct stat st;
817 n = (cp != NULL && strcmp(cp, "nopoll"));
818 if ((mb.mb_type == MB_FILE && !stat(mailname, &st) &&
819 st.st_size > mailsize) ||
820 (mb.mb_type == MB_MAILDIR && n != 0)) {
821 size_t odot = PTR2SIZE(dot - message);
822 ui32_t odid = (pstate & PS_DID_PRINT_DOT);
824 if (setfile(mailname,
825 FEDIT_NEWMAIL |
826 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)) < 0) {
827 exit_status |= EXIT_ERR;
828 rv = FAL0;
829 break;
831 dot = message + odot;
832 pstate |= odid;
836 _reset_on_stop = 1;
837 exit_status = EXIT_OK;
840 /* Read a line of commands and handle end of file specially */
841 jreadline:
842 ev.ev_line.l = ev.ev_line_size;
843 n = readline_input(NULL, TRU1, &ev.ev_line.s, &ev.ev_line.l,
844 ev.ev_new_content);
845 ev.ev_line_size = (ui32_t)ev.ev_line.l;
846 ev.ev_line.l = (ui32_t)n;
847 _reset_on_stop = 0;
848 if (n < 0) {
849 /* EOF */
850 if (pstate & PS_LOADING)
851 break;
852 if (pstate & PS_SOURCING) {
853 unstack();
854 continue;
856 if ((options & OPT_INTERACTIVE) && ok_blook(ignoreeof)) {
857 printf(_("*ignoreeof* set, use `quit' to quit.\n"));
858 continue;
860 break;
863 temporary_orig_line = ((pstate & PS_SOURCING) ||
864 !(options & OPT_INTERACTIVE)) ? NULL
865 : savestrbuf(ev.ev_line.s, ev.ev_line.l);
866 pstate &= ~PS_HOOK_MASK;
867 if (evaluate(&ev)) {
868 if (pstate & PS_LOADING) /* TODO mess; join PS_EVAL_ERROR.. */
869 rv = FAL0;
870 break;
872 if ((options & OPT_BATCH_FLAG) && ok_blook(batch_exit_on_error)) {
873 if (exit_status != EXIT_OK)
874 break;
875 if ((pstate & (PS_IN_LOAD | PS_EVAL_ERROR)) == PS_EVAL_ERROR) {
876 exit_status = EXIT_ERR;
877 break;
880 if (!(pstate & PS_SOURCING) && (options & OPT_INTERACTIVE)) {
881 if (ev.ev_new_content != NULL)
882 goto jreadline;
883 /* That *can* happen since evaluate() unstack()s on error! */
884 if (temporary_orig_line != NULL)
885 n_tty_addhist(temporary_orig_line, !ev.ev_add_history);
889 if (ev.ev_line.s != NULL)
890 free(ev.ev_line.s);
891 if (pstate & PS_SOURCING)
892 sreset(FAL0);
893 NYD_LEAVE;
894 return rv;
897 FL int
898 execute(char *linebuf, size_t linesize) /* XXX LEGACY */
900 struct eval_ctx ev;
901 int rv;
902 NYD_ENTER;
904 memset(&ev, 0, sizeof ev);
905 ev.ev_line.s = linebuf;
906 ev.ev_line.l = linesize;
907 ev.ev_is_recursive = TRU1;
908 n_COLOUR( n_colour_env_push(); )
909 rv = evaluate(&ev);
910 n_COLOUR( n_colour_env_pop(FAL0); )
912 NYD_LEAVE;
913 return rv;
916 FL int
917 evaluate(struct eval_ctx *evp)
919 struct str line;
920 char _wordbuf[2], *arglist[MAXARGC], *cp, *word;
921 struct cmd_ghost *cg = NULL;
922 struct cmd const *com = NULL;
923 int muvec[2], c, e = 1;
924 NYD_ENTER;
926 line = evp->ev_line; /* XXX don't change original (buffer pointer) */
927 evp->ev_add_history = FAL0;
928 evp->ev_new_content = NULL;
930 /* Command ghosts that refer to shell commands or macro expansion restart */
931 jrestart:
933 /* Strip the white space away from the beginning of the command */
934 for (cp = line.s; whitechar(*cp); ++cp)
936 line.l -= PTR2SIZE(cp - line.s);
938 /* Ignore comments */
939 if (*cp == '#')
940 goto jleave0;
942 /* Handle ! differently to get the correct lexical conventions */
943 if (*cp == '!') {
944 if (pstate & PS_SOURCING) {
945 n_err(_("Can't `!' while `source'ing\n"));
946 goto jleave;
948 c_shell(++cp);
949 evp->ev_add_history = TRU1;
950 goto jleave0;
953 /* Isolate the actual command; since it may not necessarily be
954 * separated from the arguments (as in `p1') we need to duplicate it to
955 * be able to create a NUL terminated version.
956 * We must be aware of several special one letter commands here */
957 arglist[0] = cp;
958 if ((cp = _lex_isolate(cp)) == arglist[0] &&
959 (*cp == '|' || *cp == '~' || *cp == '?'))
960 ++cp;
961 c = (int)PTR2SIZE(cp - arglist[0]);
962 line.l -= c;
963 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : salloc(c +1);
964 memcpy(word, arglist[0], c);
965 word[c] = '\0';
967 /* Look up the command; if not found, bitch.
968 * Normally, a blank command would map to the first command in the
969 * table; while PS_SOURCING, however, we ignore blank lines to eliminate
970 * confusion; act just the same for ghosts */
971 if (*word == '\0') {
972 if ((pstate & PS_SOURCING) || cg != NULL)
973 goto jleave0;
974 com = _cmd_tab + 0;
975 goto jexec;
978 /* If this is the first evaluation, check command ghosts */
979 if (cg == NULL) {
980 /* TODO relink list head, so it's sorted on usage over time?
981 * TODO in fact, there should be one hashmap over all commands and ghosts
982 * TODO so that the lookup could be made much more efficient than it is
983 * TODO now (two adjacent list searches! */
984 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
985 if (!strcmp(word, cg->name)) {
986 if (line.l > 0) {
987 size_t i = cg->cmd.l;
988 line.s = salloc(i + line.l +1);
989 memcpy(line.s, cg->cmd.s, i);
990 memcpy(line.s + i, cp, line.l);
991 line.s[i += line.l] = '\0';
992 line.l = i;
993 } else {
994 line.s = cg->cmd.s;
995 line.l = cg->cmd.l;
997 goto jrestart;
1001 if ((com = _lex(word)) == NULL || com->func == &c_cmdnotsupp) {
1002 bool_t s = condstack_isskip();
1003 if (!s || (options & OPT_D_V))
1004 n_err(_("Unknown command%s: `%s'\n"),
1005 (s ? _(" (conditionally ignored)") : ""), word);
1006 if (s)
1007 goto jleave0;
1008 if (com != NULL) {
1009 c_cmdnotsupp(NULL);
1010 com = NULL;
1012 goto jleave;
1015 /* See if we should execute the command -- if a conditional we always
1016 * execute it, otherwise, check the state of cond */
1017 jexec:
1018 if (!(com->argtype & ARG_F) && condstack_isskip())
1019 goto jleave0;
1021 /* Process the arguments to the command, depending on the type it expects,
1022 * default to error. If we're PS_SOURCING an interactive command: error */
1023 if ((options & OPT_SENDMODE) && !(com->argtype & ARG_M)) {
1024 n_err(_("May not execute `%s' while sending\n"), com->name);
1025 goto jleave;
1027 if ((pstate & PS_SOURCING) && (com->argtype & ARG_I)) {
1028 n_err(_("May not execute `%s' while `source'ing\n"), com->name);
1029 goto jleave;
1031 if (!(mb.mb_perm & MB_DELE) && (com->argtype & ARG_W)) {
1032 n_err(_("May not execute `%s' -- message file is read only\n"),
1033 com->name);
1034 goto jleave;
1036 if (evp->ev_is_recursive && (com->argtype & ARG_R)) {
1037 n_err(_("Cannot recursively invoke `%s'\n"), com->name);
1038 goto jleave;
1040 if (mb.mb_type == MB_VOID && (com->argtype & ARG_A)) {
1041 n_err(_("Cannot execute `%s' without active mailbox\n"), com->name);
1042 goto jleave;
1044 if (com->argtype & ARG_O)
1045 OBSOLETE2(_("this command will be removed"), com->name);
1047 if (com->argtype & ARG_V)
1048 temporary_arg_v_store = NULL;
1050 switch (com->argtype & ARG_ARGMASK) {
1051 case ARG_MSGLIST:
1052 /* Message list defaulting to nearest forward legal message */
1053 if (_msgvec == NULL)
1054 goto je96;
1055 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
1056 break;
1057 if (c == 0) {
1058 *_msgvec = first(com->msgflag, com->msgmask);
1059 if (*_msgvec != 0)
1060 _msgvec[1] = 0;
1062 if (*_msgvec == 0) {
1063 if (!(pstate & PS_HOOK_MASK))
1064 printf(_("No applicable messages\n"));
1065 break;
1067 e = (*com->func)(_msgvec);
1068 break;
1070 case ARG_NDMLIST:
1071 /* Message list with no defaults, but no error if none exist */
1072 if (_msgvec == NULL) {
1073 je96:
1074 n_err(_("Invalid use of \"message list\"\n"));
1075 break;
1077 if ((c = getmsglist(cp, _msgvec, com->msgflag)) < 0)
1078 break;
1079 e = (*com->func)(_msgvec);
1080 break;
1082 case ARG_STRLIST:
1083 /* Just the straight string, with leading blanks removed */
1084 while (whitechar(*cp))
1085 ++cp;
1086 e = (*com->func)(cp);
1087 break;
1089 case ARG_RAWLIST:
1090 case ARG_ECHOLIST:
1091 /* A vector of strings, in shell style */
1092 if ((c = getrawlist(cp, line.l, arglist, NELEM(arglist),
1093 ((com->argtype & ARG_ARGMASK) == ARG_ECHOLIST))) < 0)
1094 break;
1095 if (c < com->minargs) {
1096 n_err(_("`%s' requires at least %d arg(s)\n"),
1097 com->name, com->minargs);
1098 break;
1100 if (c > com->maxargs) {
1101 n_err(_("`%s' takes no more than %d arg(s)\n"),
1102 com->name, com->maxargs);
1103 break;
1105 e = (*com->func)(arglist);
1106 break;
1108 case ARG_NOLIST:
1109 /* Just the constant zero, for exiting, eg. */
1110 e = (*com->func)(0);
1111 break;
1113 default:
1114 n_panic(_("Unknown argument type"));
1117 if (e == 0 && (com->argtype & ARG_V) &&
1118 (cp = temporary_arg_v_store) != NULL) {
1119 temporary_arg_v_store = NULL;
1120 evp->ev_new_content = cp;
1121 goto jleave0;
1123 if (!(com->argtype & ARG_H) && !(pstate & PS_MSGLIST_SAW_NO))
1124 evp->ev_add_history = TRU1;
1126 jleave:
1127 /* Exit the current source file on error TODO what a mess! */
1128 if (e == 0)
1129 pstate &= ~PS_EVAL_ERROR;
1130 else {
1131 pstate |= PS_EVAL_ERROR;
1132 if (e < 0 || (pstate & PS_LOADING)) {
1133 e = 1;
1134 goto jret;
1136 if (pstate & PS_SOURCING)
1137 unstack();
1138 goto jret0;
1140 if (com == NULL)
1141 goto jret0;
1142 if ((com->argtype & ARG_P) && ok_blook(autoprint))
1143 if (visible(dot)) {
1144 muvec[0] = (int)PTR2SIZE(dot - message + 1);
1145 muvec[1] = 0;
1146 c_type(muvec); /* TODO what if error? re-eval! */
1148 if (!(pstate & (PS_SOURCING | PS_HOOK_MASK)) && !(com->argtype & ARG_T))
1149 pstate |= PS_SAW_COMMAND;
1150 jleave0:
1151 pstate &= ~PS_EVAL_ERROR;
1152 jret0:
1153 e = 0;
1154 jret:
1155 NYD_LEAVE;
1156 return e;
1159 FL void
1160 setmsize(int sz)
1162 NYD_ENTER;
1163 if (_msgvec != NULL)
1164 free(_msgvec);
1165 _msgvec = scalloc(sz + 1, sizeof *_msgvec);
1166 NYD_LEAVE;
1169 FL void
1170 print_header_summary(char const *Larg)
1172 size_t bot, top, i, j;
1173 NYD_ENTER;
1175 if (Larg != NULL) {
1176 /* Avoid any messages XXX add a make_mua_silent() and use it? */
1177 if ((options & (OPT_VERB | OPT_EXISTONLY)) == OPT_EXISTONLY) {
1178 freopen("/dev/null", "w", stdout);
1179 freopen("/dev/null", "w", stderr);
1181 assert(_msgvec != NULL);
1182 i = (getmsglist(/*TODO make arg const */UNCONST(Larg), _msgvec, 0) <= 0);
1183 if (options & OPT_EXISTONLY) {
1184 exit_status = (int)i;
1185 goto jleave;
1187 if (i)
1188 goto jleave;
1189 for (bot = msgCount, top = 0, i = 0; (j = _msgvec[i]) != 0; ++i) {
1190 if (bot > j)
1191 bot = j;
1192 if (top < j)
1193 top = j;
1195 } else
1196 bot = 1, top = msgCount;
1197 print_headers(bot, top, (Larg != NULL)); /* TODO should take iterator!! */
1198 jleave:
1199 NYD_LEAVE;
1202 FL void
1203 onintr(int s)
1205 NYD_X; /* Signal handler */
1207 if (handlerstacktop != NULL) {
1208 handlerstacktop(s);
1209 return;
1211 safe_signal(SIGINT, onintr);
1212 noreset = 0;
1213 if (!_lex_inithdr)
1214 pstate |= PS_SAW_COMMAND;
1215 _lex_inithdr = 0;
1216 while (pstate & PS_SOURCING)
1217 unstack();
1219 termios_state_reset();
1220 close_all_files();
1222 if (image >= 0) {
1223 close(image);
1224 image = -1;
1226 if (interrupts != 1)
1227 n_err_sighdl(_("Interrupt\n"));
1228 safe_signal(SIGPIPE, _oldpipe);
1229 reset(0);
1232 FL void
1233 announce(int printheaders)
1235 int vec[2], mdot;
1236 NYD_ENTER;
1238 mdot = newfileinfo();
1239 vec[0] = mdot;
1240 vec[1] = 0;
1241 dot = message + mdot - 1;
1242 if (printheaders && msgCount > 0 && ok_blook(header)) {
1243 ++_lex_inithdr;
1244 print_header_group(vec); /* XXX errors? */
1245 _lex_inithdr = 0;
1247 NYD_LEAVE;
1250 FL int
1251 newfileinfo(void)
1253 struct message *mp;
1254 int u, n, mdot, d, s, hidden, moved;
1255 NYD_ENTER;
1257 if (mb.mb_type == MB_VOID) {
1258 mdot = 1;
1259 goto jleave;
1262 mdot = getmdot(0);
1263 s = d = hidden = moved =0;
1264 for (mp = message, n = 0, u = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
1265 if (mp->m_flag & MNEW)
1266 ++n;
1267 if ((mp->m_flag & MREAD) == 0)
1268 ++u;
1269 if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
1270 ++moved;
1271 if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
1272 ++d;
1273 if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
1274 ++s;
1275 if (mp->m_flag & MHIDDEN)
1276 ++hidden;
1279 /* If displayname gets truncated the user effectively has no option to see
1280 * the full pathname of the mailbox, so print it at least for '? fi' */
1281 printf(_("\"%s\": "),
1282 (_update_mailname(NULL) ? displayname : mailname));
1283 if (msgCount == 1)
1284 printf(_("1 message"));
1285 else
1286 printf(_("%d messages"), msgCount);
1287 if (n > 0)
1288 printf(_(" %d new"), n);
1289 if (u-n > 0)
1290 printf(_(" %d unread"), u);
1291 if (d > 0)
1292 printf(_(" %d deleted"), d);
1293 if (s > 0)
1294 printf(_(" %d saved"), s);
1295 if (moved > 0)
1296 printf(_(" %d moved"), moved);
1297 if (hidden > 0)
1298 printf(_(" %d hidden"), hidden);
1299 else if (mb.mb_perm == 0)
1300 printf(_(" [Read only]"));
1301 printf("\n");
1302 jleave:
1303 NYD_LEAVE;
1304 return mdot;
1307 FL int
1308 getmdot(int nmail)
1310 struct message *mp;
1311 char *cp;
1312 int mdot;
1313 enum mflag avoid = MHIDDEN | MDELETED;
1314 NYD_ENTER;
1316 if (!nmail) {
1317 if (ok_blook(autothread)) {
1318 OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
1319 c_thread(NULL);
1320 } else if ((cp = ok_vlook(autosort)) != NULL) {
1321 if (mb.mb_sorted != NULL)
1322 free(mb.mb_sorted);
1323 mb.mb_sorted = sstrdup(cp);
1324 c_sort(NULL);
1327 if (mb.mb_type == MB_VOID) {
1328 mdot = 1;
1329 goto jleave;
1332 if (nmail)
1333 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1334 if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
1335 break;
1337 if (!nmail || PTRCMP(mp, >=, message + msgCount)) {
1338 if (mb.mb_threaded) {
1339 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1340 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
1341 break;
1342 } else {
1343 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1344 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
1345 break;
1349 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
1350 if (mb.mb_threaded) {
1351 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1352 if (mp->m_flag & MFLAGGED)
1353 break;
1354 } else {
1355 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1356 if (mp->m_flag & MFLAGGED)
1357 break;
1361 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
1362 if (mb.mb_threaded) {
1363 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1364 if (!(mp->m_flag & (MREAD | avoid)))
1365 break;
1366 } else {
1367 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1368 if (!(mp->m_flag & (MREAD | avoid)))
1369 break;
1373 if (nmail &&
1374 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
1375 mdot = (int)PTR2SIZE(mp - message + 1);
1376 else if (ok_blook(showlast)) {
1377 if (mb.mb_threaded) {
1378 for (mp = this_in_thread(threadroot, -1); mp;
1379 mp = prev_in_thread(mp))
1380 if (!(mp->m_flag & avoid))
1381 break;
1382 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
1383 } else {
1384 for (mp = message + msgCount - 1; mp >= message; --mp)
1385 if (!(mp->m_flag & avoid))
1386 break;
1387 mdot = (mp >= message) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
1389 } else if (!nmail &&
1390 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
1391 mdot = (int)PTR2SIZE(mp - message + 1);
1392 else if (mb.mb_threaded) {
1393 for (mp = threadroot; mp; mp = next_in_thread(mp))
1394 if (!(mp->m_flag & avoid))
1395 break;
1396 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : 1;
1397 } else {
1398 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1399 if (!(mp->m_flag & avoid))
1400 break;
1401 mdot = PTRCMP(mp, <, message + msgCount)
1402 ? (int)PTR2SIZE(mp - message + 1) : 1;
1404 jleave:
1405 NYD_LEAVE;
1406 return mdot;
1409 FL void
1410 initbox(char const *name)
1412 char *tempMesg;
1413 NYD_ENTER;
1415 if (mb.mb_type != MB_VOID)
1416 n_strscpy(prevfile, mailname, PATH_MAX);
1418 /* TODO name always NE mailname (but goes away for objects anyway)
1419 * TODO Well, not true no more except that in parens */
1420 _update_mailname((name != mailname) ? name : NULL);
1422 if ((mb.mb_otf = Ftmp(&tempMesg, "tmpmbox", OF_WRONLY | OF_HOLDSIGS)) ==
1423 NULL) {
1424 n_perr(_("temporary mail message file"), 0);
1425 exit(EXIT_ERR);
1427 if ((mb.mb_itf = safe_fopen(tempMesg, "r", NULL)) == NULL) {
1428 n_perr(_("temporary mail message file"), 0);
1429 exit(EXIT_ERR);
1431 Ftmp_release(&tempMesg);
1433 message_reset();
1434 mb.mb_threaded = 0;
1435 if (mb.mb_sorted != NULL) {
1436 free(mb.mb_sorted);
1437 mb.mb_sorted = NULL;
1439 dot = prevdot = threadroot = NULL;
1440 pstate &= ~PS_DID_PRINT_DOT;
1441 NYD_LEAVE;
1444 #ifdef HAVE_DOCSTRINGS
1445 FL bool_t
1446 print_comm_docstr(char const *comm)
1448 struct cmd_ghost const *cg;
1449 struct cmd const *cp, *cpmax;
1450 bool_t rv = FAL0;
1451 NYD_ENTER;
1453 /* Ghosts take precedence */
1454 for (cg = _cmd_ghosts; cg != NULL; cg = cg->next)
1455 if (!strcmp(comm, cg->name)) {
1456 printf("%s -> ", comm);
1457 comm = cg->cmd.s;
1458 break;
1461 cpmax = (cp = _cmd_tab) + NELEM(_cmd_tab);
1462 jredo:
1463 for (; cp < cpmax; ++cp) {
1464 if (!strcmp(comm, cp->name))
1465 printf("%s: %s\n", comm, V_(cp->doc));
1466 else if (is_prefix(comm, cp->name))
1467 printf("%s (%s): %s\n", comm, cp->name, V_(cp->doc));
1468 else
1469 continue;
1470 rv = TRU1;
1471 break;
1473 if (!rv && cpmax == _cmd_tab + NELEM(_cmd_tab)) {
1474 cpmax = (cp = _special_cmd_tab) + NELEM(_special_cmd_tab);
1475 goto jredo;
1478 if (!rv && cg != NULL) {
1479 printf("\"%s\"\n", comm);
1480 rv = TRU1;
1482 NYD_LEAVE;
1483 return rv;
1485 #endif
1487 /* s-it-mode */