Tweak previous, it added a bad memory access
[s-mailx.git] / go.c
blob3720b92c76e76d4c632670ccf067df32d29ebfa0
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 -aliases 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 */
102 a_GO_XCALL_LOOP_ERROR = 1u<<25, /* .. state machine error transporter */
103 a_GO_XCALL_LOOP_MASK = a_GO_XCALL_LOOP | a_GO_XCALL_LOOP_ERROR
106 enum a_go_cleanup_mode{
107 a_GO_CLEANUP_UNWIND = 1u<<0, /* Teardown all contexts except outermost */
108 a_GO_CLEANUP_TEARDOWN = 1u<<1, /* Teardown current context */
109 a_GO_CLEANUP_LOOPTICK = 1u<<2, /* Normal looptick cleanup */
110 a_GO_CLEANUP_MODE_MASK = n_BITENUM_MASK(0, 2),
112 a_GO_CLEANUP_ERROR = 1u<<8, /* Error occurred on level */
113 a_GO_CLEANUP_SIGINT = 1u<<9, /* Interrupt signal received */
114 a_GO_CLEANUP_HOLDALLSIGS = 1u<<10 /* hold_all_sigs() active TODO */
117 struct a_go_cmd_desc{
118 char const *gcd_name; /* Name of command */
119 int (*gcd_func)(void*); /* Implementor of command */
120 enum n_cmd_arg_flags gcd_caflags;
121 si16_t gcd_msgflag; /* Required flags of msgs */
122 si16_t gcd_msgmask; /* Relevant flags of msgs */
123 #ifdef HAVE_DOCSTRINGS
124 char const *gcd_doc; /* One line doc for command */
125 #endif
127 /* Yechh, can't initialize unions */
128 #define gcd_minargs gcd_msgflag /* Minimum argcount for WYSH/WYRA/RAWLIST */
129 #define gcd_maxargs gcd_msgmask /* Max argcount for WYSH/WYRA/RAWLIST */
131 struct a_go_alias{ /* TODO binary search */
132 struct a_go_alias *ga_next;
133 struct str ga_cmd; /* Data follows after .ga_name */
134 char ga_name[n_VFIELD_SIZE(0)];
137 struct a_go_eval_ctx{
138 struct str gec_line; /* The terminated data to _evaluate() */
139 ui32_t gec_line_size; /* May be used to store line memory size */
140 ui8_t gec__dummy[2];
141 bool_t gec_ever_seen; /* Has ever been used (main_loop() only) */
142 bool_t gec_add_history; /* Add command to history (TRUM1=gabby)? */
145 struct a_go_input_inject{
146 struct a_go_input_inject *gii_next;
147 size_t gii_len;
148 bool_t gii_commit;
149 char gii_dat[n_VFIELD_SIZE(7)];
152 struct a_go_ctx{
153 struct a_go_ctx *gc_outer;
154 sigset_t gc_osigmask;
155 ui32_t gc_flags; /* enum a_go_flags */
156 ui32_t gc_loff; /* Pseudo (macro): index in .gc_lines */
157 char **gc_lines; /* Pseudo content, lines unfolded */
158 FILE *gc_file; /* File we were in, if applicable */
159 struct a_go_input_inject *gc_inject; /* To be consumed first */
160 void (*gc_on_finalize)(void *);
161 void *gc_finalize_arg;
162 sigjmp_buf gc_eloop_jmp; /* TODO one day... for _event_loop() */
163 /* SPLICE hacks: saved stdin/stdout, saved pstate */
164 FILE *gc_splice_stdin;
165 FILE *gc_splice_stdout;
166 ui32_t gc_splice_psonce;
167 ui8_t gc_splice__dummy[4];
168 struct n_go_data_ctx gc_data;
169 char gc_name[n_VFIELD_SIZE(0)]; /* Name of file or macro */
172 struct a_go_xcall{
173 struct a_go_ctx *gx_upto; /* Unroll stack up to this level */
174 size_t gx_buflen; /* ARGV requires that much bytes, all in all */
175 size_t gx_argc;
176 struct str gx_argv[n_VFIELD_SIZE(0)];
179 static sighandler_type a_go_oldpipe;
180 static struct a_go_alias *a_go_aliases;
181 /* a_go_cmd_tab[] after fun protos */
183 /* Our current execution context, and the buffer backing the outermost level */
184 static struct a_go_ctx *a_go_ctx;
186 #define a_GO_MAINCTX_NAME "Main event loop"
187 static union{
188 ui64_t align;
189 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
190 sizeof(a_GO_MAINCTX_NAME)];
191 } a_go__mainctx_b;
193 /* `xcall' stack-avoidance bypass optimization */
194 static struct a_go_xcall *a_go_xcall;
196 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
198 /* Isolate the command from the arguments */
199 static char *a_go_isolate(char const *comm);
201 /* `eval' */
202 static int a_go_c_eval(void *vp);
204 /* `xcall' */
205 static int a_go_c_xcall(void *vp);
207 /* `(un)?commandalias' */
208 static int a_go_c_alias(void *vp);
209 static int a_go_c_unalias(void *vp);
211 /* Create a multiline info string about all known additional infos for lcp */
212 #ifdef HAVE_DOCSTRINGS
213 static char const *a_go_cmdinfo(struct a_go_cmd_desc const *gcdp);
214 #endif
216 /* Print a list of all commands */
217 static int a_go_c_list(void *vp);
219 static int a_go__pcmd_cmp(void const *s1, void const *s2);
221 /* `help' / `?' command */
222 static int a_go_c_help(void *vp);
224 /* `exit' and `quit' commands */
225 static int a_go_c_exit(void *vp);
226 static int a_go_c_quit(void *vp);
228 /* Print the version number of the binary */
229 static int a_go_c_version(void *vp);
231 static int a_go__version_cmp(void const *s1, void const *s2);
233 /* n_PS_STATE_PENDMASK requires some actions */
234 static void a_go_update_pstate(void);
236 /* Evaluate a single command */
237 static bool_t a_go_evaluate(struct a_go_eval_ctx *gecp);
239 /* Get first-fit, or NULL */
240 static struct a_go_cmd_desc const *a_go__firstfit(char const *comm);
242 /* Branch here on hangup signal and simulate "exit" */
243 static void a_go_hangup(int s);
245 /* The following gets called on receipt of an interrupt */
246 static void a_go_onintr(int s);
248 /* Cleanup current execution context, update the program state.
249 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
250 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we hold_all_sigs() */
251 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
253 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
254 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
255 * upon evaluation error, and TRU1 on success */
256 static bool_t a_go_file(char const *file, bool_t silent_open_error);
258 /* System resource file load()ing or -X command line option array traversal */
259 static bool_t a_go_load(struct a_go_ctx *gcp);
261 /* A simplified command loop for recursed state machines */
262 static bool_t a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
264 /* `read' */
265 static int a_go_c_read(void *vp);
267 static bool_t a_go__read_set(char const *cp, char const *value);
269 /* List of all commands, and list of commands which are specially treated
270 * and deduced in _evaluate(), but must offer normal descriptions for others */
271 #ifdef HAVE_DOCSTRINGS
272 # define DS(S) , S
273 #else
274 # define DS(S)
275 #endif
276 static struct a_go_cmd_desc const a_go_cmd_tab[] = {
277 #include "cmd-tab.h"
279 a_go_special_cmd_tab[] = {
280 { n_ns, NULL, n_CMD_ARG_TYPE_STRING, 0, 0
281 DS(N_("Comment command: ignore remaining (continuable) line")) },
282 { "-", NULL, n_CMD_ARG_TYPE_WYSH, 0, 0
283 DS(N_("Print out the preceding message")) }
285 #undef DS
287 static char *
288 a_go_isolate(char const *comm){
289 NYD2_ENTER;
290 while(*comm != '\0' &&
291 strchr("~|? \t0123456789&%@$^.:/-+*'\",;(`", *comm) == NULL)
292 ++comm;
293 NYD2_LEAVE;
294 return n_UNCONST(comm);
297 static int
298 a_go_c_eval(void *vp){
299 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
300 * TODO ARGV with shell rules, but if that is not possible then simply
301 * TODO adjust argv/argc of "the CmdCtx" that we will have "exec" real cmd */
302 struct n_string s_b, *sp;
303 si32_t rv;
304 size_t i, j;
305 char const **argv, *cp;
306 NYD_ENTER;
308 argv = vp;
310 for(j = i = 0; (cp = argv[i]) != NULL; ++i)
311 j += strlen(cp);
313 sp = n_string_creat_auto(&s_b);
314 sp = n_string_reserve(sp, j);
316 for(i = 0; (cp = argv[i]) != NULL; ++i){
317 if(i > 0)
318 sp = n_string_push_c(sp, ' ');
319 sp = n_string_push_cp(sp, cp);
322 /* TODO HACK! We should inherit the current n_go_input_flags via CmdCtx,
323 * TODO for now we don't have such sort of! n_PS_COMPOSE_MODE is a hack
324 * TODO by itself, since ever: misuse the hack for a hack.
325 * TODO Further more, exit handling is very grazy */
326 (void)/*XXX*/n_go_command((n_pstate & n_PS_COMPOSE_MODE
327 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT), n_string_cp(sp));
329 /* n_pstate_err_no = n_pstate_err_no; */
330 rv = (a_go_xcall != NULL) ? 0 : n_pstate_ex_no;
331 NYD_LEAVE;
332 return rv;
335 static int
336 a_go_c_xcall(void *vp){
337 struct a_go_xcall *gxp;
338 size_t i, j;
339 char *xcp;
340 char const **oargv, *cp;
341 int rv;
342 struct a_go_ctx *gcp;
343 NYD2_ENTER;
345 /* The context can only be a macro context, except that possibly a single
346 * level of `eval' (TODO: yet) was used to double-expand our arguments */
347 if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
348 gcp = gcp->gc_outer;
349 if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_CMD)) != a_GO_MACRO)
350 goto jerr;
352 /* Try to roll up the stack as much as possible.
353 * See a_GO_XCALL_LOOP flag description for more */
354 if(gcp->gc_outer != NULL){
355 if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
356 gcp = gcp->gc_outer;
357 }else{
358 /* Otherwise this macro is invoked from the top level, in which case we
359 * silently act as if we were `call'... */
360 rv = c_call(vp);
361 /* ...which means we must ensure the rest of the macro that was us
362 * doesn't become evaluated! */
363 a_go_xcall = (struct a_go_xcall*)-1;
364 goto jleave;
367 oargv = vp;
369 for(j = i = 0; (cp = oargv[i]) != NULL; ++i)
370 j += strlen(cp) +1;
372 gxp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_xcall, gx_argv) +
373 (sizeof(struct str) * i) + ++j);
374 gxp->gx_upto = gcp;
375 gxp->gx_buflen = (sizeof(char*) * (i + 1)) + j; /* ARGV inc. strings! */
376 gxp->gx_argc = i;
377 xcp = (char*)&gxp->gx_argv[i];
379 for(i = 0; (cp = oargv[i]) != NULL; ++i){
380 gxp->gx_argv[i].l = j = strlen(cp);
381 gxp->gx_argv[i].s = xcp;
382 memcpy(xcp, cp, ++j);
383 xcp += j;
386 a_go_xcall = gxp;
387 rv = 0;
388 jleave:
389 NYD2_LEAVE;
390 return rv;
391 jerr:
392 n_err(_("`xcall': can only be used inside a macro\n"));
393 n_pstate_err_no = n_ERR_INVAL;
394 rv = 1;
395 goto jleave;
398 static int
399 a_go_c_alias(void *vp){
400 struct a_go_alias *lgap, *gap;
401 size_t i, cl, nl;
402 char *cp;
403 char const **argv;
404 NYD_ENTER;
406 argv = vp;
408 /* Show the list? */
409 if(*argv == NULL){
410 FILE *fp;
412 if((fp = Ftmp(NULL, "commandalias", OF_RDWR | OF_UNLINK | OF_REGISTER)
413 ) == NULL)
414 fp = n_stdout;
416 for(i = 0, gap = a_go_aliases; gap != NULL; gap = gap->ga_next)
417 fprintf(fp, "commandalias %s %s\n",
418 gap->ga_name, n_shexp_quote_cp(gap->ga_cmd.s, TRU1));
420 if(fp != n_stdout){
421 page_or_print(fp, i);
422 Fclose(fp);
424 goto jleave;
427 /* Verify the name is a valid one, and not a command modifier */
428 if(*argv[0] == '\0' || *a_go_isolate(argv[0]) != '\0' ||
429 !asccasecmp(argv[0], "ignerr") || !asccasecmp(argv[0], "wysh") ||
430 !asccasecmp(argv[0], "vput")){
431 n_err(_("`commandalias': cannot canonicalize %s\n"),
432 n_shexp_quote_cp(argv[0], FAL0));
433 vp = NULL;
434 goto jleave;
437 /* Show command of single alias? */
438 if(argv[1] == NULL){
439 for(gap = a_go_aliases; gap != NULL; gap = gap->ga_next)
440 if(!strcmp(argv[0], gap->ga_name)){
441 fprintf(n_stdout, "commandalias %s %s\n",
442 gap->ga_name, n_shexp_quote_cp(gap->ga_cmd.s, TRU1));
443 goto jleave;
445 n_err(_("`commandalias': no such alias: %s\n"), argv[0]);
446 vp = NULL;
447 goto jleave;
450 /* Define command for alias: verify command content */
451 for(cl = 0, i = 1; (cp = n_UNCONST(argv[i])) != NULL; ++i)
452 if(*cp != '\0')
453 cl += strlen(cp) +1; /* SP or NUL */
454 if(cl == 0){
455 n_err(_("`commandalias': empty arguments after %s\n"), argv[0]);
456 vp = NULL;
457 goto jleave;
460 /* If the alias already exists, recreate */
461 for(lgap = NULL, gap = a_go_aliases; gap != NULL;
462 lgap = gap, gap = gap->ga_next)
463 if(!strcmp(gap->ga_name, argv[0])){
464 if(lgap != NULL)
465 lgap->ga_next = gap->ga_next;
466 else
467 a_go_aliases = gap->ga_next;
468 n_free(gap);
469 break;
472 nl = strlen(argv[0]) +1;
473 gap = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_alias, ga_name) + nl + cl);
474 gap->ga_next = a_go_aliases;
475 a_go_aliases = gap;
476 memcpy(gap->ga_name, argv[0], nl);
477 cp = gap->ga_cmd.s = &gap->ga_name[nl];
478 gap->ga_cmd.l = --cl;
480 while(*++argv != NULL)
481 if((i = strlen(*argv)) > 0){
482 memcpy(cp, *argv, i);
483 cp += i;
484 *cp++ = ' ';
486 *--cp = '\0';
487 jleave:
488 NYD_LEAVE;
489 return (vp == NULL);
492 static int
493 a_go_c_unalias(void *vp){
494 struct a_go_alias *lgap, *gap;
495 char const **argv, *cp;
496 int rv;
497 NYD_ENTER;
499 rv = 0;
500 argv = vp;
502 while((cp = *argv++) != NULL){
503 if(cp[0] == '*' && cp[1] == '\0'){
504 while((gap = a_go_aliases) != NULL){
505 a_go_aliases = gap->ga_next;
506 n_free(gap);
508 }else{
509 for(lgap = NULL, gap = a_go_aliases; gap != NULL;
510 lgap = gap, gap = gap->ga_next)
511 if(!strcmp(gap->ga_name, cp)){
512 if(lgap != NULL)
513 lgap->ga_next = gap->ga_next;
514 else
515 a_go_aliases = gap->ga_next;
516 n_free(gap);
517 goto jouter;
519 n_err(_("`uncommandalias': no such alias: %s\n"),
520 n_shexp_quote_cp(cp, FAL0));
521 rv = 1;
522 jouter: ;
525 NYD_LEAVE;
526 return rv;
529 #ifdef HAVE_DOCSTRINGS
530 static char const *
531 a_go_cmdinfo(struct a_go_cmd_desc const *gcdp){
532 struct n_string rvb, *rv;
533 char const *cp;
534 NYD2_ENTER;
536 rv = n_string_creat_auto(&rvb);
537 rv = n_string_reserve(rv, 80);
539 switch(gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK){
540 case n_CMD_ARG_TYPE_MSGLIST:
541 cp = N_("message-list");
542 break;
543 case n_CMD_ARG_TYPE_STRING:
544 case n_CMD_ARG_TYPE_RAWDAT:
545 cp = N_("string data");
546 break;
547 case n_CMD_ARG_TYPE_RAWLIST:
548 cp = N_("old-style quoting");
549 break;
550 case n_CMD_ARG_TYPE_NDMLIST:
551 cp = N_("message-list (no default)");
552 break;
553 case n_CMD_ARG_TYPE_WYRA:
554 cp = N_("`wysh' for sh(1)ell-style quoting");
555 break;
556 default:
557 case n_CMD_ARG_TYPE_WYSH:
558 cp = (gcdp->gcd_minargs == 0 && gcdp->gcd_maxargs == 0)
559 ? N_("sh(1)ell-style quoting (takes no arguments)")
560 : N_("sh(1)ell-style quoting");
561 break;
563 rv = n_string_push_cp(rv, V_(cp));
565 if(gcdp->gcd_caflags & n_CMD_ARG_V)
566 rv = n_string_push_cp(rv, _(" | vput modifier"));
567 if(gcdp->gcd_caflags & n_CMD_ARG_EM)
568 rv = n_string_push_cp(rv, _(" | error in *!*"));
570 if(gcdp->gcd_caflags & n_CMD_ARG_A)
571 rv = n_string_push_cp(rv, _(" | needs box"));
572 if(gcdp->gcd_caflags & n_CMD_ARG_I)
573 rv = n_string_push_cp(rv, _(" | ok: batch or interactive"));
574 if(gcdp->gcd_caflags & n_CMD_ARG_M)
575 rv = n_string_push_cp(rv, _(" | ok: send mode"));
576 if(gcdp->gcd_caflags & n_CMD_ARG_R)
577 rv = n_string_push_cp(rv, _(" | not ok: compose mode"));
578 if(gcdp->gcd_caflags & n_CMD_ARG_S)
579 rv = n_string_push_cp(rv, _(" | not ok: during startup"));
580 if(gcdp->gcd_caflags & n_CMD_ARG_X)
581 rv = n_string_push_cp(rv, _(" | ok: subprocess"));
583 if(gcdp->gcd_caflags & n_CMD_ARG_G)
584 rv = n_string_push_cp(rv, _(" | gabby history"));
586 cp = n_string_cp(rv);
587 NYD2_LEAVE;
588 return cp;
590 #endif /* HAVE_DOCSTRINGS */
592 static int
593 a_go_c_list(void *vp){
594 FILE *fp;
595 struct a_go_cmd_desc const **gcdpa, *gcdp, **gcdpa_curr;
596 size_t i, scrwid, l;
597 NYD_ENTER;
599 i = n_NELEM(a_go_cmd_tab) + n_NELEM(a_go_special_cmd_tab) +1;
600 gcdpa = n_autorec_alloc(sizeof(gcdp) * i);
602 for(i = 0; i < n_NELEM(a_go_cmd_tab); ++i)
603 gcdpa[i] = &a_go_cmd_tab[i];
604 /* C99 */{
605 size_t j;
607 for(j = 0; j < n_NELEM(a_go_special_cmd_tab); ++i, ++j)
608 gcdpa[i] = &a_go_special_cmd_tab[j];
610 gcdpa[i] = NULL;
612 if(*(void**)vp == NULL)
613 qsort(gcdpa, i, sizeof(*gcdpa), &a_go__pcmd_cmp);
615 if((fp = Ftmp(NULL, "list", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
616 fp = n_stdout;
618 fprintf(fp, _("Commands are:\n"));
619 scrwid = n_SCRNWIDTH_FOR_LISTS;
620 l = 1;
621 for(i = 0, gcdpa_curr = gcdpa; (gcdp = *gcdpa_curr++) != NULL;){
622 if(gcdp->gcd_func == &c_cmdnotsupp)
623 continue;
624 if(n_poption & n_PO_D_V){
625 fprintf(fp, "%s\n", gcdp->gcd_name);
626 ++l;
627 #ifdef HAVE_DOCSTRINGS
628 fprintf(fp, " : %s\n", V_(gcdp->gcd_doc));
629 ++l;
630 fprintf(fp, " : %s\n", a_go_cmdinfo(gcdp));
631 ++l;
632 #endif
633 }else{
634 size_t j;
636 if((i += (j = strlen(gcdp->gcd_name) + 2)) > scrwid){
637 i = j;
638 fprintf(fp, "\n");
639 ++l;
641 fprintf(fp, (*gcdpa_curr != NULL ? "%s, " : "%s\n"), gcdp->gcd_name);
645 if(fp != n_stdout){
646 page_or_print(fp, l);
647 Fclose(fp);
649 NYD_LEAVE;
650 return 0;
653 static int
654 a_go__pcmd_cmp(void const *s1, void const *s2){
655 struct a_go_cmd_desc const * const *gcdpa1, * const *gcdpa2;
656 int rv;
657 NYD2_ENTER;
659 gcdpa1 = s1;
660 gcdpa2 = s2;
661 rv = strcmp((*gcdpa1)->gcd_name, (*gcdpa2)->gcd_name);
662 NYD2_LEAVE;
663 return rv;
666 static int
667 a_go_c_help(void *vp){
668 int rv;
669 char *arg;
670 NYD_ENTER;
672 /* Help for a single command? */
673 if((arg = *(char**)vp) != NULL){
674 struct a_go_alias const *gap;
675 struct a_go_cmd_desc const *gcdp, *gcdp_max;
677 /* Aliases take precedence */
678 for(gap = a_go_aliases; gap != NULL; gap = gap->ga_next)
679 if(!strcmp(arg, gap->ga_name)){
680 fprintf(n_stdout, "%s -> ", arg);
681 arg = gap->ga_cmd.s;
682 break;
685 gcdp_max = &(gcdp = a_go_cmd_tab)[n_NELEM(a_go_cmd_tab)];
686 jredo:
687 for(; gcdp < gcdp_max; ++gcdp){
688 if(is_prefix(arg, gcdp->gcd_name)){
689 fputs(arg, n_stdout);
690 if(strcmp(arg, gcdp->gcd_name))
691 fprintf(n_stdout, " (%s)", gcdp->gcd_name);
692 }else
693 continue;
695 #ifdef HAVE_DOCSTRINGS
696 fprintf(n_stdout, ": %s", V_(gcdp->gcd_doc));
697 if(n_poption & n_PO_D_V)
698 fprintf(n_stdout, "\n : %s", a_go_cmdinfo(gcdp));
699 #endif
700 putc('\n', n_stdout);
701 rv = 0;
702 goto jleave;
705 if(gcdp_max == &a_go_cmd_tab[n_NELEM(a_go_cmd_tab)]){
706 gcdp_max = &(gcdp =
707 a_go_special_cmd_tab)[n_NELEM(a_go_special_cmd_tab)];
708 goto jredo;
711 if(gap != NULL){
712 fprintf(n_stdout, "%s\n", n_shexp_quote_cp(arg, TRU1));
713 rv = 0;
714 }else{
715 n_err(_("Unknown command: `%s'\n"), arg);
716 rv = 1;
718 }else{
719 /* Very ugly, but take care for compiler supported string lengths :( */
720 fputs(n_progname, n_stdout);
721 fputs(_(
722 " commands -- <msglist> denotes message specifications,\n"
723 "e.g., 1-5, :n or ., separated by spaces:\n"), n_stdout);
724 fputs(_(
725 "\n"
726 "type <msglist> type (`print') messages (honour `headerpick' etc.)\n"
727 "Type <msglist> like `type' but always show all headers\n"
728 "next goto and type next message\n"
729 "from <msglist> (search and) print header summary for the given list\n"
730 "headers header summary for messages surrounding \"dot\"\n"
731 "delete <msglist> delete messages (can be `undelete'd)\n"),
732 n_stdout);
734 fputs(_(
735 "\n"
736 "save <msglist> folder append messages to folder and mark as saved\n"
737 "copy <msglist> folder like `save', but don't mark them (`move' moves)\n"
738 "write <msglist> file write message contents to file (prompts for parts)\n"
739 "Reply <msglist> reply to message senders only\n"
740 "reply <msglist> like `Reply', but address all recipients\n"
741 "Lreply <msglist> forced mailing-list `reply' (see `mlist')\n"),
742 n_stdout);
744 fputs(_(
745 "\n"
746 "mail <recipients> compose a mail for the given recipients\n"
747 "file folder change to another mailbox\n"
748 "File folder like `file', but open readonly\n"
749 "quit quit and apply changes to the current mailbox\n"
750 "xit or exit like `quit', but discard changes\n"
751 "!shell command shell escape\n"
752 "list [<anything>] all available commands [in search order]\n"),
753 n_stdout);
755 rv = (ferror(n_stdout) != 0);
757 jleave:
758 NYD_LEAVE;
759 return rv;
762 static int
763 a_go_c_exit(void *vp){
764 NYD_ENTER;
765 n_UNUSED(vp);
766 n_psonce |= n_PSO_XIT;
767 NYD_LEAVE;
768 return 0;
771 static int
772 a_go_c_quit(void *vp){
773 NYD_ENTER;
774 n_UNUSED(vp);
775 n_psonce |= n_PSO_QUIT;
776 NYD_LEAVE;
777 return 0;
780 static int
781 a_go_c_version(void *vp){
782 int longest, rv;
783 char *iop;
784 char const *cp, **arr;
785 size_t i, i2;
786 NYD_ENTER;
787 n_UNUSED(vp);
789 fprintf(n_stdout, _("%s version %s\nFeatures included (+) or not (-)\n"),
790 n_uagent, ok_vlook(version));
792 /* *features* starts with dummy byte to avoid + -> *folder* expansions */
793 i = strlen(cp = &ok_vlook(features)[1]) +1;
794 iop = n_autorec_alloc(i);
795 memcpy(iop, cp, i);
797 arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT);
798 for(longest = 0, i = 0; (cp = n_strsep(&iop, ',', TRU1)) != NULL; ++i){
799 arr[i] = cp;
800 i2 = strlen(cp);
801 longest = n_MAX(longest, (int)i2);
803 qsort(arr, i, sizeof(cp), &a_go__version_cmp);
805 /* We use aligned columns, so don't use n_SCRNWIDTH_FOR_LISTS */
806 for(++longest, i2 = 0; i-- > 0;){
807 cp = *(arr++);
808 fprintf(n_stdout, "%-*s ", longest, cp);
809 i2 += longest;
810 if(UICMP(z, ++i2 + longest, >=, n_scrnwidth) || i == 0){
811 i2 = 0;
812 putc('\n', n_stdout);
816 if((rv = ferror(n_stdout) != 0))
817 clearerr(n_stdout);
818 NYD_LEAVE;
819 return rv;
822 static int
823 a_go__version_cmp(void const *s1, void const *s2){
824 char const * const *cp1, * const *cp2;
825 int rv;
826 NYD2_ENTER;
828 cp1 = s1;
829 cp2 = s2;
830 rv = strcmp(&(*cp1)[1], &(*cp2)[1]);
831 NYD2_LEAVE;
832 return rv;
835 static void
836 a_go_update_pstate(void){
837 bool_t act;
838 NYD_ENTER;
840 act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
841 n_pstate &= ~n_PS_PSTATE_PENDMASK;
843 if(act){
844 char buf[32];
846 snprintf(buf, sizeof buf, "%d", n_scrnwidth);
847 ok_vset(COLUMNS, buf);
848 snprintf(buf, sizeof buf, "%d", n_scrnheight);
849 ok_vset(LINES, buf);
851 NYD_LEAVE;
854 static bool_t
855 a_go_evaluate(struct a_go_eval_ctx *gecp){
856 /* xxx old style(9), but also old code */
857 /* TODO a_go_evaluate() should be splitted in multiple subfunctions,
858 * TODO `eval' should be a prefix, etc., a Ctx should be passed along */
859 struct str line;
860 char _wordbuf[2], **arglist_base/*[n_MAXARGC]*/, **arglist, *cp, *word;
861 struct a_go_alias *gap;
862 struct a_go_cmd_desc const *gcdp;
863 si32_t nerrn, nexn; /* TODO n_pstate_ex_no -> si64_t! */
864 int rv, c;
865 enum{
866 a_NONE = 0,
867 a_ALIAS_MASK = n_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
868 a_NOPREFIX = 1u<<4, /* Modifier prefix not allowed right now */
869 a_NOALIAS = 1u<<5, /* "No alias!" expansion modifier */
870 /* New modifier prefixes must be reflected in a_go_c_alias()! */
871 a_IGNERR = 1u<<6, /* ignerr modifier prefix */
872 a_WYSH = 1u<<7, /* XXX v15+ drop wysh modifier prefix */
873 a_VPUT = 1u<<8, /* vput modifier prefix */
874 a_NO_ERRNO = 1u<<16 /* Don't set n_pstate_err_no */
875 } flags;
876 NYD_ENTER;
878 flags = a_NONE;
879 rv = 1;
880 nerrn = n_ERR_NONE;
881 nexn = n_EXIT_OK;
882 gcdp = NULL;
883 gap = NULL;
884 arglist =
885 arglist_base = n_autorec_alloc(sizeof(*arglist_base) * n_MAXARGC);
886 line = gecp->gec_line; /* TODO const-ify original (buffer pointer)! */
887 assert(line.s[line.l] == '\0');
888 gecp->gec_add_history = FAL0;
890 /* Aliases that refer to shell commands or macro expansion restart */
891 jrestart:
893 /* Strip the white space away from end and beginning of command.
894 * XXX Ideally this will be m_string_trim() */
895 if(line.l > 0){
896 size_t i;
898 i = line.l;
899 for(cp = &line.s[i -1]; spacechar(*cp); --cp)
900 if(--i == 0)
901 break;
902 line.s[line.l = i] = '\0';
904 if(line.l > 0){
905 for(cp = line.s; spacechar(*cp); ++cp)
907 line.l -= PTR2SIZE(cp - line.s);
908 line.s = cp;
910 if(line.l == 0)
911 goto jempty;
913 /* Ignore null commands (comments) */
914 if(*(cp = line.s) == '#')
915 goto jret0;
917 /* Handle ! differently to get the correct lexical conventions */
918 if(*cp == '!')
919 ++cp;
920 /* Isolate the actual command; since it may not necessarily be
921 * separated from the arguments (as in `p1') we need to duplicate it to
922 * be able to create a NUL terminated version.
923 * We must be aware of several special one letter commands here */
924 else if((cp = a_go_isolate(cp)) == line.s &&
925 (*cp == '|' || *cp == '~' || *cp == '?'))
926 ++cp;
927 c = (int)PTR2SIZE(cp - line.s);
928 line.l -= c;
929 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
930 memcpy(word, arglist[0] = line.s, c);
931 word[c] = '\0';
932 line.s = cp;
934 /* No-expansion modifier? */
935 if(!(flags & a_NOPREFIX) && *word == '\\'){
936 ++word;
937 --c;
938 flags |= a_NOALIAS;
941 /* It may be a modifier prefix */
942 if(c == sizeof("ignerr") -1 && !asccasecmp(word, "ignerr")){
943 flags |= a_NOPREFIX | a_IGNERR;
944 goto jrestart;
945 }else if(c == sizeof("wysh") -1 && !asccasecmp(word, "wysh")){
946 flags |= a_NOPREFIX | a_WYSH;
947 goto jrestart;
948 }else if(c == sizeof("vput") -1 && !asccasecmp(word, "vput")){
949 flags |= a_NOPREFIX | a_VPUT;
950 goto jrestart;
953 /* Look up the command; if not found, bitch.
954 * Normally, a blank command would map to the first command in the
955 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
956 * confusion; act just the same for aliases */
957 if(*word == '\0'){
958 jempty:
959 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
960 gap != NULL)
961 goto jret0;
962 gcdp = &a_go_cmd_tab[0];
963 goto jexec;
966 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
967 ui8_t expcnt;
969 expcnt = (flags & a_ALIAS_MASK);
970 ++expcnt;
971 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
973 /* Avoid self-recursion; yes, the user could use \ no-expansion, but.. */
974 if(gap != NULL && !strcmp(word, gap->ga_name)){
975 if(n_poption & n_PO_D_V)
976 n_err(_("Actively avoiding self-recursion of `commandalias': %s\n"),
977 word);
978 }else for(gap = a_go_aliases; gap != NULL; gap = gap->ga_next)
979 if(!strcmp(word, gap->ga_name)){
980 if(line.l > 0){
981 size_t i;
983 i = gap->ga_cmd.l;
984 line.s = n_autorec_alloc(i + line.l +1);
985 memcpy(line.s, gap->ga_cmd.s, i);
986 memcpy(line.s + i, cp, line.l);
987 line.s[i += line.l] = '\0';
988 line.l = i;
989 }else{
990 line.s = gap->ga_cmd.s;
991 line.l = gap->ga_cmd.l;
993 goto jrestart;
997 if((gcdp = a_go__firstfit(word)) == NULL || gcdp->gcd_func == &c_cmdnotsupp){
998 bool_t s;
1000 if(!(s = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
1001 n_err(_("Unknown command%s: `%s'\n"),
1002 (s ? _(" (ignored due to `if' condition)") : n_empty), word);
1003 if(s)
1004 goto jret0;
1005 if(gcdp != NULL){
1006 c_cmdnotsupp(NULL);
1007 gcdp = NULL;
1009 nerrn = n_ERR_NOSYS;
1010 goto jleave;
1013 /* See if we should execute the command -- if a conditional we always
1014 * execute it, otherwise, check the state of cond */
1015 jexec:
1016 if(!(gcdp->gcd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip())
1017 goto jret0;
1019 nerrn = n_ERR_INVAL;
1021 /* Process the arguments to the command, depending on the type it expects */
1022 if((gcdp->gcd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
1023 !(n_poption & n_PO_BATCH_FLAG)){
1024 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
1025 gcdp->gcd_name);
1026 goto jleave;
1028 if(!(gcdp->gcd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
1029 n_err(_("May not execute `%s' while sending\n"), gcdp->gcd_name);
1030 goto jleave;
1032 if(gcdp->gcd_caflags & n_CMD_ARG_R){
1033 if(n_pstate & n_PS_COMPOSE_MODE){
1034 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
1035 n_err(_("Cannot invoke `%s' when in compose mode\n"), gcdp->gcd_name);
1036 goto jleave;
1038 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
1039 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
1040 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING)){
1041 n_err(_("Cannot invoke `%s' from a macro or during file inclusion\n"),
1042 gcdp->gcd_name);
1043 goto jleave;
1046 if((gcdp->gcd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED)){
1047 n_err(_("May not execute `%s' during startup\n"), gcdp->gcd_name);
1048 goto jleave;
1050 if(!(gcdp->gcd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
1051 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
1052 gcdp->gcd_name);
1053 goto jleave;
1056 if((gcdp->gcd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
1057 n_err(_("Cannot execute `%s' without active mailbox\n"), gcdp->gcd_name);
1058 goto jleave;
1060 if((gcdp->gcd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
1061 n_err(_("May not execute `%s' -- message file is read only\n"),
1062 gcdp->gcd_name);
1063 goto jleave;
1066 if(gcdp->gcd_caflags & n_CMD_ARG_O)
1067 n_OBSOLETE2(_("this command will be removed"), gcdp->gcd_name);
1069 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
1070 * TODO doesn't use any of those list commands which strip this mask,
1071 * TODO and for now we misuse bits for checking relation to history;
1072 * TODO argument state should be property of a per-command carrier instead */
1073 n_pstate &= ~n_PS_ARGLIST_MASK;
1075 if((flags & a_WYSH) &&
1076 (gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
1077 n_err(_("`wysh' prefix does not affect `%s'\n"), gcdp->gcd_name);
1078 flags &= ~a_WYSH;
1081 if(flags & a_VPUT){
1082 if(gcdp->gcd_caflags & n_CMD_ARG_V){
1083 char const *emsg;
1085 emsg = line.s; /* xxx Cannot pass &char* as char const**, so no cp */
1086 arglist[0] = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIMSPACE |
1087 n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_META_KEEP), &emsg);
1088 line.l -= PTR2SIZE(emsg - line.s);
1089 line.s = cp = n_UNCONST(emsg);
1090 if(cp == NULL)
1091 emsg = N_("could not parse input token");
1092 else if(!n_shexp_is_valid_varname(arglist[0]))
1093 emsg = N_("not a valid variable name");
1094 else if(!n_var_is_user_writable(arglist[0]))
1095 emsg = N_("either not a user writable, or a boolean variable");
1096 else
1097 emsg = NULL;
1098 if(emsg != NULL){
1099 n_err("`%s': vput: %s: %s\n",
1100 gcdp->gcd_name, V_(emsg), n_shexp_quote_cp(arglist[0], FAL0));
1101 nerrn = n_ERR_NOTSUP;
1102 goto jleave;
1104 ++arglist;
1105 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
1106 * TODO on in getrawlist() etc., i.e., the argument vector producers,
1107 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
1108 }else{
1109 n_err(_("`vput' prefix does not affect `%s'\n"), gcdp->gcd_name);
1110 flags &= ~a_VPUT;
1114 switch(gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK){
1115 case n_CMD_ARG_TYPE_MSGLIST:
1116 /* Message list defaulting to nearest forward legal message */
1117 if(n_msgvec == NULL)
1118 goto jemsglist;
1119 if((c = getmsglist(line.s, n_msgvec, gcdp->gcd_msgflag)) < 0){
1120 nerrn = n_ERR_NOMSG;
1121 flags |= a_NO_ERRNO;
1122 break;
1124 if(c == 0){
1125 if((n_msgvec[0] = first(gcdp->gcd_msgflag, gcdp->gcd_msgmask)) != 0)
1126 n_msgvec[1] = 0;
1128 if(n_msgvec[0] == 0){
1129 jemsglist:
1130 if(!(n_pstate & n_PS_HOOK_MASK))
1131 fprintf(n_stdout, _("No applicable messages\n"));
1132 nerrn = n_ERR_NOMSG;
1133 flags |= a_NO_ERRNO;
1134 break;
1136 rv = (*gcdp->gcd_func)(n_msgvec);
1137 break;
1139 case n_CMD_ARG_TYPE_NDMLIST:
1140 /* Message list with no defaults, but no error if none exist */
1141 if(n_msgvec == NULL)
1142 goto jemsglist;
1143 if((c = getmsglist(line.s, n_msgvec, gcdp->gcd_msgflag)) < 0){
1144 nerrn = n_ERR_NOMSG;
1145 flags |= a_NO_ERRNO;
1146 break;
1148 rv = (*gcdp->gcd_func)(n_msgvec);
1149 break;
1151 case n_CMD_ARG_TYPE_STRING:
1152 /* Just the straight string, old style, with leading blanks removed */
1153 for(cp = line.s; spacechar(*cp);)
1154 ++cp;
1155 rv = (*gcdp->gcd_func)(cp);
1156 break;
1157 case n_CMD_ARG_TYPE_RAWDAT:
1158 /* Just the straight string, leading blanks removed, placed in argv[] */
1159 for(cp = line.s; spacechar(*cp);)
1160 ++cp;
1161 *arglist++ = cp;
1162 *arglist = NULL;
1163 rv = (*gcdp->gcd_func)(arglist_base);
1164 break;
1166 case n_CMD_ARG_TYPE_WYSH:
1167 c = 1;
1168 if(0){
1169 /* FALLTHRU */
1170 case n_CMD_ARG_TYPE_WYRA:
1171 c = (flags & a_WYSH) ? 1 : 0;
1172 if(0){
1173 case n_CMD_ARG_TYPE_RAWLIST:
1174 c = 0;
1177 if((c = getrawlist((c != 0), arglist,
1178 n_MAXARGC - PTR2SIZE(arglist - arglist_base), line.s, line.l)) < 0){
1179 n_err(_("Invalid argument list\n"));
1180 flags |= a_NO_ERRNO;
1181 break;
1184 if(c < gcdp->gcd_minargs){
1185 n_err(_("`%s' requires at least %u arg(s)\n"),
1186 gcdp->gcd_name, (ui32_t)gcdp->gcd_minargs);
1187 flags |= a_NO_ERRNO;
1188 break;
1190 #undef gcd_minargs
1191 if(c > gcdp->gcd_maxargs){
1192 n_err(_("`%s' takes no more than %u arg(s)\n"),
1193 gcdp->gcd_name, (ui32_t)gcdp->gcd_maxargs);
1194 flags |= a_NO_ERRNO;
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 nerrn = n_ERR_NOTOBACCO;
1211 nexn = 1;
1212 goto jret0;
1215 if(!(gcdp->gcd_caflags & n_CMD_ARG_H))
1216 gecp->gec_add_history = (((gcdp->gcd_caflags & n_CMD_ARG_G) ||
1217 (n_pstate & n_PS_MSGLIST_GABBY)) ? TRUM1 : TRU1);
1219 if(rv != 0){
1220 if(!(flags & a_NO_ERRNO)){
1221 if(gcdp->gcd_caflags & n_CMD_ARG_EM)
1222 flags |= a_NO_ERRNO;
1223 else if((nerrn = n_err_no) == 0)
1224 nerrn = n_ERR_INVAL;
1225 }else
1226 flags ^= a_NO_ERRNO;
1227 }else if(gcdp->gcd_caflags & n_CMD_ARG_EM)
1228 flags |= a_NO_ERRNO;
1229 else
1230 nerrn = n_ERR_NONE;
1231 jleave:
1232 nexn = rv;
1234 if(flags & a_IGNERR){
1235 n_pstate &= ~n_PS_ERR_EXIT_MASK;
1236 n_exit_status = n_EXIT_OK;
1237 }else if(rv != 0){
1238 bool_t bo;
1240 if((bo = ok_blook(batch_exit_on_error))){
1241 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
1242 if(!(n_poption & n_PO_BATCH_FLAG))
1243 bo = FAL0;
1245 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
1246 n_pstate |= n_PS_ERR_QUIT;
1247 else if(!(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) &&
1248 ok_blook(posix))
1249 n_pstate |= n_PS_ERR_XIT;
1250 else
1251 rv = 0;
1253 if(rv != 0){
1254 if(n_exit_status == n_EXIT_OK)
1255 n_exit_status = n_EXIT_ERR;
1256 if((n_poption & n_PO_D_V) &&
1257 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
1258 n_alert(_("Non-interactive, bailing out due to errors "
1259 "in startup load phase"));
1260 goto jret;
1264 if(gcdp == NULL)
1265 goto jret0;
1266 if((gcdp->gcd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
1267 if(visible(dot))
1268 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
1269 sizeof("\\type") -1);
1271 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
1272 !(gcdp->gcd_caflags & n_CMD_ARG_T))
1273 n_pstate |= n_PS_SAW_COMMAND;
1274 jret0:
1275 rv = 0;
1276 jret:
1277 if(!(flags & a_NO_ERRNO))
1278 n_pstate_err_no = nerrn;
1279 n_pstate_ex_no = nexn;
1280 NYD_LEAVE;
1281 return (rv == 0);
1284 static struct a_go_cmd_desc const *
1285 a_go__firstfit(char const *comm){ /* TODO *hashtable*! linear list search!!! */
1286 struct a_go_cmd_desc const *gcdp;
1287 NYD2_ENTER;
1289 for(gcdp = a_go_cmd_tab; gcdp < &a_go_cmd_tab[n_NELEM(a_go_cmd_tab)]; ++gcdp)
1290 if(*comm == *gcdp->gcd_name && is_prefix(comm, gcdp->gcd_name))
1291 goto jleave;
1292 gcdp = NULL;
1293 jleave:
1294 NYD2_LEAVE;
1295 return gcdp;
1298 static void
1299 a_go_hangup(int s){
1300 NYD_X; /* Signal handler */
1301 n_UNUSED(s);
1302 /* nothing to do? */
1303 exit(n_EXIT_ERR);
1306 static void
1307 a_go_onintr(int s){ /* TODO block signals while acting */
1308 NYD_X; /* Signal handler */
1309 n_UNUSED(s);
1311 safe_signal(SIGINT, a_go_onintr);
1313 termios_state_reset();
1315 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
1317 if(interrupts != 1)
1318 n_err_sighdl(_("Interrupt\n"));
1319 safe_signal(SIGPIPE, a_go_oldpipe);
1320 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
1323 static void
1324 a_go_cleanup(enum a_go_cleanup_mode gcm){
1325 /* Signals blocked */
1326 struct a_go_ctx *gcp;
1327 NYD_ENTER;
1329 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1330 hold_all_sigs();
1331 jrestart:
1332 gcp = a_go_ctx;
1334 /* Free input injections of this level first */
1335 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
1336 struct a_go_input_inject **giipp, *giip;
1338 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
1339 *giipp = giip->gii_next;
1340 n_free(giip);
1344 /* Cleanup non-crucial external stuff */
1345 n_COLOUR(
1346 if(gcp->gc_data.gdc_colour != NULL)
1347 n_colour_stack_del(NULL);
1350 /* Work the actual context (according to cleanup mode) */
1351 if(gcp->gc_outer == NULL){
1352 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
1353 a_go_xcall = NULL;
1354 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1355 n_pstate &= ~n_PS_ERR_EXIT_MASK;
1356 close_all_files();
1357 }else{
1358 if(!(n_pstate & n_PS_SOURCING))
1359 close_all_files();
1362 n_memory_reset();
1364 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1365 assert(a_go_xcall == NULL);
1366 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
1367 assert(gcp->gc_on_finalize == NULL);
1368 assert(gcp->gc_data.gdc_colour == NULL);
1369 goto jxleave;
1370 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
1371 n_memory_reset();
1372 goto jxleave;
1373 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
1374 n_stdin = gcp->gc_splice_stdin;
1375 n_stdout = gcp->gc_splice_stdout;
1376 n_psonce = gcp->gc_splice_psonce;
1377 goto jstackpop;
1380 /* Cleanup crucial external stuff */
1381 if(gcp->gc_data.gdc_ifcond != NULL){
1382 n_cnd_if_stack_del(gcp->gc_data.gdc_ifcond);
1383 if(!(gcp->gc_flags & a_GO_FORCE_EOF) &&
1384 !(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
1385 a_go_xcall == NULL)
1386 n_err(_("Unmatched `if' at end of %s %s\n"),
1387 ((gcp->gc_flags & a_GO_MACRO
1388 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
1389 : _("`source'd file"))),
1390 gcp->gc_name);
1391 gcm |= a_GO_CLEANUP_ERROR;
1394 /* Teardown context */
1395 if(gcp->gc_flags & a_GO_MACRO){
1396 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
1397 char **lp;
1399 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
1400 n_free(*lp);
1401 ++gcp->gc_loff;
1403 /* Part of gcp's memory chunk, then */
1404 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
1405 n_free(gcp->gc_lines);
1407 }else if(gcp->gc_flags & a_GO_PIPE)
1408 /* XXX command manager should -TERM then -KILL instead of hoping
1409 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
1410 Pclose(gcp->gc_file, TRU1);
1411 else if(gcp->gc_flags & a_GO_FILE)
1412 Fclose(gcp->gc_file);
1414 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
1415 if(gcp->gc_data.gdc_mempool != NULL)
1416 n_memory_pool_pop(NULL);
1417 }else
1418 n_memory_reset();
1420 jstackpop:
1421 n_go_data = &(a_go_ctx = gcp->gc_outer)->gc_data;
1422 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
1423 (a_GO_MACRO | a_GO_SUPER_MACRO)){
1424 n_pstate &= ~n_PS_SOURCING;
1425 assert(n_pstate & n_PS_ROBOT);
1426 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
1427 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1428 else
1429 assert(n_pstate & n_PS_ROBOT);
1431 if(gcp->gc_on_finalize != NULL)
1432 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
1434 if(gcm & a_GO_CLEANUP_ERROR){
1435 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
1436 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
1437 goto jerr;
1439 jleave:
1440 if(gcp->gc_flags & a_GO_FREE)
1441 n_free(gcp);
1443 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
1444 goto jrestart;
1446 jxleave:
1447 NYD_LEAVE;
1448 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1449 rele_all_sigs();
1450 return;
1452 jerr:
1453 /* With *posix* we follow what POSIX says:
1454 * Any errors in the start-up file shall either cause mailx to
1455 * terminate with a diagnostic message and a non-zero status or to
1456 * continue after writing a diagnostic message, ignoring the
1457 * remainder of the lines in the start-up file
1458 * Print the diagnostic only for the outermost resource unless the user
1459 * is debugging or in verbose mode */
1460 if((n_poption & n_PO_D_V) ||
1461 (!(n_psonce & n_PSO_STARTED) &&
1462 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1463 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
1464 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1465 n_alert(_("Stopped %s %s due to errors%s"),
1466 (n_psonce & n_PSO_STARTED
1467 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1468 : (gcp->gc_flags & a_GO_MACRO
1469 ? (gcp->gc_flags & a_GO_MACRO_CMD
1470 ? _("evaluating command") : _("evaluating macro"))
1471 : (gcp->gc_flags & a_GO_PIPE
1472 ? _("executing `source'd pipe")
1473 : (gcp->gc_flags & a_GO_FILE
1474 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1476 : (gcp->gc_flags & a_GO_MACRO
1477 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
1478 ? _("evaluating command line") : _("evaluating macro"))
1479 : _("loading initialization resource"))),
1480 gcp->gc_name,
1481 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
1482 goto jleave;
1485 static bool_t
1486 a_go_file(char const *file, bool_t silent_open_error){
1487 struct a_go_ctx *gcp;
1488 sigset_t osigmask;
1489 size_t nlen;
1490 char *nbuf;
1491 bool_t ispipe;
1492 FILE *fip;
1493 NYD_ENTER;
1495 fip = NULL;
1497 /* Being a command argument file is space-trimmed *//* TODO v15 with
1498 * TODO WYRALIST this is no longer necessary true, and for that we
1499 * TODO don't set _PARSE_TRIMSPACE because we cannot! -> cmd-tab.h!! */
1500 #if 0
1501 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
1502 file[--nlen] == '|')))
1503 #else
1504 ispipe = FAL0;
1505 if(!silent_open_error){
1506 for(nlen = strlen(file); nlen > 0;){
1507 char c;
1509 c = file[--nlen];
1510 if(!spacechar(c)){
1511 if(c == '|'){
1512 nbuf = savestrbuf(file, nlen);
1513 ispipe = TRU1;
1515 break;
1519 #endif
1521 if(ispipe){
1522 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
1523 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
1524 goto jeopencheck;
1525 }else if((nbuf = fexpand(file, FEXP_LOCAL)) == NULL)
1526 goto jeopencheck;
1527 else if((fip = Fopen(nbuf, "r")) == NULL){
1528 jeopencheck:
1529 if(!silent_open_error || (n_poption & n_PO_D_V))
1530 n_perr(nbuf, 0);
1531 if(silent_open_error)
1532 fip = (FILE*)-1;
1533 goto jleave;
1536 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1538 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1539 (nlen = strlen(nbuf) +1));
1540 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1542 hold_all_sigs();
1544 gcp->gc_outer = a_go_ctx;
1545 gcp->gc_osigmask = osigmask;
1546 gcp->gc_file = fip;
1547 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1548 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1549 memcpy(gcp->gc_name, nbuf, nlen);
1551 a_go_ctx = gcp;
1552 n_go_data = &gcp->gc_data;
1553 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1554 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1555 fip = NULL;
1556 jleave:
1557 NYD_LEAVE;
1558 return (fip != NULL);
1561 static bool_t
1562 a_go_load(struct a_go_ctx *gcp){
1563 NYD2_ENTER;
1565 assert(!(n_psonce & n_PSO_STARTED));
1566 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1568 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1569 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1571 hold_all_sigs();
1573 /* POSIX:
1574 * Any errors in the start-up file shall either cause mailx to terminate
1575 * with a diagnostic message and a non-zero status or to continue after
1576 * writing a diagnostic message, ignoring the remainder of the lines in
1577 * the start-up file. */
1578 gcp->gc_outer = a_go_ctx;
1579 a_go_ctx = gcp;
1580 n_go_data = &gcp->gc_data;
1581 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1582 n_pstate |= n_PS_ROBOT |
1583 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1585 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1587 rele_all_sigs();
1589 n_go_main_loop();
1590 NYD2_LEAVE;
1591 return (((n_psonce & n_PSO_EXIT_MASK) |
1592 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1595 static void
1596 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1597 NYD_X; /* Signal handler */
1598 n_UNUSED(sig);
1599 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1602 static bool_t
1603 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1604 sighandler_type soldhdl;
1605 struct a_go_eval_ctx gec;
1606 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1607 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1608 sigset_t osigmask;
1609 NYD2_ENTER;
1611 memset(&gec, 0, sizeof gec);
1612 osigmask = gcp->gc_osigmask;
1613 hadint = FAL0;
1614 f = a_RETOK;
1616 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1617 safe_signal(SIGINT, &a_go__eloopint);
1618 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1619 hold_all_sigs();
1620 hadint = TRU1;
1621 f &= ~a_RETOK;
1622 a_go_xcall = NULL;
1623 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1624 goto jjump;
1628 for(;; f |= a_TICKED){
1629 int n;
1631 if(f & a_TICKED)
1632 n_memory_reset();
1634 /* Read a line of commands and handle end of file specially */
1635 gec.gec_line.l = gec.gec_line_size;
1636 rele_all_sigs();
1637 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL);
1638 hold_all_sigs();
1639 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1640 gec.gec_line.l = (ui32_t)n;
1642 if(n < 0)
1643 break;
1645 rele_all_sigs();
1646 if(!a_go_evaluate(&gec))
1647 f &= ~a_RETOK;
1648 hold_all_sigs();
1650 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1651 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1652 break;
1655 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1656 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1657 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1658 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1660 if(gec.gec_line.s != NULL)
1661 n_free(gec.gec_line.s);
1663 if(soldhdl != SIG_IGN)
1664 safe_signal(SIGINT, soldhdl);
1665 NYD2_LEAVE;
1666 rele_all_sigs();
1667 if(hadint){
1668 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1669 n_raise(SIGINT);
1671 return (f & a_RETOK);
1674 static int
1675 a_go_c_read(void *v){ /* TODO IFS? how? -r */
1676 struct n_sigman sm;
1677 char const **argv, *cp, *cp2;
1678 char *linebuf;
1679 size_t linesize;
1680 int rv;
1681 NYD2_ENTER;
1683 rv = 0;
1684 linesize = 0;
1685 linebuf = NULL;
1686 argv = v;
1688 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1689 case 0:
1690 break;
1691 default:
1692 n_pstate_err_no = n_ERR_INTR;
1693 rv = 1;
1694 goto jleave;
1696 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
1697 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
1698 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
1699 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
1700 NULL, &linebuf, &linesize, NULL);
1701 n_pstate_err_no = n_ERR_NONE; /* TODO I/O error if rv<0! */
1702 if(rv < 0)
1703 goto jleave;
1705 if(rv > 0){
1706 cp = linebuf;
1708 for(rv = 0; *argv != NULL; ++argv){
1709 char c;
1711 while(spacechar(*cp))
1712 ++cp;
1713 if(*cp == '\0')
1714 break;
1716 /* The last variable gets the remaining line less trailing IFS */
1717 if(argv[1] == NULL){
1718 for(cp2 = cp; *cp2 != '\0'; ++cp2)
1720 for(; cp2 > cp; --cp2){
1721 c = cp2[-1];
1722 if(!spacechar(c))
1723 break;
1725 }else
1726 for(cp2 = cp; (c = *++cp2) != '\0';)
1727 if(spacechar(c))
1728 break;
1730 /* C99 xxx This is a CC warning workaround (-Wbad-function-cast) */{
1731 char *vcp;
1733 vcp = savestrbuf(cp, PTR2SIZE(cp2 - cp));
1734 if(!a_go__read_set(*argv, vcp)){
1735 n_pstate_err_no = n_ERR_NOTSUP;
1736 rv = 1;
1737 break;
1741 cp = cp2;
1745 /* Set the remains to the empty string */
1746 for(; *argv != NULL; ++argv)
1747 if(!a_go__read_set(*argv, n_empty)){
1748 n_pstate_err_no = n_ERR_NOTSUP;
1749 rv = 1;
1750 break;
1753 n_sigman_cleanup_ping(&sm);
1754 jleave:
1755 if(linebuf != NULL)
1756 n_free(linebuf);
1757 NYD2_LEAVE;
1758 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1759 return rv;
1762 static bool_t
1763 a_go__read_set(char const *cp, char const *value){
1764 bool_t rv;
1765 NYD2_ENTER;
1767 if(!n_shexp_is_valid_varname(cp))
1768 value = N_("not a valid variable name");
1769 else if(!n_var_is_user_writable(cp))
1770 value = N_("variable is read-only");
1771 else if(!n_var_vset(cp, (uintptr_t)value))
1772 value = N_("failed to update variable value");
1773 else{
1774 rv = TRU1;
1775 goto jleave;
1777 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
1778 rv = FAL0;
1779 jleave:
1780 NYD2_LEAVE;
1781 return rv;
1784 FL int
1785 c_cmdnotsupp(void *vp){
1786 NYD_ENTER;
1787 n_UNUSED(vp);
1788 n_err(_("The requested feature is not compiled in\n"));
1789 NYD_LEAVE;
1790 return 1;
1793 FL void
1794 n_go_init(void){
1795 struct a_go_ctx *gcp;
1796 NYD2_ENTER;
1798 assert(n_stdin != NULL);
1800 gcp = (void*)a_go__mainctx_b.uf;
1801 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1802 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1803 gcp->gc_file = n_stdin;
1804 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1805 a_go_ctx = gcp;
1806 n_go_data = &gcp->gc_data;
1808 n_child_manager_start();
1809 NYD2_LEAVE;
1812 FL bool_t
1813 n_go_main_loop(void){ /* FIXME */
1814 struct a_go_eval_ctx gec;
1815 int n, eofcnt;
1816 bool_t volatile rv;
1817 NYD_ENTER;
1819 rv = TRU1;
1821 if (!(n_pstate & n_PS_SOURCING)) {
1822 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1823 safe_signal(SIGINT, &a_go_onintr);
1824 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1825 safe_signal(SIGHUP, &a_go_hangup);
1827 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1828 safe_signal(SIGPIPE, a_go_oldpipe);
1830 memset(&gec, 0, sizeof gec);
1832 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1833 hold_all_sigs();
1835 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1836 interrupts = 0;
1838 if(gec.gec_ever_seen)
1839 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1841 if (!(n_pstate & n_PS_SOURCING)) {
1842 char *cp;
1844 /* TODO Note: this buffer may contain a password. We should redefine
1845 * TODO the code flow which has to do that */
1846 if ((cp = termios_state.ts_linebuf) != NULL) {
1847 termios_state.ts_linebuf = NULL;
1848 termios_state.ts_linesize = 0;
1849 n_free(cp); /* TODO pool give-back */
1851 if (gec.gec_line.l > LINESIZE * 3) {
1852 n_free(gec.gec_line.s);
1853 gec.gec_line.s = NULL;
1854 gec.gec_line.l = gec.gec_line_size = 0;
1858 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1859 char *cp;
1861 if ((cp = ok_vlook(newmail)) != NULL) {
1862 struct stat st;
1864 /* FIXME TEST WITH NOPOLL ETC. !!! */
1865 n = (cp != NULL && strcmp(cp, "nopoll"));
1866 if ((mb.mb_type == MB_FILE && !stat(mailname, &st) &&
1867 st.st_size > mailsize) ||
1868 (mb.mb_type == MB_MAILDIR && n != 0)) {
1869 size_t odot = PTR2SIZE(dot - message);
1870 ui32_t odid = (n_pstate & n_PS_DID_PRINT_DOT);
1871 int i;
1873 rele_all_sigs();
1874 i = setfile(mailname,
1875 FEDIT_NEWMAIL |
1876 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY));
1877 hold_all_sigs();
1878 if(i < 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 rele_all_sigs();
1894 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1895 &gec.gec_line.s, &gec.gec_line.l, NULL);
1896 hold_all_sigs();
1897 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1898 gec.gec_line.l = (ui32_t)n;
1900 if (n < 0) {
1901 if (!(n_pstate & n_PS_ROBOT) &&
1902 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1903 ++eofcnt < 4) {
1904 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1905 n_go_input_clearerr();
1906 continue;
1908 break;
1911 n_pstate &= ~n_PS_HOOK_MASK;
1912 rele_all_sigs();
1913 rv = a_go_evaluate(&gec);
1914 hold_all_sigs();
1916 if(!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE) &&
1917 gec.gec_add_history)
1918 n_tty_addhist(gec.gec_line.s, (gec.gec_add_history != TRU1));
1920 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1921 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1922 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1923 default: break;
1925 if(n_psonce & n_PSO_EXIT_MASK)
1926 break;
1928 if(!rv)
1929 break;
1932 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1933 (rv ? 0 : a_GO_CLEANUP_ERROR));
1935 if (gec.gec_line.s != NULL)
1936 n_free(gec.gec_line.s);
1938 rele_all_sigs();
1939 NYD_LEAVE;
1940 return rv;
1943 FL void
1944 n_go_input_clearerr(void){
1945 FILE *fp;
1946 NYD2_ENTER;
1948 fp = NULL;
1950 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1951 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1952 fp = a_go_ctx->gc_file;
1954 if(fp != NULL)
1955 clearerr(fp);
1956 NYD2_LEAVE;
1959 FL void
1960 n_go_input_force_eof(void){
1961 NYD_ENTER;
1962 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1963 NYD_LEAVE;
1966 FL void
1967 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1968 size_t len){
1969 NYD_ENTER;
1970 if(len == UIZ_MAX)
1971 len = strlen(buf);
1973 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1974 len > 0){
1975 size_t i;
1976 struct a_go_input_inject *giip, **giipp;
1978 hold_all_sigs();
1980 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1981 ) + 1 + len +1);
1982 giipp = &a_go_ctx->gc_inject;
1983 giip->gii_next = *giipp;
1984 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1985 if(buf[i = 0] != ' ' && !(giif & n_GO_INPUT_INJECT_HISTORY))
1986 giip->gii_dat[i++] = ' '; /* TODO prim. hack to avoid history put! */
1987 memcpy(&giip->gii_dat[i], buf, len);
1988 i += len;
1989 giip->gii_dat[giip->gii_len = i] = '\0';
1990 *giipp = giip;
1992 rele_all_sigs();
1994 NYD_LEAVE;
1997 FL int
1998 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1999 size_t *linesize, char const *string n_MEMORY_DEBUG_ARGS){
2000 /* TODO readline: linebuf pool!; n_go_input should return si64_t */
2001 struct n_string xprompt;
2002 FILE *ifile;
2003 bool_t doprompt, dotty;
2004 char const *iftype;
2005 int nold, n;
2006 NYD2_ENTER;
2008 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
2009 hold_all_sigs();
2011 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
2012 n = -1;
2013 goto jleave;
2016 if(gif & n_GO_INPUT_FORCE_STDIN)
2017 goto jforce_stdin;
2019 /* Special case macro mode: never need to prompt, lines have always been
2020 * unfolded already */
2021 if(a_go_ctx->gc_flags & a_GO_MACRO){
2022 struct a_go_input_inject *giip;
2024 if(*linebuf != NULL)
2025 n_free(*linebuf);
2027 /* Injection in progress? Don't care about the autocommit state here */
2028 if((giip = a_go_ctx->gc_inject) != NULL){
2029 a_go_ctx->gc_inject = giip->gii_next;
2031 *linesize = giip->gii_len;
2032 *linebuf = (char*)giip;
2033 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
2034 iftype = "INJECTION";
2035 }else{
2036 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
2037 *linesize = 0;
2038 n = -1;
2039 goto jleave;
2042 ++a_go_ctx->gc_loff;
2043 *linesize = strlen(*linebuf);
2044 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
2045 *linebuf = sbufdup(*linebuf, *linesize);
2047 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
2048 ? "-X OPTION"
2049 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
2051 n = (int)*linesize;
2052 n_pstate |= n_PS_READLINE_NL;
2053 goto jhave_dat;
2054 }else{
2055 /* Injection in progress? */
2056 struct a_go_input_inject **giipp, *giip;
2058 giipp = &a_go_ctx->gc_inject;
2060 if((giip = *giipp) != NULL){
2061 *giipp = giip->gii_next;
2063 if(giip->gii_commit){
2064 if(*linebuf != NULL)
2065 n_free(*linebuf);
2067 /* Simply reuse the buffer */
2068 n = (int)(*linesize = giip->gii_len);
2069 *linebuf = (char*)giip;
2070 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
2071 iftype = "INJECTION";
2072 n_pstate |= n_PS_READLINE_NL;
2073 goto jhave_dat;
2074 }else{
2075 string = savestrbuf(giip->gii_dat, giip->gii_len);
2076 n_free(giip);
2081 jforce_stdin:
2082 n_pstate &= ~n_PS_READLINE_NL;
2083 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
2084 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
2085 doprompt = ((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
2086 (n_PSO_INTERACTIVE | n_PSO_STARTED) && !(n_pstate & n_PS_ROBOT));
2087 dotty = (doprompt && !ok_blook(line_editor_disable));
2088 if(!doprompt)
2089 gif |= n_GO_INPUT_PROMPT_NONE;
2090 else{
2091 if(!dotty)
2092 n_string_creat_auto(&xprompt);
2093 if(prompt == NULL)
2094 gif |= n_GO_INPUT_PROMPT_EVAL;
2097 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
2098 if(!dotty && (gif & n_GO_INPUT_PROMPT_NONE))
2099 fflush(n_stdout);
2101 ifile = (gif & n_GO_INPUT_FORCE_STDIN) ? n_stdin : a_go_ctx->gc_file;
2102 if(ifile == NULL){
2103 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
2104 (a_go_ctx->gc_flags & a_GO_MACRO));
2105 ifile = n_stdin;
2108 for(nold = n = 0;;){
2109 if(dotty){
2110 assert(ifile == n_stdin);
2111 if(string != NULL && (n = (int)strlen(string)) > 0){
2112 if(*linesize > 0)
2113 *linesize += n +1;
2114 else
2115 *linesize = (size_t)n + LINESIZE +1;
2116 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
2117 memcpy(*linebuf, string, (size_t)n +1);
2119 string = NULL;
2121 rele_all_sigs();
2123 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n
2124 n_MEMORY_DEBUG_ARGSCALL);
2126 hold_all_sigs();
2127 }else{
2128 if(!(gif & n_GO_INPUT_PROMPT_NONE))
2129 n_tty_create_prompt(&xprompt, prompt, gif);
2131 rele_all_sigs();
2133 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
2134 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
2135 fflush(n_stdout);
2138 n = (readline_restart)(ifile, linebuf, linesize, n
2139 n_MEMORY_DEBUG_ARGSCALL);
2141 hold_all_sigs();
2143 if(n > 0 && nold > 0){
2144 char const *cp;
2145 int i;
2147 i = 0;
2148 cp = &(*linebuf)[nold];
2149 while(spacechar(*cp) && n - i >= nold)
2150 ++cp, ++i;
2151 if(i > 0){
2152 memmove(&(*linebuf)[nold], cp, n - nold - i);
2153 n -= i;
2154 (*linebuf)[n] = '\0';
2159 if(n <= 0)
2160 break;
2162 /* POSIX says:
2163 * An unquoted <backslash> at the end of a command line shall
2164 * be discarded and the next line shall continue the command */
2165 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\'){
2166 if(dotty)
2167 n_pstate |= n_PS_READLINE_NL;
2168 break;
2170 /* Definitely outside of quotes, thus the quoting rules are so that an
2171 * uneven number of successive reverse solidus at EOL is a continuation */
2172 if(n > 1){
2173 size_t i, j;
2175 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
2176 if((*linebuf)[i] != '\\')
2177 break;
2178 if(!(j & 1))
2179 break;
2181 (*linebuf)[nold = --n] = '\0';
2182 gif |= n_GO_INPUT_NL_FOLLOW;
2185 if(n < 0)
2186 goto jleave;
2187 (*linebuf)[*linesize = n] = '\0';
2189 jhave_dat:
2190 #if 0
2191 if(gif & n_GO_INPUT_DROP_TRAIL_SPC){
2192 char *cp, c;
2193 size_t i;
2195 for(cp = &(*linebuf)[i = (size_t)n];; --i){
2196 c = *--cp;
2197 if(!spacechar(c))
2198 break;
2200 (*linebuf)[n = (int)i] = '\0';
2203 if(gif & n_GO_INPUT_DROP_LEAD_SPC){
2204 char *cp, c;
2205 size_t j, i;
2207 for(cp = &(*linebuf)[0], j = (size_t)n, i = 0; i < j; ++i){
2208 c = *cp++;
2209 if(!spacechar(c))
2210 break;
2212 if(i > 0){
2213 memmove(&(*linebuf)[0], &(*linebuf)[i], j -= i);
2214 (*linebuf)[n = (int)j] = '\0';
2217 #endif /* 0 (notyet - must take care for reverse solidus escaped space) */
2219 if(n_poption & n_PO_D_VV)
2220 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
2221 jleave:
2222 if (n_pstate & n_PS_PSTATE_PENDMASK)
2223 a_go_update_pstate();
2225 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
2226 * TODO but only established from the outside and we need to drop this
2227 * TODO overlay context somehow */
2228 if(n < 0 && (a_go_ctx->gc_flags & a_GO_SPLICE))
2229 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
2231 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
2232 rele_all_sigs();
2233 NYD2_LEAVE;
2234 return n;
2237 FL char *
2238 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
2239 char const *string){
2240 struct n_sigman sm;
2241 size_t linesize;
2242 char *linebuf, * volatile rv;
2243 int n;
2244 NYD2_ENTER;
2246 linesize = 0;
2247 linebuf = NULL;
2248 rv = NULL;
2250 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
2251 case 0:
2252 break;
2253 default:
2254 goto jleave;
2257 n = n_go_input(gif, prompt, &linebuf, &linesize, string);
2258 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
2259 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE))
2260 n_tty_addhist(rv, ((gif & n_GO_INPUT_HIST_GABBY) != 0));
2262 n_sigman_cleanup_ping(&sm);
2263 jleave:
2264 if(linebuf != NULL)
2265 n_free(linebuf);
2266 NYD2_LEAVE;
2267 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
2268 return rv;
2271 FL bool_t
2272 n_go_load(char const *name){
2273 struct a_go_ctx *gcp;
2274 size_t i;
2275 FILE *fip;
2276 bool_t rv;
2277 NYD_ENTER;
2279 rv = TRU1;
2281 if(name == NULL || *name == '\0')
2282 goto jleave;
2283 else if((fip = Fopen(name, "r")) == NULL){
2284 if(n_poption & n_PO_D_V)
2285 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
2286 goto jleave;
2289 i = strlen(name) +1;
2290 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
2291 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2293 gcp->gc_file = fip;
2294 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
2295 memcpy(gcp->gc_name, name, i);
2297 if(n_poption & n_PO_D_V)
2298 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
2299 rv = a_go_load(gcp);
2300 jleave:
2301 NYD_LEAVE;
2302 return rv;
2305 FL bool_t
2306 n_go_Xargs(char const **lines, size_t cnt){
2307 static char const name[] = "-X";
2309 union{
2310 bool_t rv;
2311 ui64_t align;
2312 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
2313 } b;
2314 char const *srcp, *xsrcp;
2315 char *cp;
2316 size_t imax, i, len;
2317 struct a_go_ctx *gcp;
2318 NYD_ENTER;
2320 gcp = (void*)b.uf;
2321 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2323 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
2324 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
2325 memcpy(gcp->gc_name, name, sizeof name);
2327 /* The problem being that we want to support reverse solidus newline
2328 * escaping also within multiline -X, i.e., POSIX says:
2329 * An unquoted <backslash> at the end of a command line shall
2330 * be discarded and the next line shall continue the command
2331 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
2332 * entire lines array and set _MACRO_FREE_DATA */
2333 imax = cnt + 1;
2334 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
2336 /* For each of the input lines.. */
2337 for(i = len = 0, cp = NULL; cnt > 0;){
2338 bool_t keep;
2339 size_t j;
2341 if((j = strlen(srcp = *lines)) == 0){
2342 ++lines, --cnt;
2343 continue;
2346 /* Separate one line from a possible multiline input string */
2347 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
2348 *lines = &xsrcp[1];
2349 j = PTR2SIZE(xsrcp - srcp);
2350 }else
2351 ++lines, --cnt;
2353 /* The (separated) string may itself indicate soft newline escaping */
2354 if((keep = (srcp[j - 1] == '\\'))){
2355 size_t xj, xk;
2357 /* Need an uneven number of reverse solidus */
2358 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
2359 if(srcp[xj] != '\\')
2360 break;
2361 if(xk & 1)
2362 --j;
2363 else
2364 keep = FAL0;
2367 /* Strip any leading WS from follow lines, then */
2368 if(cp != NULL)
2369 while(j > 0 && spacechar(*srcp))
2370 ++srcp, --j;
2372 if(j > 0){
2373 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
2374 imax += 4;
2375 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
2376 imax);
2378 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
2379 memcpy(&cp[len], srcp, j);
2380 cp[len += j] = '\0';
2382 if(!keep)
2383 ++i;
2385 if(!keep)
2386 cp = NULL, len = 0;
2388 if(cp != NULL){
2389 assert(i + 1 < imax);
2390 gcp->gc_lines[i++] = cp;
2392 gcp->gc_lines[i] = NULL;
2394 b.rv = a_go_load(gcp);
2395 NYD_LEAVE;
2396 return b.rv;
2399 FL int
2400 c_source(void *v){
2401 int rv;
2402 NYD_ENTER;
2404 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
2405 NYD_LEAVE;
2406 return rv;
2409 FL int
2410 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
2411 int rv;
2412 NYD_ENTER;
2414 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
2415 NYD_LEAVE;
2416 return rv;
2419 FL bool_t
2420 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
2421 void (*on_finalize)(void*), void *finalize_arg){
2422 struct a_go_ctx *gcp;
2423 size_t i;
2424 int rv;
2425 sigset_t osigmask;
2426 NYD_ENTER;
2428 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2430 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2431 (i = strlen(name) +1));
2432 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2434 hold_all_sigs();
2436 gcp->gc_outer = a_go_ctx;
2437 gcp->gc_osigmask = osigmask;
2438 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
2439 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2440 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2441 gcp->gc_lines = lines;
2442 gcp->gc_on_finalize = on_finalize;
2443 gcp->gc_finalize_arg = finalize_arg;
2444 memcpy(gcp->gc_name, name, i);
2446 a_go_ctx = gcp;
2447 n_go_data = &gcp->gc_data;
2448 n_pstate |= n_PS_ROBOT;
2449 rv = a_go_event_loop(gcp, gif);
2451 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
2452 if(a_go_xcall != NULL){
2453 if(a_go_xcall == (struct a_go_xcall*)-1)
2454 a_go_xcall = NULL;
2455 else if(a_go_xcall->gx_upto == gcp){
2456 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
2457 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
2458 while(a_go_xcall != NULL){
2459 char *cp, **argv;
2460 struct a_go_xcall *gxp;
2462 hold_all_sigs();
2464 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
2465 gxp = a_go_xcall;
2466 a_go_xcall = NULL;
2468 /* Recreate the ARGV of this command on the LOFI memory of the
2469 * hosting a_go_ctx, so that it will become auto-reclaimed */
2470 /* C99 */{
2471 void *vp;
2473 vp = n_lofi_alloc(gxp->gx_buflen);
2474 cp = vp;
2475 argv = vp;
2477 cp += sizeof(*argv) * (gxp->gx_argc + 1);
2478 for(i = 0; i < gxp->gx_argc; ++i){
2479 argv[i] = cp;
2480 memcpy(cp, gxp->gx_argv[i].s, gxp->gx_argv[i].l +1);
2481 cp += gxp->gx_argv[i].l +1;
2483 argv[i] = NULL;
2484 n_free(gxp);
2486 rele_all_sigs();
2488 (void)c_call(argv);
2490 n_lofi_free(argv);
2492 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) != 0);
2493 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
2496 NYD_LEAVE;
2497 return rv;
2500 FL bool_t
2501 n_go_command(enum n_go_input_flags gif, char const *cmd){
2502 struct a_go_ctx *gcp;
2503 bool_t rv;
2504 size_t i, ial;
2505 sigset_t osigmask;
2506 NYD_ENTER;
2508 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2510 i = strlen(cmd) +1;
2511 ial = n_ALIGN(i);
2512 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2513 ial + 2*sizeof(char*));
2514 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2516 hold_all_sigs();
2518 gcp->gc_outer = a_go_ctx;
2519 gcp->gc_osigmask = osigmask;
2520 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
2521 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2522 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2523 gcp->gc_lines = (void*)&gcp->gc_name[ial];
2524 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
2525 gcp->gc_lines[1] = NULL;
2527 a_go_ctx = gcp;
2528 n_go_data = &gcp->gc_data;
2529 n_pstate |= n_PS_ROBOT;
2530 rv = a_go_event_loop(gcp, gif);
2531 NYD_LEAVE;
2532 return rv;
2535 FL void
2536 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
2537 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
2538 struct a_go_ctx *gcp;
2539 size_t i;
2540 sigset_t osigmask;
2541 NYD_ENTER;
2543 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2545 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2546 (i = strlen(cmd) +1));
2547 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2549 hold_all_sigs();
2551 gcp->gc_outer = a_go_ctx;
2552 gcp->gc_osigmask = osigmask;
2553 gcp->gc_file = new_stdin;
2554 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE;
2555 gcp->gc_on_finalize = on_finalize;
2556 gcp->gc_finalize_arg = finalize_arg;
2557 gcp->gc_splice_stdin = n_stdin;
2558 gcp->gc_splice_stdout = n_stdout;
2559 gcp->gc_splice_psonce = n_psonce;
2560 memcpy(gcp->gc_name, cmd, i);
2562 n_stdin = new_stdin;
2563 n_stdout = new_stdout;
2564 n_psonce = new_psonce;
2565 a_go_ctx = gcp;
2566 n_pstate |= n_PS_ROBOT;
2568 rele_all_sigs();
2569 NYD_LEAVE;
2572 FL void
2573 n_go_splice_hack_remove_after_jump(void){
2574 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2577 FL bool_t
2578 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2579 struct a_go_ctx *gcp;
2580 bool_t rv;
2581 NYD2_ENTER;
2583 rv = FAL0;
2585 /* Only when interactive and startup completed */
2586 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) !=
2587 (n_PSO_INTERACTIVE | n_PSO_STARTED))
2588 goto jleave;
2590 /* Not when running any hook */
2591 if(n_pstate & n_PS_HOOK_MASK)
2592 goto jleave;
2594 /* Traverse up the stack:
2595 * . not when controlled by a child process
2596 * TODO . not when there are pipes involved, we neither handle job control,
2597 * TODO nor process groups, that is, controlling terminal acceptably
2598 * . not when sourcing a file */
2599 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2600 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2601 goto jleave;
2604 rv = TRU1;
2605 jleave:
2606 NYD2_LEAVE;
2607 return rv;
2610 /* s-it-mode */