C****Y BSD signal handling! Try improve ^C stability..
[s-mailx.git] / go.c
blob656b786e64a3960ac1331806bd8a4f32968a82ff
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Program input of all sorts, input lexing, event loops, command evaluation.
3 *@ TODO n_PS_ROBOT requires yet n_PS_SOURCING, which REALLY sucks.
4 *@ TODO Commands and ghosts deserve a tree. Or so.
6 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
7 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8 */
9 /*
10 * Copyright (c) 1980, 1993
11 * The Regents of the University of California. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. Neither the name of the University nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
37 #undef n_FILE
38 #define n_FILE go
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 enum a_go_flags{
45 a_GO_NONE,
46 a_GO_FREE = 1u<<0, /* Structure was allocated, n_free() it */
47 a_GO_PIPE = 1u<<1, /* Open on a pipe */
48 a_GO_FILE = 1u<<2, /* Loading or sourcing a file */
49 a_GO_MACRO = 1u<<3, /* Running a macro */
50 a_GO_MACRO_FREE_DATA = 1u<<4, /* Lines are allocated, n_free() once done */
51 /* TODO For simplicity this is yet _MACRO plus specialization overlay
52 * TODO (_X_OPTION, _CMD) -- these should be types on their own! */
53 a_GO_MACRO_X_OPTION = 1u<<5, /* Macro indeed command line -X option */
54 a_GO_MACRO_CMD = 1u<<6, /* Macro indeed single-line: ~:COMMAND */
55 /* TODO a_GO_SPLICE: the right way to support *on-compose-splice(-shell)?*
56 * TODO would be a command_loop object that emits an on_read_line event, and
57 * TODO have a special handler for the compose mode; with that, then,
58 * TODO _event_loop() would not call _evaluate() but CTX->on_read_line,
59 * TODO and _evaluate() would be the standard impl.,
60 * TODO whereas the COMMAND ESCAPE switch in collect.c would be another one.
61 * TODO With this generic accmacvar.c:temporary_compose_mode_hook_call()
62 * TODO could be dropped, and n_go_macro() could become extended,
63 * TODO and/or we would add a n_go_anything(), which would allow special
64 * TODO input handlers, special I/O input and output, special `localopts'
65 * TODO etc., to be glued to the new execution context. And all I/O all
66 * TODO over this software should not use stdin/stdout, but CTX->in/out.
67 * TODO The pstate must be a property of the current execution context, too.
68 * TODO This not today. :( For now we invent a special SPLICE execution
69 * TODO context overlay that at least allows to temporarily modify the
70 * TODO global pstate, and the global stdin and stdout pointers. HACK!
71 * TODO This splice thing is very special and has to go again. HACK!!
72 * TODO a_go_input() will drop it once it sees EOF (HACK!), but care for
73 * TODO jumps must be taken by splice creators. HACK!!! But works. ;} */
74 a_GO_SPLICE = 1u<<7,
75 /* If it is none of those, it must be the outermost, the global one */
76 a_GO_TYPE_MASK = a_GO_PIPE | a_GO_FILE | a_GO_MACRO |
77 /* a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD | */ a_GO_SPLICE,
79 a_GO_FORCE_EOF = 1u<<8, /* go_input() shall return EOF next */
81 a_GO_SUPER_MACRO = 1u<<16, /* *Not* inheriting n_PS_SOURCING state */
82 /* This context has inherited the memory pool from its parent.
83 * In practice only used for resource file loading and -X args, which enter
84 * a top level n_go_main_loop() and should (re)use the in practice already
85 * allocated memory pool of the global context */
86 a_GO_MEMPOOL_INHERITED = 1u<<17,
88 /* `xcall' optimization barrier: n_go_macro() has been finished with
89 * a `xcall' request, and `xcall' set this in the parent a_go_input of the
90 * said n_go_macro() to indicate a barrier: we teardown the a_go_input of
91 * the n_go_macro() away after leaving its _event_loop(), but then,
92 * back in n_go_macro(), that enters a for(;;) loop that directly calls
93 * c_call() -- our `xcall' stack avoidance optimization --, yet this call
94 * will itself end up in a new n_go_macro(), and if that again ends up with
95 * `xcall' this should teardown and leave its own n_go_macro(), unrolling the
96 * stack "up to the barrier level", but which effectively still is the
97 * n_go_macro() that lost its a_go_input and is looping the `xcall'
98 * optimization loop. If no `xcall' is desired that loop is simply left and
99 * the _event_loop() of the outer a_go_ctx will perform a loop tick and
100 * clear this bit again OR become teardown itself */
101 a_GO_XCALL_LOOP = 1u<<24 /* `xcall' optimization barrier level */
104 enum a_go_cleanup_mode{
105 a_GO_CLEANUP_UNWIND = 1u<<0, /* Teardown all contexts except outermost */
106 a_GO_CLEANUP_TEARDOWN = 1u<<1, /* Teardown current context */
107 a_GO_CLEANUP_LOOPTICK = 1u<<2, /* Normal looptick cleanup */
108 a_GO_CLEANUP_MODE_MASK = n_BITENUM_MASK(0, 2),
110 a_GO_CLEANUP_ERROR = 1u<<8, /* Error occurred on level */
111 a_GO_CLEANUP_SIGINT = 1u<<9, /* Interrupt signal received */
112 a_GO_CLEANUP_HOLDALLSIGS = 1u<<10 /* hold_all_sigs() active TODO */
115 struct a_go_cmd_desc{
116 char const *gcd_name; /* Name of command */
117 int (*gcd_func)(void*); /* Implementor of command */
118 enum n_cmd_arg_flags gcd_caflags;
119 si16_t gcd_msgflag; /* Required flags of msgs */
120 si16_t gcd_msgmask; /* Relevant flags of msgs */
121 #ifdef HAVE_DOCSTRINGS
122 char const *gcd_doc; /* One line doc for command */
123 #endif
125 /* Yechh, can't initialize unions */
126 #define gcd_minargs gcd_msgflag /* Minimum argcount for WYSH/WYRA/RAWLIST */
127 #define gcd_maxargs gcd_msgmask /* Max argcount for WYSH/WYRA/RAWLIST */
129 struct a_go_ghost{ /* TODO binary search */
130 struct a_go_ghost *gg_next;
131 struct str gg_cmd; /* Data follows after .gg_name */
132 char gg_name[n_VFIELD_SIZE(0)];
135 struct a_go_eval_ctx{
136 struct str gec_line; /* The terminated data to _evaluate() */
137 ui32_t gec_line_size; /* May be used to store line memory size */
138 ui8_t gec__dummy[2];
139 bool_t gec_ever_seen; /* Has ever been used (main_loop() only) */
140 bool_t gec_add_history; /* Add command to history (TRUM1=gabby)? */
143 struct a_go_input_inject{
144 struct a_go_input_inject *gii_next;
145 size_t gii_len;
146 bool_t gii_commit;
147 char gii_dat[n_VFIELD_SIZE(7)];
150 struct a_go_ctx{
151 struct a_go_ctx *gc_outer;
152 sigset_t gc_osigmask;
153 ui32_t gc_flags; /* enum a_go_flags */
154 ui32_t gc_loff; /* Pseudo (macro): index in .gc_lines */
155 char **gc_lines; /* Pseudo content, lines unfolded */
156 FILE *gc_file; /* File we were in, if applicable */
157 struct a_go_input_inject *gc_inject; /* To be consumed first */
158 void (*gc_on_finalize)(void *);
159 void *gc_finalize_arg;
160 sigjmp_buf gc_eloop_jmp; /* TODO one day... for _event_loop() */
161 /* SPLICE hacks: saved stdin/stdout, saved pstate */
162 FILE *gc_splice_stdin;
163 FILE *gc_splice_stdout;
164 ui32_t gc_splice_psonce;
165 ui8_t gc_splice__dummy[4];
166 struct n_go_data_ctx gc_data;
167 char gc_name[n_VFIELD_SIZE(0)]; /* Name of file or macro */
170 struct a_go_xcall{
171 struct a_go_ctx *gx_upto; /* Unroll stack up to this level */
172 size_t gx_buflen; /* ARGV requires that much bytes, all in all */
173 size_t gx_argc;
174 struct str gx_argv[n_VFIELD_SIZE(0)];
177 static sighandler_type a_go_oldpipe;
178 static struct a_go_ghost *a_go_ghosts;
179 /* a_go_cmd_tab[] after fun protos */
181 /* Our current execution context, and the buffer backing the outermost level */
182 static struct a_go_ctx *a_go_ctx;
184 #define a_GO_MAINCTX_NAME "Main event loop"
185 static union{
186 ui64_t align;
187 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
188 sizeof(a_GO_MAINCTX_NAME)];
189 } a_go__mainctx_b;
191 /* `xcall' stack-avoidance bypass optimization */
192 static struct a_go_xcall *a_go_xcall;
194 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
196 /* Isolate the command from the arguments */
197 static char *a_go_isolate(char const *comm);
199 /* `eval' */
200 static int a_go_c_eval(void *vp);
202 /* `xcall' */
203 static int a_go_c_xcall(void *vp);
205 /* Command ghost handling */
206 static int a_go_c_ghost(void *vp);
207 static int a_go_c_unghost(void *vp);
209 /* Create a multiline info string about all known additional infos for lcp */
210 #ifdef HAVE_DOCSTRINGS
211 static char const *a_go_cmdinfo(struct a_go_cmd_desc const *gcdp);
212 #endif
214 /* Print a list of all commands */
215 static int a_go_c_list(void *vp);
217 static int a_go__pcmd_cmp(void const *s1, void const *s2);
219 /* `help' / `?' command */
220 static int a_go_c_help(void *vp);
222 /* `exit' and `quit' commands */
223 static int a_go_c_exit(void *vp);
224 static int a_go_c_quit(void *vp);
226 /* Print the version number of the binary */
227 static int a_go_c_version(void *vp);
229 static int a_go__version_cmp(void const *s1, void const *s2);
231 /* n_PS_STATE_PENDMASK requires some actions */
232 static void a_go_update_pstate(void);
234 /* Evaluate a single command.
235 * .gec_add_history will be updated upon success.
236 * TODO Command functions return 0 for success, 1 for error, and -1 for abort.
237 * 1 or -1 aborts a load or source, a -1 aborts the interactive command loop */
238 static int a_go_evaluate(struct a_go_eval_ctx *gecp);
240 /* Get first-fit, or NULL */
241 static struct a_go_cmd_desc const *a_go__firstfit(char const *comm);
243 /* Branch here on hangup signal and simulate "exit" */
244 static void a_go_hangup(int s);
246 /* The following gets called on receipt of an interrupt */
247 static void a_go_onintr(int s);
249 /* Cleanup current execution context, update the program state.
250 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
251 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we hold_all_sigs() */
252 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
254 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
255 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
256 * upon evaluation error, and TRU1 on success */
257 static bool_t a_go_file(char const *file, bool_t silent_open_error);
259 /* System resource file load()ing or -X command line option array traversal */
260 static bool_t a_go_load(struct a_go_ctx *gcp);
262 /* A simplified command loop for recursed state machines */
263 static bool_t a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
265 /* `read' */
266 static int a_go_c_read(void *vp);
268 static bool_t a_go__read_set(char const *cp, char const *value);
270 /* List of all commands, and list of commands which are specially treated
271 * and deduced in _evaluate(), but must offer normal descriptions for others */
272 #ifdef HAVE_DOCSTRINGS
273 # define DS(S) , S
274 #else
275 # define DS(S)
276 #endif
277 static struct a_go_cmd_desc const a_go_cmd_tab[] = {
278 #include "cmd-tab.h"
280 a_go_special_cmd_tab[] = {
281 { "#", NULL, n_CMD_ARG_TYPE_STRING, 0, 0
282 DS(N_("Comment command: ignore remaining (continuable) line")) },
283 { "-", NULL, n_CMD_ARG_TYPE_WYSH, 0, 0
284 DS(N_("Print out the preceding message")) }
286 #undef DS
288 static char *
289 a_go_isolate(char const *comm){
290 NYD2_ENTER;
291 while(*comm != '\0' &&
292 strchr("~|? \t0123456789&%@$^.:/-+*'\",;(`", *comm) == NULL)
293 ++comm;
294 NYD2_LEAVE;
295 return n_UNCONST(comm);
298 static int
299 a_go_c_eval(void *vp){
300 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
301 * TODO ARGV with shell rules, but if that is not possible then simply
302 * TODO adjust argv/argc of "the CmdCtx" that we will have "exec" real cmd */
303 struct n_string s_b, *sp;
304 si32_t rv;
305 size_t i, j;
306 char const **argv, *cp;
307 NYD_ENTER;
309 argv = vp;
311 for(j = i = 0; (cp = argv[i]) != NULL; ++i)
312 j += strlen(cp);
314 sp = n_string_creat_auto(&s_b);
315 sp = n_string_reserve(sp, j);
317 for(i = 0; (cp = argv[i]) != NULL; ++i){
318 if(i > 0)
319 sp = n_string_push_c(sp, ' ');
320 sp = n_string_push_cp(sp, cp);
323 /* TODO HACK! We should inherit the current n_go_input_flags via CmdCtx,
324 * TODO for now we don't have such sort of! n_PS_COMPOSE_MODE is a hack
325 * TODO by itself, since ever: misuse the hack for a hack.
326 * TODO Further more, exit handling is very grazy */
327 (void)/*XXX*/n_go_command((n_pstate & n_PS_COMPOSE_MODE
328 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT), n_string_cp(sp));
330 if(a_go_xcall != NULL)
331 rv = 0;
332 else{
333 cp = ok_vlook(__qm);
334 if(cp == n_0) /* This is a hack, but since anything is a hack, be hacky */
335 rv = 0;
336 else if(cp == n_1)
337 rv = 1;
338 else if(cp == n_m1)
339 rv = -1;
340 else
341 n_idec_si32_cp(&rv, cp, 10, NULL);
343 NYD_LEAVE;
344 return rv;
347 static int
348 a_go_c_xcall(void *vp){
349 struct a_go_xcall *gxp;
350 size_t i, j;
351 char *xcp;
352 char const **oargv, *cp;
353 int rv;
354 struct a_go_ctx *gcp;
355 NYD2_ENTER;
357 /* The context can only be a macro context, except that possibly a single
358 * level of `eval' (TODO: yet) was used to double-expand our arguments */
359 if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
360 gcp = gcp->gc_outer;
361 if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_CMD)) != a_GO_MACRO)
362 goto jerr;
364 /* Try to roll up the stack as much as possible.
365 * See a_GO_XCALL_LOOP flag description for more */
366 if(gcp->gc_outer != NULL){
367 if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
368 gcp = gcp->gc_outer;
369 }else{
370 /* Otherwise this macro is invoked from the top level, in which case we
371 * silently act as if we were `call'... */
372 rv = c_call(vp);
373 /* ...which means we must ensure the rest of the macro that was us
374 * doesn't become evaluated! */
375 a_go_xcall = (struct a_go_xcall*)-1;
376 goto jleave;
379 oargv = vp;
381 for(j = i = 0; (cp = oargv[i]) != NULL; ++i)
382 j += strlen(cp) +1;
384 gxp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_xcall, gx_argv) +
385 (sizeof(struct str) * i) + ++j);
386 gxp->gx_upto = gcp;
387 gxp->gx_buflen = (sizeof(char*) * (i + 1)) + j; /* ARGV inc. strings! */
388 gxp->gx_argc = i;
389 xcp = (char*)&gxp->gx_argv[i];
391 for(i = 0; (cp = oargv[i]) != NULL; ++i){
392 gxp->gx_argv[i].l = j = strlen(cp);
393 gxp->gx_argv[i].s = xcp;
394 memcpy(xcp, cp, ++j);
395 xcp += j;
398 a_go_xcall = gxp;
399 rv = 0;
400 jleave:
401 NYD2_LEAVE;
402 return rv;
403 jerr:
404 n_err(_("`xcall': can only be used inside a macro\n"));
405 rv = 1;
406 goto jleave;
409 static int
410 a_go_c_ghost(void *vp){
411 struct a_go_ghost *lggp, *ggp;
412 size_t i, cl, nl;
413 char *cp;
414 char const **argv;
415 NYD_ENTER;
417 argv = vp;
419 /* Show the list? */
420 if(*argv == NULL){
421 FILE *fp;
423 if((fp = Ftmp(NULL, "ghost", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
424 fp = n_stdout;
426 for(i = 0, ggp = a_go_ghosts; ggp != NULL; ggp = ggp->gg_next)
427 fprintf(fp, "wysh ghost %s %s\n",
428 ggp->gg_name, n_shexp_quote_cp(ggp->gg_cmd.s, TRU1));
430 if(fp != n_stdout){
431 page_or_print(fp, i);
432 Fclose(fp);
434 goto jleave;
437 /* Verify the ghost name is a valid one, and not a command modifier */
438 if(*argv[0] == '\0' || *a_go_isolate(argv[0]) != '\0' ||
439 !asccasecmp(argv[0], "ignerr") || !asccasecmp(argv[0], "wysh") ||
440 !asccasecmp(argv[0], "vput")){
441 n_err(_("`ghost': can't canonicalize %s\n"),
442 n_shexp_quote_cp(argv[0], FAL0));
443 vp = NULL;
444 goto jleave;
447 /* Show command of single ghost? */
448 if(argv[1] == NULL){
449 for(ggp = a_go_ghosts; ggp != NULL; ggp = ggp->gg_next)
450 if(!strcmp(argv[0], ggp->gg_name)){
451 fprintf(n_stdout, "wysh ghost %s %s\n",
452 ggp->gg_name, n_shexp_quote_cp(ggp->gg_cmd.s, TRU1));
453 goto jleave;
455 n_err(_("`ghost': no such alias: %s\n"), argv[0]);
456 vp = NULL;
457 goto jleave;
460 /* Define command for ghost: verify command content */
461 for(cl = 0, i = 1; (cp = n_UNCONST(argv[i])) != NULL; ++i)
462 if(*cp != '\0')
463 cl += strlen(cp) +1; /* SP or NUL */
464 if(cl == 0){
465 n_err(_("`ghost': empty command arguments after %s\n"), argv[0]);
466 vp = NULL;
467 goto jleave;
470 /* If the ghost already exists, recreate */
471 for(lggp = NULL, ggp = a_go_ghosts; ggp != NULL;
472 lggp = ggp, ggp = ggp->gg_next)
473 if(!strcmp(ggp->gg_name, argv[0])){
474 if(lggp != NULL)
475 lggp->gg_next = ggp->gg_next;
476 else
477 a_go_ghosts = ggp->gg_next;
478 n_free(ggp);
479 break;
482 nl = strlen(argv[0]) +1;
483 ggp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ghost, gg_name) + nl + cl);
484 ggp->gg_next = a_go_ghosts;
485 a_go_ghosts = ggp;
486 memcpy(ggp->gg_name, argv[0], nl);
487 cp = ggp->gg_cmd.s = &ggp->gg_name[nl];
488 ggp->gg_cmd.l = --cl;
490 while(*++argv != NULL)
491 if((i = strlen(*argv)) > 0){
492 memcpy(cp, *argv, i);
493 cp += i;
494 *cp++ = ' ';
496 *--cp = '\0';
497 jleave:
498 NYD_LEAVE;
499 return (vp == NULL);
502 static int
503 a_go_c_unghost(void *vp){
504 struct a_go_ghost *lggp, *ggp;
505 char const **argv, *cp;
506 int rv;
507 NYD_ENTER;
509 rv = 0;
510 argv = vp;
512 while((cp = *argv++) != NULL){
513 if(cp[0] == '*' && cp[1] == '\0'){
514 while((ggp = a_go_ghosts) != NULL){
515 a_go_ghosts = ggp->gg_next;
516 n_free(ggp);
518 }else{
519 for(lggp = NULL, ggp = a_go_ghosts; ggp != NULL;
520 lggp = ggp, ggp = ggp->gg_next)
521 if(!strcmp(ggp->gg_name, cp)){
522 if(lggp != NULL)
523 lggp->gg_next = ggp->gg_next;
524 else
525 a_go_ghosts = ggp->gg_next;
526 n_free(ggp);
527 goto jouter;
529 n_err(_("`unghost': no such alias: %s\n"),
530 n_shexp_quote_cp(cp, FAL0));
531 rv = 1;
532 jouter: ;
535 NYD_LEAVE;
536 return rv;
539 #ifdef HAVE_DOCSTRINGS
540 static char const *
541 a_go_cmdinfo(struct a_go_cmd_desc const *gcdp){
542 struct n_string rvb, *rv;
543 char const *cp;
544 NYD2_ENTER;
546 rv = n_string_creat_auto(&rvb);
547 rv = n_string_reserve(rv, 80);
549 switch(gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK){
550 case n_CMD_ARG_TYPE_MSGLIST:
551 cp = N_("message-list");
552 break;
553 case n_CMD_ARG_TYPE_STRING:
554 case n_CMD_ARG_TYPE_RAWDAT:
555 cp = N_("string data");
556 break;
557 case n_CMD_ARG_TYPE_RAWLIST:
558 cp = N_("old-style quoting");
559 break;
560 case n_CMD_ARG_TYPE_NDMLIST:
561 cp = N_("message-list (no default)");
562 break;
563 case n_CMD_ARG_TYPE_WYRA:
564 cp = N_("`wysh' for sh(1)ell-style quoting");
565 break;
566 default:
567 case n_CMD_ARG_TYPE_WYSH:
568 cp = (gcdp->gcd_minargs == 0 && gcdp->gcd_maxargs == 0)
569 ? N_("sh(1)ell-style quoting (takes no arguments)")
570 : N_("sh(1)ell-style quoting");
571 break;
573 rv = n_string_push_cp(rv, V_(cp));
575 if(gcdp->gcd_caflags & n_CMD_ARG_V)
576 rv = n_string_push_cp(rv, _(" | vput modifier"));
577 if(gcdp->gcd_caflags & n_CMD_ARG_EM)
578 rv = n_string_push_cp(rv, _(" | status in *!*"));
580 if(gcdp->gcd_caflags & n_CMD_ARG_A)
581 rv = n_string_push_cp(rv, _(" | needs box"));
582 if(gcdp->gcd_caflags & n_CMD_ARG_I)
583 rv = n_string_push_cp(rv, _(" | ok: batch or interactive"));
584 if(gcdp->gcd_caflags & n_CMD_ARG_M)
585 rv = n_string_push_cp(rv, _(" | ok: send mode"));
586 if(gcdp->gcd_caflags & n_CMD_ARG_R)
587 rv = n_string_push_cp(rv, _(" | not ok: compose mode"));
588 if(gcdp->gcd_caflags & n_CMD_ARG_S)
589 rv = n_string_push_cp(rv, _(" | not ok: during startup"));
590 if(gcdp->gcd_caflags & n_CMD_ARG_X)
591 rv = n_string_push_cp(rv, _(" | ok: in subprocess"));
593 if(gcdp->gcd_caflags & n_CMD_ARG_G)
594 rv = n_string_push_cp(rv, _(" | gabby history"));
596 cp = n_string_cp(rv);
597 NYD2_LEAVE;
598 return cp;
600 #endif /* HAVE_DOCSTRINGS */
602 static int
603 a_go_c_list(void *vp){
604 FILE *fp;
605 struct a_go_cmd_desc const **gcdpa, *gcdp, **gcdpa_curr;
606 size_t i, scrwid, l;
607 NYD_ENTER;
609 i = n_NELEM(a_go_cmd_tab) + n_NELEM(a_go_special_cmd_tab) +1;
610 gcdpa = n_autorec_alloc(sizeof(gcdp) * i);
612 for(i = 0; i < n_NELEM(a_go_cmd_tab); ++i)
613 gcdpa[i] = &a_go_cmd_tab[i];
614 /* C99 */{
615 size_t j;
617 for(j = 0; j < n_NELEM(a_go_special_cmd_tab); ++i, ++j)
618 gcdpa[i] = &a_go_special_cmd_tab[j];
620 gcdpa[i] = NULL;
622 if(*(void**)vp == NULL)
623 qsort(gcdpa, i, sizeof(*gcdpa), &a_go__pcmd_cmp);
625 if((fp = Ftmp(NULL, "list", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
626 fp = n_stdout;
628 fprintf(fp, _("Commands are:\n"));
629 scrwid = n_SCRNWIDTH_FOR_LISTS;
630 l = 1;
631 for(i = 0, gcdpa_curr = gcdpa; (gcdp = *gcdpa_curr++) != NULL;){
632 if(gcdp->gcd_func == &c_cmdnotsupp)
633 continue;
634 if(n_poption & n_PO_D_V){
635 fprintf(fp, "%s\n", gcdp->gcd_name);
636 ++l;
637 #ifdef HAVE_DOCSTRINGS
638 fprintf(fp, " : %s\n", V_(gcdp->gcd_doc));
639 ++l;
640 fprintf(fp, " : %s\n", a_go_cmdinfo(gcdp));
641 ++l;
642 #endif
643 }else{
644 size_t j;
646 if((i += (j = strlen(gcdp->gcd_name) + 2)) > scrwid){
647 i = j;
648 fprintf(fp, "\n");
649 ++l;
651 fprintf(fp, (*gcdpa_curr != NULL ? "%s, " : "%s\n"), gcdp->gcd_name);
655 if(fp != n_stdout){
656 page_or_print(fp, l);
657 Fclose(fp);
659 NYD_LEAVE;
660 return 0;
663 static int
664 a_go__pcmd_cmp(void const *s1, void const *s2){
665 struct a_go_cmd_desc const * const *gcdpa1, * const *gcdpa2;
666 int rv;
667 NYD2_ENTER;
669 gcdpa1 = s1;
670 gcdpa2 = s2;
671 rv = strcmp((*gcdpa1)->gcd_name, (*gcdpa2)->gcd_name);
672 NYD2_LEAVE;
673 return rv;
676 static int
677 a_go_c_help(void *vp){
678 int rv;
679 char *arg;
680 NYD_ENTER;
682 /* Help for a single command? */
683 if((arg = *(char**)vp) != NULL){
684 struct a_go_ghost const *ggp;
685 struct a_go_cmd_desc const *gcdp, *gcdp_max;
687 /* Ghosts take precedence */
688 for(ggp = a_go_ghosts; ggp != NULL; ggp = ggp->gg_next)
689 if(!strcmp(arg, ggp->gg_name)){
690 fprintf(n_stdout, "%s -> ", arg);
691 arg = ggp->gg_cmd.s;
692 break;
695 gcdp_max = &(gcdp = a_go_cmd_tab)[n_NELEM(a_go_cmd_tab)];
696 jredo:
697 for(; gcdp < gcdp_max; ++gcdp){
698 if(is_prefix(arg, gcdp->gcd_name)){
699 fputs(arg, n_stdout);
700 if(strcmp(arg, gcdp->gcd_name))
701 fprintf(n_stdout, " (%s)", gcdp->gcd_name);
702 }else
703 continue;
705 #ifdef HAVE_DOCSTRINGS
706 fprintf(n_stdout, ": %s", V_(gcdp->gcd_doc));
707 if(n_poption & n_PO_D_V)
708 fprintf(n_stdout, "\n : %s", a_go_cmdinfo(gcdp));
709 #endif
710 putc('\n', n_stdout);
711 rv = 0;
712 goto jleave;
715 if(gcdp_max == &a_go_cmd_tab[n_NELEM(a_go_cmd_tab)]){
716 gcdp_max = &(gcdp =
717 a_go_special_cmd_tab)[n_NELEM(a_go_special_cmd_tab)];
718 goto jredo;
721 if(ggp != NULL){
722 fprintf(n_stdout, "%s\n", n_shexp_quote_cp(arg, TRU1));
723 rv = 0;
724 }else{
725 n_err(_("Unknown command: `%s'\n"), arg);
726 rv = 1;
728 }else{
729 /* Very ugly, but take care for compiler supported string lengths :( */
730 fputs(n_progname, n_stdout);
731 fputs(_(
732 " commands -- <msglist> denotes message specifications,\n"
733 "e.g., 1-5, :n or ., separated by spaces:\n"), n_stdout);
734 fputs(_(
735 "\n"
736 "type <msglist> type (`print') messages (honour `headerpick' etc.)\n"
737 "Type <msglist> like `type' but always show all headers\n"
738 "next goto and type next message\n"
739 "from <msglist> (search and) print header summary for the given list\n"
740 "headers header summary for messages surrounding \"dot\"\n"
741 "delete <msglist> delete messages (can be `undelete'd)\n"),
742 n_stdout);
744 fputs(_(
745 "\n"
746 "save <msglist> folder append messages to folder and mark as saved\n"
747 "copy <msglist> folder like `save', but don't mark them (`move' moves)\n"
748 "write <msglist> file write message contents to file (prompts for parts)\n"
749 "Reply <msglist> reply to message senders only\n"
750 "reply <msglist> like `Reply', but address all recipients\n"
751 "Lreply <msglist> forced mailing-list `reply' (see `mlist')\n"),
752 n_stdout);
754 fputs(_(
755 "\n"
756 "mail <recipients> compose a mail for the given recipients\n"
757 "file folder change to another mailbox\n"
758 "File folder like `file', but open readonly\n"
759 "quit quit and apply changes to the current mailbox\n"
760 "xit or exit like `quit', but discard changes\n"
761 "!shell command shell escape\n"
762 "list [<anything>] all available commands [in search order]\n"),
763 n_stdout);
765 rv = (ferror(n_stdout) != 0);
767 jleave:
768 NYD_LEAVE;
769 return rv;
772 static int
773 a_go_c_exit(void *vp){
774 NYD_ENTER;
775 n_UNUSED(vp);
777 if(n_psonce & n_PSO_STARTED){
778 /* In recursed state, return error to just pop the input level */
779 if(!(n_pstate & n_PS_SOURCING)){ /* FIXME a_go_ctx->gc_outer == NULL */
780 #ifdef n_HAVE_TCAP
781 if((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_QUICKRUN_MASK))
782 n_termcap_destroy();
783 #endif
784 exit(n_EXIT_OK);
787 n_pstate |= n_PS_EXIT; /* FIXME Always like this here, then CLEANUP_UNWIND!*/
788 NYD_LEAVE;
789 return 0;
792 static int
793 a_go_c_quit(void *vp){
794 NYD_ENTER;
795 n_UNUSED(vp);
796 n_pstate |= n_PS_EXIT;
797 NYD_LEAVE;
798 return 0;
801 static int
802 a_go_c_version(void *vp){
803 int longest, rv;
804 char *iop;
805 char const *cp, **arr;
806 size_t i, i2;
807 NYD_ENTER;
808 n_UNUSED(vp);
810 fprintf(n_stdout, _("%s version %s\nFeatures included (+) or not (-)\n"),
811 n_uagent, ok_vlook(version));
813 /* *features* starts with dummy byte to avoid + -> *folder* expansions */
814 i = strlen(cp = &ok_vlook(features)[1]) +1;
815 iop = n_autorec_alloc(i);
816 memcpy(iop, cp, i);
818 arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT);
819 for(longest = 0, i = 0; (cp = n_strsep(&iop, ',', TRU1)) != NULL; ++i){
820 arr[i] = cp;
821 i2 = strlen(cp);
822 longest = n_MAX(longest, (int)i2);
824 qsort(arr, i, sizeof(cp), &a_go__version_cmp);
826 /* We use aligned columns, so don't use n_SCRNWIDTH_FOR_LISTS */
827 for(++longest, i2 = 0; i-- > 0;){
828 cp = *(arr++);
829 fprintf(n_stdout, "%-*s ", longest, cp);
830 i2 += longest;
831 if(UICMP(z, ++i2 + longest, >=, n_scrnwidth) || i == 0){
832 i2 = 0;
833 putc('\n', n_stdout);
837 if((rv = ferror(n_stdout) != 0))
838 clearerr(n_stdout);
839 NYD_LEAVE;
840 return rv;
843 static int
844 a_go__version_cmp(void const *s1, void const *s2){
845 char const * const *cp1, * const *cp2;
846 int rv;
847 NYD2_ENTER;
849 cp1 = s1;
850 cp2 = s2;
851 rv = strcmp(&(*cp1)[1], &(*cp2)[1]);
852 NYD2_LEAVE;
853 return rv;
856 static void
857 a_go_update_pstate(void){
858 bool_t act;
859 NYD_ENTER;
861 act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
862 n_pstate &= ~n_PS_PSTATE_PENDMASK;
864 if(act){
865 char buf[32];
867 snprintf(buf, sizeof buf, "%d", n_scrnwidth);
868 ok_vset(COLUMNS, buf);
869 snprintf(buf, sizeof buf, "%d", n_scrnheight);
870 ok_vset(LINES, buf);
872 NYD_LEAVE;
875 static int
876 a_go_evaluate(struct a_go_eval_ctx *gecp){
877 /* xxx old style(9), but also old code */
878 struct str line;
879 char _wordbuf[2], **arglist_base/*[n_MAXARGC]*/, **arglist, *cp, *word;
880 struct a_go_ghost *ggp;
881 struct a_go_cmd_desc const *gcdp;
882 int rv, c;
883 enum {
884 a_NONE = 0,
885 a_GHOST_MASK = n_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
886 a_NOPREFIX = 1<<4, /* Modifier prefix not allowed right now */
887 a_NOGHOST = 1<<5, /* "No alias!" expansion modifier */
888 /* New modifier prefixes must be reflected in a_go_c_alias()! */
889 a_IGNERR = 1<<6, /* ignerr modifier prefix */
890 a_WYSH = 1<<7, /* XXX v15+ drop wysh modifier prefix */
891 a_VPUT = 1<<8 /* vput modifier prefix */
892 } flags;
893 NYD_ENTER;
895 flags = a_NONE;
896 rv = 1;
897 gcdp = NULL;
898 ggp = NULL;
899 arglist =
900 arglist_base = n_autorec_alloc(sizeof(*arglist_base) * n_MAXARGC);
901 line = gecp->gec_line; /* XXX don't change original (buffer pointer) */
902 assert(line.s[line.l] == '\0');
903 gecp->gec_add_history = FAL0;
905 /* Command ghosts that refer to shell commands or macro expansion restart */
906 jrestart:
908 /* Strip the white space away from end and beginning of command */
909 if(line.l > 0){
910 size_t i;
912 i = line.l;
913 for(cp = &line.s[i -1]; spacechar(*cp); --cp)
914 --i;
915 line.l = i;
917 for(cp = line.s; spacechar(*cp); ++cp)
919 line.l -= PTR2SIZE(cp - line.s);
921 /* Ignore null commands (comments) */
922 if(*cp == '#')
923 goto jerr0;
925 /* Handle ! differently to get the correct lexical conventions */
926 arglist[0] = cp;
927 if(*cp == '!')
928 ++cp;
929 /* Isolate the actual command; since it may not necessarily be
930 * separated from the arguments (as in `p1') we need to duplicate it to
931 * be able to create a NUL terminated version.
932 * We must be aware of several special one letter commands here */
933 else if((cp = a_go_isolate(cp)) == arglist[0] &&
934 (*cp == '|' || *cp == '~' || *cp == '?'))
935 ++cp;
936 c = (int)PTR2SIZE(cp - arglist[0]);
937 line.l -= c;
938 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
939 memcpy(word, arglist[0], c);
940 word[c] = '\0';
942 /* No-expansion modifier? */
943 if(!(flags & a_NOPREFIX) && *word == '\\'){
944 ++word;
945 --c;
946 flags |= a_NOGHOST;
949 /* It may be a modifier prefix */
950 if(c == sizeof("ignerr") -1 && !asccasecmp(word, "ignerr")){
951 flags |= a_NOPREFIX | a_IGNERR;
952 line.s = cp;
953 goto jrestart;
954 }else if(c == sizeof("wysh") -1 && !asccasecmp(word, "wysh")){
955 flags |= a_NOPREFIX | a_WYSH;
956 line.s = cp;
957 goto jrestart;
958 }else if(c == sizeof("vput") -1 && !asccasecmp(word, "vput")){
959 flags |= a_NOPREFIX | a_VPUT;
960 line.s = cp;
961 goto jrestart;
964 /* Look up the command; if not found, bitch.
965 * Normally, a blank command would map to the first command in the
966 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
967 * confusion; act just the same for ghosts */
968 if(*word == '\0'){
969 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
970 ggp != NULL)
971 goto jerr0;
972 gcdp = &a_go_cmd_tab[0];
973 goto jexec;
976 if(!(flags & a_NOGHOST) && (flags & a_GHOST_MASK) != a_GHOST_MASK){
977 ui8_t expcnt;
979 expcnt = (flags & a_GHOST_MASK);
980 ++expcnt;
981 flags = (flags & ~(a_GHOST_MASK | a_NOPREFIX)) | expcnt;
983 /* Avoid self-recursion; yes, the user could use \ no-expansion, but.. */
984 if(ggp != NULL && !strcmp(word, ggp->gg_name)){
985 if(n_poption & n_PO_D_V)
986 n_err(_("Actively avoiding self-recursion of `ghost': %s\n"), word);
987 }else for(ggp = a_go_ghosts; ggp != NULL; ggp = ggp->gg_next)
988 if(!strcmp(word, ggp->gg_name)){
989 if(line.l > 0){
990 size_t i;
992 i = ggp->gg_cmd.l;
993 line.s = n_autorec_alloc(i + line.l +1);
994 memcpy(line.s, ggp->gg_cmd.s, i);
995 memcpy(line.s + i, cp, line.l);
996 line.s[i += line.l] = '\0';
997 line.l = i;
998 }else{
999 line.s = ggp->gg_cmd.s;
1000 line.l = ggp->gg_cmd.l;
1002 goto jrestart;
1006 if((gcdp = a_go__firstfit(word)) == NULL || gcdp->gcd_func == &c_cmdnotsupp){
1007 bool_t s;
1009 if(!(s = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
1010 n_err(_("Unknown command%s: `%s'\n"),
1011 (s ? _(" (ignored due to `if' condition)") : n_empty), word);
1012 if(s)
1013 goto jerr0;
1014 if(gcdp != NULL){
1015 c_cmdnotsupp(NULL);
1016 gcdp = NULL;
1018 n_pstate_var__em = n_m1;
1019 goto jleave;
1022 /* See if we should execute the command -- if a conditional we always
1023 * execute it, otherwise, check the state of cond */
1024 jexec:
1025 if(!(gcdp->gcd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip())
1026 goto jerr0;
1028 n_pstate_var__em = n_1;
1030 /* Process the arguments to the command, depending on the type it expects */
1031 if((gcdp->gcd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
1032 !(n_poption & n_PO_BATCH_FLAG)){
1033 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
1034 gcdp->gcd_name);
1035 goto jleave;
1037 if(!(gcdp->gcd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
1038 n_err(_("May not execute `%s' while sending\n"), gcdp->gcd_name);
1039 goto jleave;
1041 if(gcdp->gcd_caflags & n_CMD_ARG_R){
1042 if(n_pstate & n_PS_COMPOSE_MODE){
1043 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
1044 n_err(_("Cannot invoke `%s' when in compose mode\n"), gcdp->gcd_name);
1045 goto jleave;
1047 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
1048 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
1049 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING)){
1050 n_err(_("Cannot invoke `%s' from a macro or during file inclusion\n"),
1051 gcdp->gcd_name);
1052 goto jleave;
1055 if((gcdp->gcd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED)){
1056 n_err(_("May not execute `%s' during startup\n"), gcdp->gcd_name);
1057 goto jleave;
1059 if(!(gcdp->gcd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
1060 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
1061 gcdp->gcd_name);
1062 goto jleave;
1065 if((gcdp->gcd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
1066 n_err(_("Cannot execute `%s' without active mailbox\n"), gcdp->gcd_name);
1067 goto jleave;
1069 if((gcdp->gcd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
1070 n_err(_("May not execute `%s' -- message file is read only\n"),
1071 gcdp->gcd_name);
1072 goto jleave;
1075 if(gcdp->gcd_caflags & n_CMD_ARG_O)
1076 n_OBSOLETE2(_("this command will be removed"), gcdp->gcd_name);
1078 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
1079 * TODO doesn't use any of those list commands which strip this mask,
1080 * TODO and for now we misuse bits for checking relation to history;
1081 * TODO argument state should be property of a per-command carrier instead */
1082 n_pstate &= ~n_PS_ARGLIST_MASK;
1084 if((flags & a_WYSH) &&
1085 (gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
1086 n_err(_("`wysh' prefix does not affect `%s'\n"), gcdp->gcd_name);
1087 flags &= ~a_WYSH;
1090 if(flags & a_VPUT){
1091 if(gcdp->gcd_caflags & n_CMD_ARG_V){
1092 char const *xcp;
1094 xcp = cp;
1095 arglist[0] = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIMSPACE |
1096 n_SHEXP_PARSE_LOG), &xcp);
1097 line.l -= PTR2SIZE(xcp - cp);
1098 cp = n_UNCONST(xcp);
1099 if(cp == NULL)
1100 xcp = N_("could not parse input token");
1101 else if(!n_shexp_is_valid_varname(arglist[0]))
1102 xcp = N_("not a valid variable name");
1103 else if(!n_var_is_user_writable(arglist[0]))
1104 xcp = N_("either not a user writable, or a boolean variable");
1105 else
1106 xcp = NULL;
1107 if(xcp != NULL){
1108 n_err("`%s': vput: %s: %s\n",
1109 gcdp->gcd_name, V_(xcp), n_shexp_quote_cp(arglist[0], FAL0));
1110 goto jleave;
1112 ++arglist;
1113 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
1114 * TODO on in getrawlist() etc., i.e., the argument vector producers,
1115 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
1116 }else{
1117 n_err(_("`vput' prefix does not affect `%s'\n"), gcdp->gcd_name);
1118 flags &= ~a_VPUT;
1122 switch(gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK){
1123 case n_CMD_ARG_TYPE_MSGLIST:
1124 /* Message list defaulting to nearest forward legal message */
1125 if(n_msgvec == NULL)
1126 goto je96;
1127 if((c = getmsglist(cp, n_msgvec, gcdp->gcd_msgflag)) < 0)
1128 break;
1129 if(c == 0){
1130 if((n_msgvec[0] = first(gcdp->gcd_msgflag, gcdp->gcd_msgmask)) != 0)
1131 n_msgvec[1] = 0;
1133 if(n_msgvec[0] == 0){
1134 if(!(n_pstate & n_PS_HOOK_MASK))
1135 fprintf(n_stdout, _("No applicable messages\n"));
1136 break;
1138 rv = (*gcdp->gcd_func)(n_msgvec);
1139 break;
1141 case n_CMD_ARG_TYPE_NDMLIST:
1142 /* Message list with no defaults, but no error if none exist */
1143 if(n_msgvec == NULL){
1144 je96:
1145 n_err(_("Invalid use of message list\n"));
1146 break;
1148 if((c = getmsglist(cp, n_msgvec, gcdp->gcd_msgflag)) < 0)
1149 break;
1150 rv = (*gcdp->gcd_func)(n_msgvec);
1151 break;
1153 case n_CMD_ARG_TYPE_STRING:
1154 /* Just the straight string, old style, with leading blanks removed */
1155 while(spacechar(*cp))
1156 ++cp;
1157 rv = (*gcdp->gcd_func)(cp);
1158 break;
1159 case n_CMD_ARG_TYPE_RAWDAT:
1160 /* Just the straight string, leading blanks removed, placed in argv[] */
1161 while(spacechar(*cp))
1162 ++cp;
1163 *arglist++ = cp;
1164 *arglist = NULL;
1165 rv = (*gcdp->gcd_func)(arglist_base);
1166 break;
1168 case n_CMD_ARG_TYPE_WYSH:
1169 c = 1;
1170 if(0){
1171 /* FALLTHRU */
1172 case n_CMD_ARG_TYPE_WYRA:
1173 c = (flags & a_WYSH) ? 1 : 0;
1174 if(0){
1175 case n_CMD_ARG_TYPE_RAWLIST:
1176 c = 0;
1179 if((c = getrawlist((c != 0), arglist,
1180 n_MAXARGC - PTR2SIZE(arglist - arglist_base),
1181 cp, line.l)) < 0){
1182 n_err(_("Invalid argument list\n"));
1183 break;
1186 if(c < gcdp->gcd_minargs){
1187 n_err(_("`%s' requires at least %u arg(s)\n"),
1188 gcdp->gcd_name, (ui32_t)gcdp->gcd_minargs);
1189 break;
1191 #undef gcd_minargs
1192 if(c > gcdp->gcd_maxargs){
1193 n_err(_("`%s' takes no more than %u arg(s)\n"),
1194 gcdp->gcd_name, (ui32_t)gcdp->gcd_maxargs);
1195 break;
1197 #undef gcd_maxargs
1199 if(flags & a_VPUT)
1200 n_pstate |= n_PS_ARGMOD_VPUT;
1202 rv = (*gcdp->gcd_func)(arglist_base);
1203 if(a_go_xcall != NULL)
1204 goto jret0;
1205 break;
1207 default:
1208 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
1209 gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK); )
1210 goto jerr0;
1213 if(!(gcdp->gcd_caflags & n_CMD_ARG_H))
1214 gecp->gec_add_history = (((gcdp->gcd_caflags & n_CMD_ARG_G) ||
1215 (n_pstate & n_PS_MSGLIST_GABBY)) ? TRUM1 : TRU1);
1217 if(!(gcdp->gcd_caflags & n_CMD_ARG_EM) && rv == 0)
1218 n_pstate_var__em = n_0;
1219 jleave:
1220 n_PS_ROOT_BLOCK(
1221 ok_vset(__qm, (rv == 0 ? n_0 : n_1));
1222 ok_vset(__em, n_pstate_var__em)
1225 if(flags & a_IGNERR){
1226 rv = 0;
1227 n_exit_status = n_EXIT_OK;
1230 /* Exit the current source file on error TODO what a mess! */
1231 if(rv == 0)
1232 n_pstate &= ~n_PS_EVAL_ERROR;
1233 else{
1234 n_pstate |= n_PS_EVAL_ERROR;
1235 if(rv < 0 || (n_pstate & n_PS_ROBOT)){ /* FIXME */
1236 rv = 1;
1237 goto jret;
1239 goto jret0;
1242 if(gcdp == NULL)
1243 goto jret0;
1244 if((gcdp->gcd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
1245 if(visible(dot))
1246 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
1247 sizeof("\\type") -1);
1249 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
1250 !(gcdp->gcd_caflags & n_CMD_ARG_T))
1251 n_pstate |= n_PS_SAW_COMMAND;
1252 jleave0:
1253 n_pstate &= ~n_PS_EVAL_ERROR;
1254 jret0:
1255 rv = 0;
1256 jret:
1257 NYD_LEAVE;
1258 return rv;
1259 jerr0:
1260 n_PS_ROOT_BLOCK(
1261 ok_vset(__qm, n_0);
1262 ok_vset(__em, n_0)
1264 goto jleave0;
1267 static struct a_go_cmd_desc const *
1268 a_go__firstfit(char const *comm){ /* TODO *hashtable*! linear list search!!! */
1269 struct a_go_cmd_desc const *gcdp;
1270 NYD2_ENTER;
1272 for(gcdp = a_go_cmd_tab; gcdp < &a_go_cmd_tab[n_NELEM(a_go_cmd_tab)]; ++gcdp)
1273 if(*comm == *gcdp->gcd_name && is_prefix(comm, gcdp->gcd_name))
1274 goto jleave;
1275 gcdp = NULL;
1276 jleave:
1277 NYD2_LEAVE;
1278 return gcdp;
1281 static void
1282 a_go_hangup(int s){
1283 NYD_X; /* Signal handler */
1284 n_UNUSED(s);
1285 /* nothing to do? */
1286 exit(n_EXIT_ERR);
1289 static void
1290 a_go_onintr(int s){ /* TODO block signals while acting */
1291 NYD_X; /* Signal handler */
1292 n_UNUSED(s);
1294 safe_signal(SIGINT, a_go_onintr);
1296 termios_state_reset();
1298 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
1300 if(interrupts != 1)
1301 n_err_sighdl(_("Interrupt\n"));
1302 safe_signal(SIGPIPE, a_go_oldpipe);
1303 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
1306 static void
1307 a_go_cleanup(enum a_go_cleanup_mode gcm){
1308 /* Signals blocked */
1309 struct a_go_ctx *gcp;
1310 NYD_ENTER;
1312 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1313 hold_all_sigs();
1314 jrestart:
1315 gcp = a_go_ctx;
1317 /* Free input injections of this level first */
1318 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
1319 struct a_go_input_inject **giipp, *giip;
1321 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
1322 *giipp = giip->gii_next;
1323 n_free(giip);
1327 /* Cleanup non-crucial external stuff */
1328 n_COLOUR(
1329 if(gcp->gc_data.gdc_colour != NULL)
1330 n_colour_stack_del(NULL);
1333 /* Work the actual context (according to cleanup mode) */
1334 if(gcp->gc_outer == NULL){
1335 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
1336 a_go_xcall = NULL;
1337 gcp->gc_flags &= ~a_GO_XCALL_LOOP;
1338 close_all_files();
1339 }else if(!(n_pstate & n_PS_SOURCING))
1340 close_all_files();
1342 n_memory_reset();
1344 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1345 assert(a_go_xcall == NULL);
1346 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP));
1347 assert(gcp->gc_on_finalize == NULL);
1348 assert(gcp->gc_data.gdc_colour == NULL);
1349 goto jxleave;
1350 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
1351 n_memory_reset();
1352 goto jxleave;
1353 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
1354 n_stdin = gcp->gc_splice_stdin;
1355 n_stdout = gcp->gc_splice_stdout;
1356 n_psonce = gcp->gc_splice_psonce;
1357 goto jstackpop;
1360 /* Cleanup crucial external stuff */
1361 if(gcp->gc_data.gdc_ifcond != NULL){
1362 n_cnd_if_stack_del(gcp->gc_data.gdc_ifcond);
1363 if(!(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
1364 a_go_xcall == NULL)
1365 n_err(_("Unmatched `if' at end of %s %s\n"),
1366 ((gcp->gc_flags & a_GO_MACRO
1367 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
1368 : _("`source'd file"))),
1369 gcp->gc_name);
1370 gcm |= a_GO_CLEANUP_ERROR;
1373 /* Teardown context */
1374 if(gcp->gc_flags & a_GO_MACRO){
1375 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
1376 char **lp;
1378 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
1379 n_free(*lp);
1380 ++gcp->gc_loff;
1382 /* Part of gcp's memory chunk, then */
1383 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
1384 n_free(gcp->gc_lines);
1386 }else if(gcp->gc_flags & a_GO_PIPE)
1387 /* XXX command manager should -TERM then -KILL instead of hoping
1388 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
1389 Pclose(gcp->gc_file, TRU1);
1390 else if(gcp->gc_flags & a_GO_FILE)
1391 Fclose(gcp->gc_file);
1393 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
1394 if(gcp->gc_data.gdc_mempool != NULL)
1395 n_memory_pool_pop(NULL);
1396 }else
1397 n_memory_reset();
1399 jstackpop:
1400 n_go_data = &(a_go_ctx = gcp->gc_outer)->gc_data;
1401 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
1402 (a_GO_MACRO | a_GO_SUPER_MACRO)){
1403 n_pstate &= ~n_PS_SOURCING;
1404 assert(n_pstate & n_PS_ROBOT);
1405 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
1406 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1407 else
1408 assert(n_pstate & n_PS_ROBOT);
1410 if(gcp->gc_on_finalize != NULL)
1411 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
1413 if(gcm & a_GO_CLEANUP_ERROR)
1414 goto jerr;
1415 jleave:
1416 if(gcp->gc_flags & a_GO_FREE)
1417 n_free(gcp);
1419 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
1420 goto jrestart;
1422 jxleave:
1423 NYD_LEAVE;
1424 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1425 rele_all_sigs();
1426 return;
1428 jerr:
1429 /* With *posix* we follow what POSIX says:
1430 * Any errors in the start-up file shall either cause mailx to
1431 * terminate with a diagnostic message and a non-zero status or to
1432 * continue after writing a diagnostic message, ignoring the
1433 * remainder of the lines in the start-up file
1434 * Print the diagnostic only for the outermost resource unless the user
1435 * is debugging or in verbose mode */
1436 if((n_poption & n_PO_D_V) ||
1437 (!(n_psonce & n_PSO_STARTED) &&
1438 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1439 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
1440 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1441 n_alert(_("Stopped %s %s due to errors%s"),
1442 (n_psonce & n_PSO_STARTED
1443 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1444 : (gcp->gc_flags & a_GO_MACRO
1445 ? (gcp->gc_flags & a_GO_MACRO_CMD
1446 ? _("evaluating command") : _("evaluating macro"))
1447 : (gcp->gc_flags & a_GO_PIPE
1448 ? _("executing `source'd pipe")
1449 : (gcp->gc_flags & a_GO_FILE
1450 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1452 : (gcp->gc_flags & a_GO_MACRO
1453 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
1454 ? _("evaluating command line") : _("evaluating macro"))
1455 : _("loading initialization resource"))),
1456 gcp->gc_name,
1457 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
1459 if(!(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) && ok_blook(posix)){
1460 if(n_poption & n_PO_D_V)
1461 n_alert(_("Non-interactive, bailing out due to errors "
1462 "in startup load phase"));
1463 exit(n_EXIT_ERR);
1465 goto jleave;
1468 static bool_t
1469 a_go_file(char const *file, bool_t silent_open_error){
1470 struct a_go_ctx *gcp;
1471 sigset_t osigmask;
1472 size_t nlen;
1473 char *nbuf;
1474 bool_t ispipe;
1475 FILE *fip;
1476 NYD_ENTER;
1478 fip = NULL;
1480 /* Being a command argument file is space-trimmed *//* TODO v15 with
1481 * TODO WYRALIST this is no longer necessary true, and for that we
1482 * TODO don't set _PARSE_TRIMSPACE because we cannot! -> cmd-tab.h!! */
1483 #if 0
1484 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
1485 file[--nlen] == '|')))
1486 #else
1487 ispipe = FAL0;
1488 if(!silent_open_error){
1489 for(nlen = strlen(file); nlen > 0;){
1490 char c;
1492 c = file[--nlen];
1493 if(!spacechar(c)){
1494 if(c == '|'){
1495 nbuf = savestrbuf(file, nlen);
1496 ispipe = TRU1;
1498 break;
1502 #endif
1504 if(ispipe){
1505 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
1506 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
1507 goto jeopencheck;
1508 }else if((nbuf = fexpand(file, FEXP_LOCAL)) == NULL)
1509 goto jeopencheck;
1510 else if((fip = Fopen(nbuf, "r")) == NULL){
1511 jeopencheck:
1512 if(!silent_open_error || (n_poption & n_PO_D_V))
1513 n_perr(nbuf, 0);
1514 if(silent_open_error)
1515 fip = (FILE*)-1;
1516 goto jleave;
1519 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1521 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1522 (nlen = strlen(nbuf) +1));
1523 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1525 hold_all_sigs();
1527 gcp->gc_outer = a_go_ctx;
1528 gcp->gc_osigmask = osigmask;
1529 gcp->gc_file = fip;
1530 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1531 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1532 memcpy(gcp->gc_name, nbuf, nlen);
1534 a_go_ctx = gcp;
1535 n_go_data = &gcp->gc_data;
1536 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1537 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1538 fip = NULL;
1539 jleave:
1540 NYD_LEAVE;
1541 return (fip != NULL);
1544 static bool_t
1545 a_go_load(struct a_go_ctx *gcp){
1546 bool_t rv;
1547 NYD2_ENTER;
1549 assert(!(n_psonce & n_PSO_STARTED));
1550 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1552 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1553 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1555 hold_all_sigs();
1557 /* POSIX:
1558 * Any errors in the start-up file shall either cause mailx to terminate
1559 * with a diagnostic message and a non-zero status or to continue after
1560 * writing a diagnostic message, ignoring the remainder of the lines in
1561 * the start-up file. */
1562 gcp->gc_outer = a_go_ctx;
1563 a_go_ctx = gcp;
1564 n_go_data = &gcp->gc_data;
1565 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1566 n_pstate |= n_PS_ROBOT |
1567 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1569 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1571 rele_all_sigs();
1573 if(!(rv = n_go_main_loop())){
1574 if(!(n_psonce & n_PSO_INTERACTIVE)){
1575 if(n_poption & n_PO_D_V)
1576 n_alert(_("Non-interactive program mode, forced exit"));
1577 exit(n_EXIT_ERR);
1578 }else if(n_poption & n_PO_BATCH_FLAG){
1579 char const *beoe;
1581 if((beoe = ok_vlook(batch_exit_on_error)) != NULL && *beoe == '1')
1582 n_pstate |= n_PS_EXIT;
1585 /* n_PS_EXIT handled by callers */
1586 NYD2_LEAVE;
1587 return rv;
1590 static void
1591 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1592 NYD_X; /* Signal handler */
1593 n_UNUSED(sig);
1594 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1597 static bool_t
1598 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1599 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1600 sighandler_type soldhdl;
1601 struct a_go_eval_ctx gec;
1602 bool_t rv, ever;
1603 sigset_t osigmask;
1604 NYD2_ENTER;
1606 memset(&gec, 0, sizeof gec);
1607 osigmask = gcp->gc_osigmask;
1608 hadint = FAL0;
1610 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1611 safe_signal(SIGINT, &a_go__eloopint);
1612 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1613 hold_all_sigs();
1614 hadint = TRU1;
1615 a_go_xcall = NULL;
1616 gcp->gc_flags &= ~a_GO_XCALL_LOOP;
1617 goto jjump;
1621 rv = TRU1;
1622 for(ever = FAL0;; ever = TRU1){
1623 char const *beoe;
1624 int n;
1626 if(ever)
1627 n_memory_reset();
1629 /* Read a line of commands and handle end of file specially */
1630 gec.gec_line.l = gec.gec_line_size;
1631 rele_all_sigs();
1632 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL);
1633 hold_all_sigs();
1634 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1635 gec.gec_line.l = (ui32_t)n;
1637 if(n < 0)
1638 break;
1640 rele_all_sigs();
1641 n = a_go_evaluate(&gec);
1642 hold_all_sigs();
1644 if(a_go_xcall != NULL)
1645 break;
1647 beoe = (n_poption & n_PO_BATCH_FLAG)
1648 ? ok_vlook(batch_exit_on_error) : NULL;
1650 if(n){
1651 if(beoe != NULL && *beoe == '1'){
1652 if(n_exit_status == n_EXIT_OK)
1653 n_exit_status = n_EXIT_ERR;
1655 rv = FAL0;
1656 break;
1658 if(beoe != NULL){
1659 if(n_exit_status != n_EXIT_OK)
1660 break;
1663 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1664 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | (rv ? 0 : a_GO_CLEANUP_ERROR) |
1665 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1667 if(gec.gec_line.s != NULL)
1668 n_free(gec.gec_line.s);
1670 if(soldhdl != SIG_IGN)
1671 safe_signal(SIGINT, soldhdl);
1672 NYD2_LEAVE;
1673 rele_all_sigs();
1674 if(hadint){
1675 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1676 n_raise(SIGINT);
1678 return rv;
1681 static int
1682 a_go_c_read(void *v){ /* TODO IFS? how? -r */
1683 struct n_sigman sm;
1684 char const **argv, *cp, *cp2;
1685 char *linebuf;
1686 size_t linesize;
1687 int rv;
1688 NYD2_ENTER;
1690 rv = 0;
1691 linesize = 0;
1692 linebuf = NULL;
1693 argv = v;
1695 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1696 case 0:
1697 break;
1698 default:
1699 rv = 1;
1700 goto jleave;
1702 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
1703 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
1704 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
1705 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
1706 NULL, &linebuf, &linesize, NULL);
1707 if(rv < 0)
1708 goto jleave;
1710 if(rv > 0){
1711 cp = linebuf;
1713 for(rv = 0; *argv != NULL; ++argv){
1714 char c;
1716 while(spacechar(*cp))
1717 ++cp;
1718 if(*cp == '\0')
1719 break;
1721 /* The last variable gets the remaining line less trailing IFS */
1722 if(argv[1] == NULL){
1723 for(cp2 = cp; *cp2 != '\0'; ++cp2)
1725 for(; cp2 > cp; --cp2){
1726 c = cp2[-1];
1727 if(!spacechar(c))
1728 break;
1730 }else
1731 for(cp2 = cp; (c = *++cp2) != '\0';)
1732 if(spacechar(c))
1733 break;
1735 /* C99 xxx This is a CC warning workaround (-Wbad-function-cast) */{
1736 char *vcp;
1738 vcp = savestrbuf(cp, PTR2SIZE(cp2 - cp));
1739 if(!a_go__read_set(*argv, vcp)){
1740 rv = 1;
1741 break;
1745 cp = cp2;
1749 /* Set the remains to the empty string */
1750 for(; *argv != NULL; ++argv)
1751 if(!a_go__read_set(*argv, n_empty)){
1752 rv = 1;
1753 break;
1756 if(rv == 0)
1757 n_pstate_var__em = n_0;
1759 n_sigman_cleanup_ping(&sm);
1760 jleave:
1761 if(linebuf != NULL)
1762 n_free(linebuf);
1763 NYD2_LEAVE;
1764 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1765 return rv;
1768 static bool_t
1769 a_go__read_set(char const *cp, char const *value){
1770 bool_t rv;
1771 NYD2_ENTER;
1773 if(!n_shexp_is_valid_varname(cp))
1774 value = N_("not a valid variable name");
1775 else if(!n_var_is_user_writable(cp))
1776 value = N_("variable is read-only");
1777 else if(!n_var_vset(cp, (uintptr_t)value))
1778 value = N_("failed to update variable value");
1779 else{
1780 rv = TRU1;
1781 goto jleave;
1783 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
1784 rv = FAL0;
1785 jleave:
1786 NYD2_LEAVE;
1787 return rv;
1790 FL int
1791 c_cmdnotsupp(void *vp){
1792 NYD_ENTER;
1793 n_UNUSED(vp);
1794 n_err(_("The requested feature is not compiled in\n"));
1795 NYD_LEAVE;
1796 return 1;
1799 FL void
1800 n_go_init(void){
1801 struct a_go_ctx *gcp;
1802 NYD2_ENTER;
1804 assert(n_stdin != NULL);
1806 gcp = (void*)a_go__mainctx_b.uf;
1807 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1808 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1809 gcp->gc_file = n_stdin;
1810 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1811 a_go_ctx = gcp;
1812 n_go_data = &gcp->gc_data;
1814 n_child_manager_start();
1815 NYD2_LEAVE;
1818 FL bool_t
1819 n_go_main_loop(void){ /* FIXME */
1820 struct a_go_eval_ctx gec;
1821 int n, eofcnt;
1822 bool_t volatile rv;
1823 NYD_ENTER;
1825 rv = TRU1;
1827 if (!(n_pstate & n_PS_SOURCING)) {
1828 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1829 safe_signal(SIGINT, &a_go_onintr);
1830 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1831 safe_signal(SIGHUP, &a_go_hangup);
1833 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1834 safe_signal(SIGPIPE, a_go_oldpipe);
1836 memset(&gec, 0, sizeof gec);
1838 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1839 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1840 interrupts = 0;
1842 if(gec.gec_ever_seen)
1843 a_go_cleanup(a_GO_CLEANUP_LOOPTICK);
1845 if (!(n_pstate & n_PS_SOURCING)) {
1846 char *cp;
1848 /* TODO Note: this buffer may contain a password. We should redefine
1849 * TODO the code flow which has to do that */
1850 if ((cp = termios_state.ts_linebuf) != NULL) {
1851 termios_state.ts_linebuf = NULL;
1852 termios_state.ts_linesize = 0;
1853 n_free(cp); /* TODO pool give-back */
1855 if (gec.gec_line.l > LINESIZE * 3) {
1856 n_free(gec.gec_line.s);
1857 gec.gec_line.s = NULL;
1858 gec.gec_line.l = gec.gec_line_size = 0;
1862 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1863 char *cp;
1865 if ((cp = ok_vlook(newmail)) != NULL) {
1866 struct stat st;
1868 /* FIXME TEST WITH NOPOLL ETC. !!! */
1869 n = (cp != NULL && strcmp(cp, "nopoll"));
1870 if ((mb.mb_type == MB_FILE && !stat(mailname, &st) &&
1871 st.st_size > mailsize) ||
1872 (mb.mb_type == MB_MAILDIR && n != 0)) {
1873 size_t odot = PTR2SIZE(dot - message);
1874 ui32_t odid = (n_pstate & n_PS_DID_PRINT_DOT);
1876 if (setfile(mailname,
1877 FEDIT_NEWMAIL |
1878 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)) < 0) {
1879 n_exit_status |= n_EXIT_ERR;
1880 rv = FAL0;
1881 break;
1883 dot = &message[odot];
1884 n_pstate |= odid;
1888 n_exit_status = n_EXIT_OK;
1891 /* Read a line of commands and handle end of file specially */
1892 gec.gec_line.l = gec.gec_line_size;
1893 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1894 &gec.gec_line.s, &gec.gec_line.l, NULL);
1895 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1896 gec.gec_line.l = (ui32_t)n;
1898 if (n < 0) {
1899 if (!(n_pstate & n_PS_ROBOT) &&
1900 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1901 ++eofcnt < 4) {
1902 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1903 n_go_input_clearerr();
1904 continue;
1906 break;
1909 n_pstate &= ~n_PS_HOOK_MASK;
1910 /* C99 */{
1911 char const *beoe;
1912 int estat;
1914 estat = a_go_evaluate(&gec);
1915 beoe = (n_poption & n_PO_BATCH_FLAG)
1916 ? ok_vlook(batch_exit_on_error) : NULL;
1918 if(estat){
1919 if(beoe != NULL && *beoe == '1'){
1920 if(n_exit_status == n_EXIT_OK)
1921 n_exit_status = n_EXIT_ERR;
1922 rv = FAL0;
1923 break;
1925 if(!(n_psonce & n_PSO_STARTED)){ /* TODO join n_PS_EVAL_ERROR */
1926 if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1927 !(a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)){
1928 rv = FAL0;
1929 break;
1931 }else
1932 break;
1935 if(beoe != NULL){
1936 if(n_exit_status != n_EXIT_OK)
1937 break;
1938 /* TODO n_PS_EVAL_ERROR and n_PS_SOURCING! Sigh!! */
1939 if((n_pstate & (n_PS_SOURCING | n_PS_EVAL_ERROR)
1940 ) == n_PS_EVAL_ERROR){
1941 n_exit_status = n_EXIT_ERR;
1942 break;
1947 if(!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE) &&
1948 gec.gec_add_history)
1949 n_tty_addhist(gec.gec_line.s, (gec.gec_add_history != TRU1));
1951 if(n_pstate & n_PS_EXIT)
1952 break;
1955 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | (rv ? 0 : a_GO_CLEANUP_ERROR));
1957 if (gec.gec_line.s != NULL)
1958 n_free(gec.gec_line.s);
1959 NYD_LEAVE;
1960 return rv;
1963 FL void
1964 n_go_input_clearerr(void){
1965 FILE *fp;
1966 NYD2_ENTER;
1968 fp = NULL;
1970 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1971 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1972 fp = a_go_ctx->gc_file;
1974 if(fp != NULL)
1975 clearerr(fp);
1976 NYD2_LEAVE;
1979 FL void
1980 n_go_input_force_eof(void){
1981 NYD_ENTER;
1982 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1983 NYD_LEAVE;
1986 FL void
1987 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1988 size_t len){
1989 NYD_ENTER;
1990 if(len == UIZ_MAX)
1991 len = strlen(buf);
1993 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1994 len > 0){
1995 size_t i;
1996 struct a_go_input_inject *giip, **giipp;
1998 hold_all_sigs();
2000 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
2001 ) + 1 + len +1);
2002 giipp = &a_go_ctx->gc_inject;
2003 giip->gii_next = *giipp;
2004 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
2005 if(buf[i = 0] != ' ' && !(giif & n_GO_INPUT_INJECT_HISTORY))
2006 giip->gii_dat[i++] = ' '; /* TODO prim. hack to avoid history put! */
2007 memcpy(&giip->gii_dat[i], buf, len);
2008 i += len;
2009 giip->gii_dat[giip->gii_len = i] = '\0';
2010 *giipp = giip;
2012 rele_all_sigs();
2014 NYD_LEAVE;
2017 FL int
2018 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
2019 size_t *linesize, char const *string n_MEMORY_DEBUG_ARGS){
2020 /* TODO readline: linebuf pool!; n_go_input should return si64_t */
2021 struct n_string xprompt;
2022 FILE *ifile;
2023 bool_t doprompt, dotty;
2024 char const *iftype;
2025 int nold, n;
2026 NYD2_ENTER;
2028 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
2029 hold_all_sigs();
2031 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
2032 n = -1;
2033 goto jleave;
2036 if(gif & n_GO_INPUT_FORCE_STDIN)
2037 goto jforce_stdin;
2039 /* Special case macro mode: never need to prompt, lines have always been
2040 * unfolded already */
2041 if(a_go_ctx->gc_flags & a_GO_MACRO){
2042 struct a_go_input_inject *giip;
2044 if(*linebuf != NULL)
2045 n_free(*linebuf);
2047 /* Injection in progress? Don't care about the autocommit state here */
2048 if((giip = a_go_ctx->gc_inject) != NULL){
2049 a_go_ctx->gc_inject = giip->gii_next;
2051 *linesize = giip->gii_len;
2052 *linebuf = (char*)giip;
2053 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
2054 iftype = "INJECTION";
2055 }else{
2056 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
2057 *linesize = 0;
2058 n = -1;
2059 goto jleave;
2062 ++a_go_ctx->gc_loff;
2063 *linesize = strlen(*linebuf);
2064 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
2065 *linebuf = sbufdup(*linebuf, *linesize);
2067 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
2068 ? "-X OPTION"
2069 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
2071 n = (int)*linesize;
2072 n_pstate |= n_PS_READLINE_NL;
2073 goto jhave_dat;
2074 }else{
2075 /* Injection in progress? */
2076 struct a_go_input_inject **giipp, *giip;
2078 giipp = &a_go_ctx->gc_inject;
2080 if((giip = *giipp) != NULL){
2081 *giipp = giip->gii_next;
2083 if(giip->gii_commit){
2084 if(*linebuf != NULL)
2085 n_free(*linebuf);
2087 /* Simply reuse the buffer */
2088 n = (int)(*linesize = giip->gii_len);
2089 *linebuf = (char*)giip;
2090 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
2091 iftype = "INJECTION";
2092 n_pstate |= n_PS_READLINE_NL;
2093 goto jhave_dat;
2094 }else{
2095 string = savestrbuf(giip->gii_dat, giip->gii_len);
2096 n_free(giip);
2101 jforce_stdin:
2102 n_pstate &= ~n_PS_READLINE_NL;
2103 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
2104 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
2105 doprompt = ((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
2106 (n_PSO_INTERACTIVE | n_PSO_STARTED) && !(n_pstate & n_PS_ROBOT));
2107 dotty = (doprompt && !ok_blook(line_editor_disable));
2108 if(!doprompt)
2109 gif |= n_GO_INPUT_PROMPT_NONE;
2110 else{
2111 if(!dotty)
2112 n_string_creat_auto(&xprompt);
2113 if(prompt == NULL)
2114 gif |= n_GO_INPUT_PROMPT_EVAL;
2117 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
2118 if(!dotty && (gif & n_GO_INPUT_PROMPT_NONE))
2119 fflush(n_stdout);
2121 ifile = (gif & n_GO_INPUT_FORCE_STDIN) ? n_stdin : a_go_ctx->gc_file;
2122 if(ifile == NULL){
2123 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
2124 (a_go_ctx->gc_flags & a_GO_MACRO));
2125 ifile = n_stdin;
2128 for(nold = n = 0;;){
2129 if(dotty){
2130 assert(ifile == n_stdin);
2131 if(string != NULL && (n = (int)strlen(string)) > 0){
2132 if(*linesize > 0)
2133 *linesize += n +1;
2134 else
2135 *linesize = (size_t)n + LINESIZE +1;
2136 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
2137 memcpy(*linebuf, string, (size_t)n +1);
2139 string = NULL;
2141 rele_all_sigs();
2143 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n
2144 n_MEMORY_DEBUG_ARGSCALL);
2146 hold_all_sigs();
2147 }else{
2148 if(!(gif & n_GO_INPUT_PROMPT_NONE))
2149 n_tty_create_prompt(&xprompt, prompt, gif);
2151 rele_all_sigs();
2153 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
2154 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
2155 fflush(n_stdout);
2158 n = (readline_restart)(ifile, linebuf, linesize, n
2159 n_MEMORY_DEBUG_ARGSCALL);
2161 hold_all_sigs();
2163 if(n > 0 && nold > 0){
2164 char const *cp;
2165 int i;
2167 i = 0;
2168 cp = &(*linebuf)[nold];
2169 while(spacechar(*cp) && n - i >= nold)
2170 ++cp, ++i;
2171 if(i > 0){
2172 memmove(&(*linebuf)[nold], cp, n - nold - i);
2173 n -= i;
2174 (*linebuf)[n] = '\0';
2179 if(n <= 0)
2180 break;
2182 /* POSIX says:
2183 * An unquoted <backslash> at the end of a command line shall
2184 * be discarded and the next line shall continue the command */
2185 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\'){
2186 if(dotty)
2187 n_pstate |= n_PS_READLINE_NL;
2188 break;
2190 /* Definitely outside of quotes, thus the quoting rules are so that an
2191 * uneven number of successive reverse solidus at EOL is a continuation */
2192 if(n > 1){
2193 size_t i, j;
2195 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
2196 if((*linebuf)[i] != '\\')
2197 break;
2198 if(!(j & 1))
2199 break;
2201 (*linebuf)[nold = --n] = '\0';
2202 gif |= n_GO_INPUT_NL_FOLLOW;
2205 if(n < 0)
2206 goto jleave;
2207 (*linebuf)[*linesize = n] = '\0';
2209 jhave_dat:
2210 #if 0
2211 if(gif & n_GO_INPUT_DROP_TRAIL_SPC){
2212 char *cp, c;
2213 size_t i;
2215 for(cp = &(*linebuf)[i = (size_t)n];; --i){
2216 c = *--cp;
2217 if(!spacechar(c))
2218 break;
2220 (*linebuf)[n = (int)i] = '\0';
2223 if(gif & n_GO_INPUT_DROP_LEAD_SPC){
2224 char *cp, c;
2225 size_t j, i;
2227 for(cp = &(*linebuf)[0], j = (size_t)n, i = 0; i < j; ++i){
2228 c = *cp++;
2229 if(!spacechar(c))
2230 break;
2232 if(i > 0){
2233 memmove(&(*linebuf)[0], &(*linebuf)[i], j -= i);
2234 (*linebuf)[n = (int)j] = '\0';
2237 #endif /* 0 (notyet - must take care for reverse solidus escaped space) */
2239 if(n_poption & n_PO_D_VV)
2240 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
2241 jleave:
2242 if (n_pstate & n_PS_PSTATE_PENDMASK)
2243 a_go_update_pstate();
2245 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
2246 * TODO but only established from the outside and we need to drop this
2247 * TODO overlay context somehow */
2248 if(n < 0 && (a_go_ctx->gc_flags & a_GO_SPLICE))
2249 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
2251 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
2252 rele_all_sigs();
2253 NYD2_LEAVE;
2254 return n;
2257 FL char *
2258 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
2259 char const *string){
2260 struct n_sigman sm;
2261 size_t linesize;
2262 char *linebuf, * volatile rv;
2263 int n;
2264 NYD2_ENTER;
2266 linesize = 0;
2267 linebuf = NULL;
2268 rv = NULL;
2270 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
2271 case 0:
2272 break;
2273 default:
2274 goto jleave;
2277 n = n_go_input(gif, prompt, &linebuf, &linesize, string);
2278 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
2279 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE))
2280 n_tty_addhist(rv, ((gif & n_GO_INPUT_HIST_GABBY) != 0));
2282 n_sigman_cleanup_ping(&sm);
2283 jleave:
2284 if(linebuf != NULL)
2285 n_free(linebuf);
2286 NYD2_LEAVE;
2287 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
2288 return rv;
2291 FL void
2292 n_go_load(char const *name){
2293 struct a_go_ctx *gcp;
2294 size_t i;
2295 FILE *fip;
2296 NYD_ENTER;
2298 if(name == NULL || *name == '\0')
2299 goto jleave;
2300 else if((fip = Fopen(name, "r")) == NULL){
2301 if(n_poption & n_PO_D_V)
2302 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
2303 goto jleave;
2306 i = strlen(name) +1;
2307 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
2308 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2310 gcp->gc_file = fip;
2311 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
2312 memcpy(gcp->gc_name, name, i);
2314 if(n_poption & n_PO_D_V)
2315 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
2316 a_go_load(gcp);
2317 n_pstate &= ~n_PS_EXIT;
2318 jleave:
2319 NYD_LEAVE;
2322 FL void
2323 n_go_Xargs(char const **lines, size_t cnt){
2324 static char const name[] = "-X";
2326 union{
2327 ui64_t align;
2328 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
2329 } b;
2330 char const *srcp, *xsrcp;
2331 char *cp;
2332 size_t imax, i, len;
2333 struct a_go_ctx *gcp;
2334 NYD_ENTER;
2336 gcp = (void*)b.uf;
2337 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2339 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
2340 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
2341 memcpy(gcp->gc_name, name, sizeof name);
2343 /* The problem being that we want to support reverse solidus newline
2344 * escaping also within multiline -X, i.e., POSIX says:
2345 * An unquoted <backslash> at the end of a command line shall
2346 * be discarded and the next line shall continue the command
2347 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
2348 * entire lines array and set _MACRO_FREE_DATA */
2349 imax = cnt + 1;
2350 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
2352 /* For each of the input lines.. */
2353 for(i = len = 0, cp = NULL; cnt > 0;){
2354 bool_t keep;
2355 size_t j;
2357 if((j = strlen(srcp = *lines)) == 0){
2358 ++lines, --cnt;
2359 continue;
2362 /* Separate one line from a possible multiline input string */
2363 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
2364 *lines = &xsrcp[1];
2365 j = PTR2SIZE(xsrcp - srcp);
2366 }else
2367 ++lines, --cnt;
2369 /* The (separated) string may itself indicate soft newline escaping */
2370 if((keep = (srcp[j - 1] == '\\'))){
2371 size_t xj, xk;
2373 /* Need an uneven number of reverse solidus */
2374 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
2375 if(srcp[xj] != '\\')
2376 break;
2377 if(xk & 1)
2378 --j;
2379 else
2380 keep = FAL0;
2383 /* Strip any leading WS from follow lines, then */
2384 if(cp != NULL)
2385 while(j > 0 && spacechar(*srcp))
2386 ++srcp, --j;
2388 if(j > 0){
2389 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
2390 imax += 4;
2391 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
2392 imax);
2394 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
2395 memcpy(&cp[len], srcp, j);
2396 cp[len += j] = '\0';
2398 if(!keep)
2399 ++i;
2401 if(!keep)
2402 cp = NULL, len = 0;
2404 if(cp != NULL){
2405 assert(i + 1 < imax);
2406 gcp->gc_lines[i++] = cp;
2408 gcp->gc_lines[i] = NULL;
2410 a_go_load(gcp);
2411 if(n_pstate & n_PS_EXIT)
2412 exit(n_exit_status);
2413 NYD_LEAVE;
2416 FL int
2417 c_source(void *v){
2418 int rv;
2419 NYD_ENTER;
2421 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
2422 NYD_LEAVE;
2423 return rv;
2426 FL int
2427 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
2428 int rv;
2429 NYD_ENTER;
2431 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
2432 NYD_LEAVE;
2433 return rv;
2436 FL bool_t
2437 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
2438 void (*on_finalize)(void*), void *finalize_arg){
2439 struct a_go_ctx *gcp;
2440 size_t i;
2441 int rv;
2442 sigset_t osigmask;
2443 NYD_ENTER;
2445 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2447 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2448 (i = strlen(name) +1));
2449 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2451 hold_all_sigs();
2453 gcp->gc_outer = a_go_ctx;
2454 gcp->gc_osigmask = osigmask;
2455 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
2456 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2457 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2458 gcp->gc_lines = lines;
2459 gcp->gc_on_finalize = on_finalize;
2460 gcp->gc_finalize_arg = finalize_arg;
2461 memcpy(gcp->gc_name, name, i);
2463 a_go_ctx = gcp;
2464 n_go_data = &gcp->gc_data;
2465 n_pstate |= n_PS_ROBOT;
2466 rv = a_go_event_loop(gcp, gif);
2468 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
2469 if(a_go_xcall != NULL){
2470 if(a_go_xcall == (struct a_go_xcall*)-1)
2471 a_go_xcall = NULL;
2472 else if(a_go_xcall->gx_upto == gcp){
2473 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
2474 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
2475 while(a_go_xcall != NULL){
2476 char *cp, **argv;
2477 struct a_go_xcall *gxp;
2479 hold_all_sigs();
2481 gxp = a_go_xcall;
2482 a_go_xcall = NULL;
2484 /* Recreate the ARGV of this command on the LOFI memory of the
2485 * hosting a_go_ctx, so that it will become auto-reclaimed */
2486 /* C99 */{
2487 void *vp;
2489 vp = n_lofi_alloc(gxp->gx_buflen);
2490 cp = vp;
2491 argv = vp;
2493 cp += sizeof(*argv) * (gxp->gx_argc + 1);
2494 for(i = 0; i < gxp->gx_argc; ++i){
2495 argv[i] = cp;
2496 memcpy(cp, gxp->gx_argv[i].s, gxp->gx_argv[i].l +1);
2497 cp += gxp->gx_argv[i].l +1;
2499 argv[i] = NULL;
2500 n_free(gxp);
2502 rele_all_sigs();
2504 rv = (c_call(argv) == 0);
2506 n_lofi_free(argv);
2508 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP;
2511 NYD_LEAVE;
2512 return rv;
2515 FL bool_t
2516 n_go_command(enum n_go_input_flags gif, char const *cmd){
2517 struct a_go_ctx *gcp;
2518 bool_t rv;
2519 size_t i, ial;
2520 sigset_t osigmask;
2521 NYD_ENTER;
2523 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2525 i = strlen(cmd) +1;
2526 ial = n_ALIGN(i);
2527 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2528 ial + 2*sizeof(char*));
2529 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2531 hold_all_sigs();
2533 gcp->gc_outer = a_go_ctx;
2534 gcp->gc_osigmask = osigmask;
2535 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
2536 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2537 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2538 gcp->gc_lines = (void*)&gcp->gc_name[ial];
2539 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
2540 gcp->gc_lines[1] = NULL;
2542 a_go_ctx = gcp;
2543 n_go_data = &gcp->gc_data;
2544 n_pstate |= n_PS_ROBOT;
2545 rv = a_go_event_loop(gcp, gif);
2546 NYD_LEAVE;
2547 return rv;
2550 FL void
2551 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
2552 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
2553 struct a_go_ctx *gcp;
2554 size_t i;
2555 sigset_t osigmask;
2556 NYD_ENTER;
2558 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2560 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2561 (i = strlen(cmd) +1));
2562 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2564 hold_all_sigs();
2566 gcp->gc_outer = a_go_ctx;
2567 gcp->gc_osigmask = osigmask;
2568 gcp->gc_file = new_stdin;
2569 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE;
2570 gcp->gc_on_finalize = on_finalize;
2571 gcp->gc_finalize_arg = finalize_arg;
2572 gcp->gc_splice_stdin = n_stdin;
2573 gcp->gc_splice_stdout = n_stdout;
2574 gcp->gc_splice_psonce = n_psonce;
2575 memcpy(gcp->gc_name, cmd, i);
2577 n_stdin = new_stdin;
2578 n_stdout = new_stdout;
2579 n_psonce = new_psonce;
2580 a_go_ctx = gcp;
2581 n_pstate |= n_PS_ROBOT;
2583 rele_all_sigs();
2584 NYD_LEAVE;
2587 FL void
2588 n_go_splice_hack_remove_after_jump(void){
2589 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2592 FL bool_t
2593 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2594 struct a_go_ctx *gcp;
2595 bool_t rv;
2596 NYD2_ENTER;
2598 rv = FAL0;
2600 /* Only when interactive and startup completed */
2601 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) !=
2602 (n_PSO_INTERACTIVE | n_PSO_STARTED))
2603 goto jleave;
2605 /* Not when running any hook */
2606 if(n_pstate & n_PS_HOOK_MASK)
2607 goto jleave;
2609 /* Traverse up the stack:
2610 * . not when controlled by a child process
2611 * TODO . not when there are pipes involved, we neither handle job control,
2612 * TODO nor process groups, that is, controlling terminal acceptably
2613 * . not when sourcing a file */
2614 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2615 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2616 goto jleave;
2619 rv = TRU1;
2620 jleave:
2621 NYD2_LEAVE;
2622 return rv;
2625 /* s-it-mode */