Add n_SHEXP_PARSE_META_KEEP (for `vput')
[s-mailx.git] / go.c
blob8f6401db0e9a0984d2b253fa43d4b387fdb6f14f
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; /* XXX don't change 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 if(line.l > 0){
895 size_t i;
897 i = line.l;
898 for(cp = &line.s[i -1]; spacechar(*cp); --cp)
899 --i;
900 line.l = i;
902 for(cp = line.s; spacechar(*cp); ++cp)
904 line.l -= PTR2SIZE(cp - line.s);
906 /* Ignore null commands (comments) */
907 if(*cp == '#')
908 goto jret0;
910 /* Handle ! differently to get the correct lexical conventions */
911 arglist[0] = cp;
912 if(*cp == '!')
913 ++cp;
914 /* Isolate the actual command; since it may not necessarily be
915 * separated from the arguments (as in `p1') we need to duplicate it to
916 * be able to create a NUL terminated version.
917 * We must be aware of several special one letter commands here */
918 else if((cp = a_go_isolate(cp)) == arglist[0] &&
919 (*cp == '|' || *cp == '~' || *cp == '?'))
920 ++cp;
921 c = (int)PTR2SIZE(cp - arglist[0]);
922 line.l -= c;
923 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
924 memcpy(word, arglist[0], c);
925 word[c] = '\0';
927 /* No-expansion modifier? */
928 if(!(flags & a_NOPREFIX) && *word == '\\'){
929 ++word;
930 --c;
931 flags |= a_NOALIAS;
934 /* It may be a modifier prefix */
935 if(c == sizeof("ignerr") -1 && !asccasecmp(word, "ignerr")){
936 flags |= a_NOPREFIX | a_IGNERR;
937 line.s = cp;
938 goto jrestart;
939 }else if(c == sizeof("wysh") -1 && !asccasecmp(word, "wysh")){
940 flags |= a_NOPREFIX | a_WYSH;
941 line.s = cp;
942 goto jrestart;
943 }else if(c == sizeof("vput") -1 && !asccasecmp(word, "vput")){
944 flags |= a_NOPREFIX | a_VPUT;
945 line.s = cp;
946 goto jrestart;
949 /* Look up the command; if not found, bitch.
950 * Normally, a blank command would map to the first command in the
951 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
952 * confusion; act just the same for aliases */
953 if(*word == '\0'){
954 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
955 gap != NULL)
956 goto jret0;
957 gcdp = &a_go_cmd_tab[0];
958 goto jexec;
961 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
962 ui8_t expcnt;
964 expcnt = (flags & a_ALIAS_MASK);
965 ++expcnt;
966 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
968 /* Avoid self-recursion; yes, the user could use \ no-expansion, but.. */
969 if(gap != NULL && !strcmp(word, gap->ga_name)){
970 if(n_poption & n_PO_D_V)
971 n_err(_("Actively avoiding self-recursion of `commandalias': %s\n"),
972 word);
973 }else for(gap = a_go_aliases; gap != NULL; gap = gap->ga_next)
974 if(!strcmp(word, gap->ga_name)){
975 if(line.l > 0){
976 size_t i;
978 i = gap->ga_cmd.l;
979 line.s = n_autorec_alloc(i + line.l +1);
980 memcpy(line.s, gap->ga_cmd.s, i);
981 memcpy(line.s + i, cp, line.l);
982 line.s[i += line.l] = '\0';
983 line.l = i;
984 }else{
985 line.s = gap->ga_cmd.s;
986 line.l = gap->ga_cmd.l;
988 goto jrestart;
992 if((gcdp = a_go__firstfit(word)) == NULL || gcdp->gcd_func == &c_cmdnotsupp){
993 bool_t s;
995 if(!(s = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
996 n_err(_("Unknown command%s: `%s'\n"),
997 (s ? _(" (ignored due to `if' condition)") : n_empty), word);
998 if(s)
999 goto jret0;
1000 if(gcdp != NULL){
1001 c_cmdnotsupp(NULL);
1002 gcdp = NULL;
1004 nerrn = n_ERR_NOSYS;
1005 goto jleave;
1008 /* See if we should execute the command -- if a conditional we always
1009 * execute it, otherwise, check the state of cond */
1010 jexec:
1011 if(!(gcdp->gcd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip())
1012 goto jret0;
1014 nerrn = n_ERR_INVAL;
1016 /* Process the arguments to the command, depending on the type it expects */
1017 if((gcdp->gcd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
1018 !(n_poption & n_PO_BATCH_FLAG)){
1019 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
1020 gcdp->gcd_name);
1021 goto jleave;
1023 if(!(gcdp->gcd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
1024 n_err(_("May not execute `%s' while sending\n"), gcdp->gcd_name);
1025 goto jleave;
1027 if(gcdp->gcd_caflags & n_CMD_ARG_R){
1028 if(n_pstate & n_PS_COMPOSE_MODE){
1029 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
1030 n_err(_("Cannot invoke `%s' when in compose mode\n"), gcdp->gcd_name);
1031 goto jleave;
1033 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
1034 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
1035 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING)){
1036 n_err(_("Cannot invoke `%s' from a macro or during file inclusion\n"),
1037 gcdp->gcd_name);
1038 goto jleave;
1041 if((gcdp->gcd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED)){
1042 n_err(_("May not execute `%s' during startup\n"), gcdp->gcd_name);
1043 goto jleave;
1045 if(!(gcdp->gcd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
1046 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
1047 gcdp->gcd_name);
1048 goto jleave;
1051 if((gcdp->gcd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
1052 n_err(_("Cannot execute `%s' without active mailbox\n"), gcdp->gcd_name);
1053 goto jleave;
1055 if((gcdp->gcd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
1056 n_err(_("May not execute `%s' -- message file is read only\n"),
1057 gcdp->gcd_name);
1058 goto jleave;
1061 if(gcdp->gcd_caflags & n_CMD_ARG_O)
1062 n_OBSOLETE2(_("this command will be removed"), gcdp->gcd_name);
1064 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
1065 * TODO doesn't use any of those list commands which strip this mask,
1066 * TODO and for now we misuse bits for checking relation to history;
1067 * TODO argument state should be property of a per-command carrier instead */
1068 n_pstate &= ~n_PS_ARGLIST_MASK;
1070 if((flags & a_WYSH) &&
1071 (gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
1072 n_err(_("`wysh' prefix does not affect `%s'\n"), gcdp->gcd_name);
1073 flags &= ~a_WYSH;
1076 if(flags & a_VPUT){
1077 if(gcdp->gcd_caflags & n_CMD_ARG_V){
1078 char const *xcp;
1080 xcp = cp;
1081 arglist[0] = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIMSPACE |
1082 n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_META_KEEP), &xcp);
1083 line.l -= PTR2SIZE(xcp - cp);
1084 cp = n_UNCONST(xcp);
1085 if(cp == NULL)
1086 xcp = N_("could not parse input token");
1087 else if(!n_shexp_is_valid_varname(arglist[0]))
1088 xcp = N_("not a valid variable name");
1089 else if(!n_var_is_user_writable(arglist[0]))
1090 xcp = N_("either not a user writable, or a boolean variable");
1091 else
1092 xcp = NULL;
1093 if(xcp != NULL){
1094 n_err("`%s': vput: %s: %s\n",
1095 gcdp->gcd_name, V_(xcp), n_shexp_quote_cp(arglist[0], FAL0));
1096 nerrn = n_ERR_NOTSUP;
1097 goto jleave;
1099 ++arglist;
1100 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
1101 * TODO on in getrawlist() etc., i.e., the argument vector producers,
1102 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
1103 }else{
1104 n_err(_("`vput' prefix does not affect `%s'\n"), gcdp->gcd_name);
1105 flags &= ~a_VPUT;
1109 switch(gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK){
1110 case n_CMD_ARG_TYPE_MSGLIST:
1111 /* Message list defaulting to nearest forward legal message */
1112 if(n_msgvec == NULL)
1113 goto jemsglist;
1114 if((c = getmsglist(cp, n_msgvec, gcdp->gcd_msgflag)) < 0){
1115 nerrn = n_ERR_NOMSG;
1116 flags |= a_NO_ERRNO;
1117 break;
1119 if(c == 0){
1120 if((n_msgvec[0] = first(gcdp->gcd_msgflag, gcdp->gcd_msgmask)) != 0)
1121 n_msgvec[1] = 0;
1123 if(n_msgvec[0] == 0){
1124 jemsglist:
1125 if(!(n_pstate & n_PS_HOOK_MASK))
1126 fprintf(n_stdout, _("No applicable messages\n"));
1127 nerrn = n_ERR_NOMSG;
1128 flags |= a_NO_ERRNO;
1129 break;
1131 rv = (*gcdp->gcd_func)(n_msgvec);
1132 break;
1134 case n_CMD_ARG_TYPE_NDMLIST:
1135 /* Message list with no defaults, but no error if none exist */
1136 if(n_msgvec == NULL)
1137 goto jemsglist;
1138 if((c = getmsglist(cp, n_msgvec, gcdp->gcd_msgflag)) < 0){
1139 nerrn = n_ERR_NOMSG;
1140 flags |= a_NO_ERRNO;
1141 break;
1143 rv = (*gcdp->gcd_func)(n_msgvec);
1144 break;
1146 case n_CMD_ARG_TYPE_STRING:
1147 /* Just the straight string, old style, with leading blanks removed */
1148 while(spacechar(*cp))
1149 ++cp;
1150 rv = (*gcdp->gcd_func)(cp);
1151 break;
1152 case n_CMD_ARG_TYPE_RAWDAT:
1153 /* Just the straight string, leading blanks removed, placed in argv[] */
1154 while(spacechar(*cp))
1155 ++cp;
1156 *arglist++ = cp;
1157 *arglist = NULL;
1158 rv = (*gcdp->gcd_func)(arglist_base);
1159 break;
1161 case n_CMD_ARG_TYPE_WYSH:
1162 c = 1;
1163 if(0){
1164 /* FALLTHRU */
1165 case n_CMD_ARG_TYPE_WYRA:
1166 c = (flags & a_WYSH) ? 1 : 0;
1167 if(0){
1168 case n_CMD_ARG_TYPE_RAWLIST:
1169 c = 0;
1172 if((c = getrawlist((c != 0), arglist,
1173 n_MAXARGC - PTR2SIZE(arglist - arglist_base),
1174 cp, line.l)) < 0){
1175 n_err(_("Invalid argument list\n"));
1176 flags |= a_NO_ERRNO;
1177 break;
1180 if(c < gcdp->gcd_minargs){
1181 n_err(_("`%s' requires at least %u arg(s)\n"),
1182 gcdp->gcd_name, (ui32_t)gcdp->gcd_minargs);
1183 flags |= a_NO_ERRNO;
1184 break;
1186 #undef gcd_minargs
1187 if(c > gcdp->gcd_maxargs){
1188 n_err(_("`%s' takes no more than %u arg(s)\n"),
1189 gcdp->gcd_name, (ui32_t)gcdp->gcd_maxargs);
1190 flags |= a_NO_ERRNO;
1191 break;
1193 #undef gcd_maxargs
1195 if(flags & a_VPUT)
1196 n_pstate |= n_PS_ARGMOD_VPUT;
1198 rv = (*gcdp->gcd_func)(arglist_base);
1199 if(a_go_xcall != NULL)
1200 goto jret0;
1201 break;
1203 default:
1204 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
1205 gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK); )
1206 nerrn = n_ERR_NOTOBACCO;
1207 nexn = 1;
1208 goto jret0;
1211 if(!(gcdp->gcd_caflags & n_CMD_ARG_H))
1212 gecp->gec_add_history = (((gcdp->gcd_caflags & n_CMD_ARG_G) ||
1213 (n_pstate & n_PS_MSGLIST_GABBY)) ? TRUM1 : TRU1);
1215 if(rv != 0){
1216 if(!(flags & a_NO_ERRNO)){
1217 if(gcdp->gcd_caflags & n_CMD_ARG_EM)
1218 flags |= a_NO_ERRNO;
1219 else if((nerrn = n_err_no) == 0)
1220 nerrn = n_ERR_INVAL;
1221 }else
1222 flags ^= a_NO_ERRNO;
1223 }else if(gcdp->gcd_caflags & n_CMD_ARG_EM)
1224 flags |= a_NO_ERRNO;
1225 else
1226 nerrn = n_ERR_NONE;
1227 jleave:
1228 nexn = rv;
1230 if(flags & a_IGNERR){
1231 n_pstate &= ~n_PS_ERR_EXIT_MASK;
1232 n_exit_status = n_EXIT_OK;
1233 }else if(rv != 0){
1234 bool_t bo;
1236 if((bo = ok_blook(batch_exit_on_error))){
1237 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
1238 if(!(n_poption & n_PO_BATCH_FLAG))
1239 bo = FAL0;
1241 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
1242 n_pstate |= n_PS_ERR_QUIT;
1243 else if(!(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) &&
1244 ok_blook(posix))
1245 n_pstate |= n_PS_ERR_XIT;
1246 else
1247 rv = 0;
1249 if(rv != 0){
1250 if(n_exit_status == n_EXIT_OK)
1251 n_exit_status = n_EXIT_ERR;
1252 if((n_poption & n_PO_D_V) &&
1253 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
1254 n_alert(_("Non-interactive, bailing out due to errors "
1255 "in startup load phase"));
1256 goto jret;
1260 if(gcdp == NULL)
1261 goto jret0;
1262 if((gcdp->gcd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
1263 if(visible(dot))
1264 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
1265 sizeof("\\type") -1);
1267 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
1268 !(gcdp->gcd_caflags & n_CMD_ARG_T))
1269 n_pstate |= n_PS_SAW_COMMAND;
1270 jret0:
1271 rv = 0;
1272 jret:
1273 if(!(flags & a_NO_ERRNO))
1274 n_pstate_err_no = nerrn;
1275 n_pstate_ex_no = nexn;
1276 NYD_LEAVE;
1277 return (rv == 0);
1280 static struct a_go_cmd_desc const *
1281 a_go__firstfit(char const *comm){ /* TODO *hashtable*! linear list search!!! */
1282 struct a_go_cmd_desc const *gcdp;
1283 NYD2_ENTER;
1285 for(gcdp = a_go_cmd_tab; gcdp < &a_go_cmd_tab[n_NELEM(a_go_cmd_tab)]; ++gcdp)
1286 if(*comm == *gcdp->gcd_name && is_prefix(comm, gcdp->gcd_name))
1287 goto jleave;
1288 gcdp = NULL;
1289 jleave:
1290 NYD2_LEAVE;
1291 return gcdp;
1294 static void
1295 a_go_hangup(int s){
1296 NYD_X; /* Signal handler */
1297 n_UNUSED(s);
1298 /* nothing to do? */
1299 exit(n_EXIT_ERR);
1302 static void
1303 a_go_onintr(int s){ /* TODO block signals while acting */
1304 NYD_X; /* Signal handler */
1305 n_UNUSED(s);
1307 safe_signal(SIGINT, a_go_onintr);
1309 termios_state_reset();
1311 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
1313 if(interrupts != 1)
1314 n_err_sighdl(_("Interrupt\n"));
1315 safe_signal(SIGPIPE, a_go_oldpipe);
1316 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
1319 static void
1320 a_go_cleanup(enum a_go_cleanup_mode gcm){
1321 /* Signals blocked */
1322 struct a_go_ctx *gcp;
1323 NYD_ENTER;
1325 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1326 hold_all_sigs();
1327 jrestart:
1328 gcp = a_go_ctx;
1330 /* Free input injections of this level first */
1331 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
1332 struct a_go_input_inject **giipp, *giip;
1334 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
1335 *giipp = giip->gii_next;
1336 n_free(giip);
1340 /* Cleanup non-crucial external stuff */
1341 n_COLOUR(
1342 if(gcp->gc_data.gdc_colour != NULL)
1343 n_colour_stack_del(NULL);
1346 /* Work the actual context (according to cleanup mode) */
1347 if(gcp->gc_outer == NULL){
1348 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
1349 a_go_xcall = NULL;
1350 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1351 n_pstate &= ~n_PS_ERR_EXIT_MASK;
1352 close_all_files();
1353 }else{
1354 if(!(n_pstate & n_PS_SOURCING))
1355 close_all_files();
1358 n_memory_reset();
1360 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1361 assert(a_go_xcall == NULL);
1362 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
1363 assert(gcp->gc_on_finalize == NULL);
1364 assert(gcp->gc_data.gdc_colour == NULL);
1365 goto jxleave;
1366 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
1367 n_memory_reset();
1368 goto jxleave;
1369 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
1370 n_stdin = gcp->gc_splice_stdin;
1371 n_stdout = gcp->gc_splice_stdout;
1372 n_psonce = gcp->gc_splice_psonce;
1373 goto jstackpop;
1376 /* Cleanup crucial external stuff */
1377 if(gcp->gc_data.gdc_ifcond != NULL){
1378 n_cnd_if_stack_del(gcp->gc_data.gdc_ifcond);
1379 if(!(gcp->gc_flags & a_GO_FORCE_EOF) &&
1380 !(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
1381 a_go_xcall == NULL)
1382 n_err(_("Unmatched `if' at end of %s %s\n"),
1383 ((gcp->gc_flags & a_GO_MACRO
1384 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
1385 : _("`source'd file"))),
1386 gcp->gc_name);
1387 gcm |= a_GO_CLEANUP_ERROR;
1390 /* Teardown context */
1391 if(gcp->gc_flags & a_GO_MACRO){
1392 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
1393 char **lp;
1395 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
1396 n_free(*lp);
1397 ++gcp->gc_loff;
1399 /* Part of gcp's memory chunk, then */
1400 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
1401 n_free(gcp->gc_lines);
1403 }else if(gcp->gc_flags & a_GO_PIPE)
1404 /* XXX command manager should -TERM then -KILL instead of hoping
1405 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
1406 Pclose(gcp->gc_file, TRU1);
1407 else if(gcp->gc_flags & a_GO_FILE)
1408 Fclose(gcp->gc_file);
1410 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
1411 if(gcp->gc_data.gdc_mempool != NULL)
1412 n_memory_pool_pop(NULL);
1413 }else
1414 n_memory_reset();
1416 jstackpop:
1417 n_go_data = &(a_go_ctx = gcp->gc_outer)->gc_data;
1418 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
1419 (a_GO_MACRO | a_GO_SUPER_MACRO)){
1420 n_pstate &= ~n_PS_SOURCING;
1421 assert(n_pstate & n_PS_ROBOT);
1422 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
1423 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1424 else
1425 assert(n_pstate & n_PS_ROBOT);
1427 if(gcp->gc_on_finalize != NULL)
1428 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
1430 if(gcm & a_GO_CLEANUP_ERROR){
1431 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
1432 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
1433 goto jerr;
1435 jleave:
1436 if(gcp->gc_flags & a_GO_FREE)
1437 n_free(gcp);
1439 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
1440 goto jrestart;
1442 jxleave:
1443 NYD_LEAVE;
1444 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1445 rele_all_sigs();
1446 return;
1448 jerr:
1449 /* With *posix* we follow what POSIX says:
1450 * Any errors in the start-up file shall either cause mailx to
1451 * terminate with a diagnostic message and a non-zero status or to
1452 * continue after writing a diagnostic message, ignoring the
1453 * remainder of the lines in the start-up file
1454 * Print the diagnostic only for the outermost resource unless the user
1455 * is debugging or in verbose mode */
1456 if((n_poption & n_PO_D_V) ||
1457 (!(n_psonce & n_PSO_STARTED) &&
1458 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1459 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
1460 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1461 n_alert(_("Stopped %s %s due to errors%s"),
1462 (n_psonce & n_PSO_STARTED
1463 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1464 : (gcp->gc_flags & a_GO_MACRO
1465 ? (gcp->gc_flags & a_GO_MACRO_CMD
1466 ? _("evaluating command") : _("evaluating macro"))
1467 : (gcp->gc_flags & a_GO_PIPE
1468 ? _("executing `source'd pipe")
1469 : (gcp->gc_flags & a_GO_FILE
1470 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1472 : (gcp->gc_flags & a_GO_MACRO
1473 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
1474 ? _("evaluating command line") : _("evaluating macro"))
1475 : _("loading initialization resource"))),
1476 gcp->gc_name,
1477 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
1478 goto jleave;
1481 static bool_t
1482 a_go_file(char const *file, bool_t silent_open_error){
1483 struct a_go_ctx *gcp;
1484 sigset_t osigmask;
1485 size_t nlen;
1486 char *nbuf;
1487 bool_t ispipe;
1488 FILE *fip;
1489 NYD_ENTER;
1491 fip = NULL;
1493 /* Being a command argument file is space-trimmed *//* TODO v15 with
1494 * TODO WYRALIST this is no longer necessary true, and for that we
1495 * TODO don't set _PARSE_TRIMSPACE because we cannot! -> cmd-tab.h!! */
1496 #if 0
1497 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
1498 file[--nlen] == '|')))
1499 #else
1500 ispipe = FAL0;
1501 if(!silent_open_error){
1502 for(nlen = strlen(file); nlen > 0;){
1503 char c;
1505 c = file[--nlen];
1506 if(!spacechar(c)){
1507 if(c == '|'){
1508 nbuf = savestrbuf(file, nlen);
1509 ispipe = TRU1;
1511 break;
1515 #endif
1517 if(ispipe){
1518 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
1519 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
1520 goto jeopencheck;
1521 }else if((nbuf = fexpand(file, FEXP_LOCAL)) == NULL)
1522 goto jeopencheck;
1523 else if((fip = Fopen(nbuf, "r")) == NULL){
1524 jeopencheck:
1525 if(!silent_open_error || (n_poption & n_PO_D_V))
1526 n_perr(nbuf, 0);
1527 if(silent_open_error)
1528 fip = (FILE*)-1;
1529 goto jleave;
1532 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1534 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1535 (nlen = strlen(nbuf) +1));
1536 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1538 hold_all_sigs();
1540 gcp->gc_outer = a_go_ctx;
1541 gcp->gc_osigmask = osigmask;
1542 gcp->gc_file = fip;
1543 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1544 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1545 memcpy(gcp->gc_name, nbuf, nlen);
1547 a_go_ctx = gcp;
1548 n_go_data = &gcp->gc_data;
1549 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1550 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1551 fip = NULL;
1552 jleave:
1553 NYD_LEAVE;
1554 return (fip != NULL);
1557 static bool_t
1558 a_go_load(struct a_go_ctx *gcp){
1559 NYD2_ENTER;
1561 assert(!(n_psonce & n_PSO_STARTED));
1562 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1564 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1565 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1567 hold_all_sigs();
1569 /* POSIX:
1570 * Any errors in the start-up file shall either cause mailx to terminate
1571 * with a diagnostic message and a non-zero status or to continue after
1572 * writing a diagnostic message, ignoring the remainder of the lines in
1573 * the start-up file. */
1574 gcp->gc_outer = a_go_ctx;
1575 a_go_ctx = gcp;
1576 n_go_data = &gcp->gc_data;
1577 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1578 n_pstate |= n_PS_ROBOT |
1579 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1581 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1583 rele_all_sigs();
1585 n_go_main_loop();
1586 NYD2_LEAVE;
1587 return (((n_psonce & n_PSO_EXIT_MASK) |
1588 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1591 static void
1592 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1593 NYD_X; /* Signal handler */
1594 n_UNUSED(sig);
1595 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1598 static bool_t
1599 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1600 sighandler_type soldhdl;
1601 struct a_go_eval_ctx gec;
1602 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1603 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1604 sigset_t osigmask;
1605 NYD2_ENTER;
1607 memset(&gec, 0, sizeof gec);
1608 osigmask = gcp->gc_osigmask;
1609 hadint = FAL0;
1610 f = a_RETOK;
1612 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1613 safe_signal(SIGINT, &a_go__eloopint);
1614 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1615 hold_all_sigs();
1616 hadint = TRU1;
1617 f &= ~a_RETOK;
1618 a_go_xcall = NULL;
1619 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1620 goto jjump;
1624 for(;; f |= a_TICKED){
1625 int n;
1627 if(f & a_TICKED)
1628 n_memory_reset();
1630 /* Read a line of commands and handle end of file specially */
1631 gec.gec_line.l = gec.gec_line_size;
1632 rele_all_sigs();
1633 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL);
1634 hold_all_sigs();
1635 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1636 gec.gec_line.l = (ui32_t)n;
1638 if(n < 0)
1639 break;
1641 rele_all_sigs();
1642 if(!a_go_evaluate(&gec))
1643 f &= ~a_RETOK;
1644 hold_all_sigs();
1646 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1647 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1648 break;
1651 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1652 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1653 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1654 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1656 if(gec.gec_line.s != NULL)
1657 n_free(gec.gec_line.s);
1659 if(soldhdl != SIG_IGN)
1660 safe_signal(SIGINT, soldhdl);
1661 NYD2_LEAVE;
1662 rele_all_sigs();
1663 if(hadint){
1664 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1665 n_raise(SIGINT);
1667 return (f & a_RETOK);
1670 static int
1671 a_go_c_read(void *v){ /* TODO IFS? how? -r */
1672 struct n_sigman sm;
1673 char const **argv, *cp, *cp2;
1674 char *linebuf;
1675 size_t linesize;
1676 int rv;
1677 NYD2_ENTER;
1679 rv = 0;
1680 linesize = 0;
1681 linebuf = NULL;
1682 argv = v;
1684 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1685 case 0:
1686 break;
1687 default:
1688 n_pstate_err_no = n_ERR_INTR;
1689 rv = 1;
1690 goto jleave;
1692 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
1693 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
1694 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
1695 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
1696 NULL, &linebuf, &linesize, NULL);
1697 n_pstate_err_no = n_ERR_NONE; /* TODO I/O error if rv<0! */
1698 if(rv < 0)
1699 goto jleave;
1701 if(rv > 0){
1702 cp = linebuf;
1704 for(rv = 0; *argv != NULL; ++argv){
1705 char c;
1707 while(spacechar(*cp))
1708 ++cp;
1709 if(*cp == '\0')
1710 break;
1712 /* The last variable gets the remaining line less trailing IFS */
1713 if(argv[1] == NULL){
1714 for(cp2 = cp; *cp2 != '\0'; ++cp2)
1716 for(; cp2 > cp; --cp2){
1717 c = cp2[-1];
1718 if(!spacechar(c))
1719 break;
1721 }else
1722 for(cp2 = cp; (c = *++cp2) != '\0';)
1723 if(spacechar(c))
1724 break;
1726 /* C99 xxx This is a CC warning workaround (-Wbad-function-cast) */{
1727 char *vcp;
1729 vcp = savestrbuf(cp, PTR2SIZE(cp2 - cp));
1730 if(!a_go__read_set(*argv, vcp)){
1731 n_pstate_err_no = n_ERR_NOTSUP;
1732 rv = 1;
1733 break;
1737 cp = cp2;
1741 /* Set the remains to the empty string */
1742 for(; *argv != NULL; ++argv)
1743 if(!a_go__read_set(*argv, n_empty)){
1744 n_pstate_err_no = n_ERR_NOTSUP;
1745 rv = 1;
1746 break;
1749 n_sigman_cleanup_ping(&sm);
1750 jleave:
1751 if(linebuf != NULL)
1752 n_free(linebuf);
1753 NYD2_LEAVE;
1754 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1755 return rv;
1758 static bool_t
1759 a_go__read_set(char const *cp, char const *value){
1760 bool_t rv;
1761 NYD2_ENTER;
1763 if(!n_shexp_is_valid_varname(cp))
1764 value = N_("not a valid variable name");
1765 else if(!n_var_is_user_writable(cp))
1766 value = N_("variable is read-only");
1767 else if(!n_var_vset(cp, (uintptr_t)value))
1768 value = N_("failed to update variable value");
1769 else{
1770 rv = TRU1;
1771 goto jleave;
1773 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
1774 rv = FAL0;
1775 jleave:
1776 NYD2_LEAVE;
1777 return rv;
1780 FL int
1781 c_cmdnotsupp(void *vp){
1782 NYD_ENTER;
1783 n_UNUSED(vp);
1784 n_err(_("The requested feature is not compiled in\n"));
1785 NYD_LEAVE;
1786 return 1;
1789 FL void
1790 n_go_init(void){
1791 struct a_go_ctx *gcp;
1792 NYD2_ENTER;
1794 assert(n_stdin != NULL);
1796 gcp = (void*)a_go__mainctx_b.uf;
1797 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1798 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1799 gcp->gc_file = n_stdin;
1800 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1801 a_go_ctx = gcp;
1802 n_go_data = &gcp->gc_data;
1804 n_child_manager_start();
1805 NYD2_LEAVE;
1808 FL bool_t
1809 n_go_main_loop(void){ /* FIXME */
1810 struct a_go_eval_ctx gec;
1811 int n, eofcnt;
1812 bool_t volatile rv;
1813 NYD_ENTER;
1815 rv = TRU1;
1817 if (!(n_pstate & n_PS_SOURCING)) {
1818 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1819 safe_signal(SIGINT, &a_go_onintr);
1820 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1821 safe_signal(SIGHUP, &a_go_hangup);
1823 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1824 safe_signal(SIGPIPE, a_go_oldpipe);
1826 memset(&gec, 0, sizeof gec);
1828 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1829 hold_all_sigs();
1831 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1832 interrupts = 0;
1834 if(gec.gec_ever_seen)
1835 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1837 if (!(n_pstate & n_PS_SOURCING)) {
1838 char *cp;
1840 /* TODO Note: this buffer may contain a password. We should redefine
1841 * TODO the code flow which has to do that */
1842 if ((cp = termios_state.ts_linebuf) != NULL) {
1843 termios_state.ts_linebuf = NULL;
1844 termios_state.ts_linesize = 0;
1845 n_free(cp); /* TODO pool give-back */
1847 if (gec.gec_line.l > LINESIZE * 3) {
1848 n_free(gec.gec_line.s);
1849 gec.gec_line.s = NULL;
1850 gec.gec_line.l = gec.gec_line_size = 0;
1854 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1855 char *cp;
1857 if ((cp = ok_vlook(newmail)) != NULL) {
1858 struct stat st;
1860 /* FIXME TEST WITH NOPOLL ETC. !!! */
1861 n = (cp != NULL && strcmp(cp, "nopoll"));
1862 if ((mb.mb_type == MB_FILE && !stat(mailname, &st) &&
1863 st.st_size > mailsize) ||
1864 (mb.mb_type == MB_MAILDIR && n != 0)) {
1865 size_t odot = PTR2SIZE(dot - message);
1866 ui32_t odid = (n_pstate & n_PS_DID_PRINT_DOT);
1867 int i;
1869 rele_all_sigs();
1870 i = setfile(mailname,
1871 FEDIT_NEWMAIL |
1872 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY));
1873 hold_all_sigs();
1874 if(i < 0) {
1875 n_exit_status |= n_EXIT_ERR;
1876 rv = FAL0;
1877 break;
1879 dot = &message[odot];
1880 n_pstate |= odid;
1884 n_exit_status = n_EXIT_OK;
1887 /* Read a line of commands and handle end of file specially */
1888 gec.gec_line.l = gec.gec_line_size;
1889 rele_all_sigs();
1890 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1891 &gec.gec_line.s, &gec.gec_line.l, NULL);
1892 hold_all_sigs();
1893 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1894 gec.gec_line.l = (ui32_t)n;
1896 if (n < 0) {
1897 if (!(n_pstate & n_PS_ROBOT) &&
1898 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1899 ++eofcnt < 4) {
1900 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1901 n_go_input_clearerr();
1902 continue;
1904 break;
1907 n_pstate &= ~n_PS_HOOK_MASK;
1908 rele_all_sigs();
1909 rv = a_go_evaluate(&gec);
1910 hold_all_sigs();
1912 if(!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE) &&
1913 gec.gec_add_history)
1914 n_tty_addhist(gec.gec_line.s, (gec.gec_add_history != TRU1));
1916 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1917 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1918 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1919 default: break;
1921 if(n_psonce & n_PSO_EXIT_MASK)
1922 break;
1924 if(!rv)
1925 break;
1928 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1929 (rv ? 0 : a_GO_CLEANUP_ERROR));
1931 if (gec.gec_line.s != NULL)
1932 n_free(gec.gec_line.s);
1934 rele_all_sigs();
1935 NYD_LEAVE;
1936 return rv;
1939 FL void
1940 n_go_input_clearerr(void){
1941 FILE *fp;
1942 NYD2_ENTER;
1944 fp = NULL;
1946 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1947 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1948 fp = a_go_ctx->gc_file;
1950 if(fp != NULL)
1951 clearerr(fp);
1952 NYD2_LEAVE;
1955 FL void
1956 n_go_input_force_eof(void){
1957 NYD_ENTER;
1958 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1959 NYD_LEAVE;
1962 FL void
1963 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1964 size_t len){
1965 NYD_ENTER;
1966 if(len == UIZ_MAX)
1967 len = strlen(buf);
1969 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1970 len > 0){
1971 size_t i;
1972 struct a_go_input_inject *giip, **giipp;
1974 hold_all_sigs();
1976 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1977 ) + 1 + len +1);
1978 giipp = &a_go_ctx->gc_inject;
1979 giip->gii_next = *giipp;
1980 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1981 if(buf[i = 0] != ' ' && !(giif & n_GO_INPUT_INJECT_HISTORY))
1982 giip->gii_dat[i++] = ' '; /* TODO prim. hack to avoid history put! */
1983 memcpy(&giip->gii_dat[i], buf, len);
1984 i += len;
1985 giip->gii_dat[giip->gii_len = i] = '\0';
1986 *giipp = giip;
1988 rele_all_sigs();
1990 NYD_LEAVE;
1993 FL int
1994 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1995 size_t *linesize, char const *string n_MEMORY_DEBUG_ARGS){
1996 /* TODO readline: linebuf pool!; n_go_input should return si64_t */
1997 struct n_string xprompt;
1998 FILE *ifile;
1999 bool_t doprompt, dotty;
2000 char const *iftype;
2001 int nold, n;
2002 NYD2_ENTER;
2004 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
2005 hold_all_sigs();
2007 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
2008 n = -1;
2009 goto jleave;
2012 if(gif & n_GO_INPUT_FORCE_STDIN)
2013 goto jforce_stdin;
2015 /* Special case macro mode: never need to prompt, lines have always been
2016 * unfolded already */
2017 if(a_go_ctx->gc_flags & a_GO_MACRO){
2018 struct a_go_input_inject *giip;
2020 if(*linebuf != NULL)
2021 n_free(*linebuf);
2023 /* Injection in progress? Don't care about the autocommit state here */
2024 if((giip = a_go_ctx->gc_inject) != NULL){
2025 a_go_ctx->gc_inject = giip->gii_next;
2027 *linesize = giip->gii_len;
2028 *linebuf = (char*)giip;
2029 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
2030 iftype = "INJECTION";
2031 }else{
2032 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
2033 *linesize = 0;
2034 n = -1;
2035 goto jleave;
2038 ++a_go_ctx->gc_loff;
2039 *linesize = strlen(*linebuf);
2040 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
2041 *linebuf = sbufdup(*linebuf, *linesize);
2043 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
2044 ? "-X OPTION"
2045 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
2047 n = (int)*linesize;
2048 n_pstate |= n_PS_READLINE_NL;
2049 goto jhave_dat;
2050 }else{
2051 /* Injection in progress? */
2052 struct a_go_input_inject **giipp, *giip;
2054 giipp = &a_go_ctx->gc_inject;
2056 if((giip = *giipp) != NULL){
2057 *giipp = giip->gii_next;
2059 if(giip->gii_commit){
2060 if(*linebuf != NULL)
2061 n_free(*linebuf);
2063 /* Simply reuse the buffer */
2064 n = (int)(*linesize = giip->gii_len);
2065 *linebuf = (char*)giip;
2066 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
2067 iftype = "INJECTION";
2068 n_pstate |= n_PS_READLINE_NL;
2069 goto jhave_dat;
2070 }else{
2071 string = savestrbuf(giip->gii_dat, giip->gii_len);
2072 n_free(giip);
2077 jforce_stdin:
2078 n_pstate &= ~n_PS_READLINE_NL;
2079 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
2080 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
2081 doprompt = ((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
2082 (n_PSO_INTERACTIVE | n_PSO_STARTED) && !(n_pstate & n_PS_ROBOT));
2083 dotty = (doprompt && !ok_blook(line_editor_disable));
2084 if(!doprompt)
2085 gif |= n_GO_INPUT_PROMPT_NONE;
2086 else{
2087 if(!dotty)
2088 n_string_creat_auto(&xprompt);
2089 if(prompt == NULL)
2090 gif |= n_GO_INPUT_PROMPT_EVAL;
2093 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
2094 if(!dotty && (gif & n_GO_INPUT_PROMPT_NONE))
2095 fflush(n_stdout);
2097 ifile = (gif & n_GO_INPUT_FORCE_STDIN) ? n_stdin : a_go_ctx->gc_file;
2098 if(ifile == NULL){
2099 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
2100 (a_go_ctx->gc_flags & a_GO_MACRO));
2101 ifile = n_stdin;
2104 for(nold = n = 0;;){
2105 if(dotty){
2106 assert(ifile == n_stdin);
2107 if(string != NULL && (n = (int)strlen(string)) > 0){
2108 if(*linesize > 0)
2109 *linesize += n +1;
2110 else
2111 *linesize = (size_t)n + LINESIZE +1;
2112 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
2113 memcpy(*linebuf, string, (size_t)n +1);
2115 string = NULL;
2117 rele_all_sigs();
2119 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n
2120 n_MEMORY_DEBUG_ARGSCALL);
2122 hold_all_sigs();
2123 }else{
2124 if(!(gif & n_GO_INPUT_PROMPT_NONE))
2125 n_tty_create_prompt(&xprompt, prompt, gif);
2127 rele_all_sigs();
2129 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
2130 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
2131 fflush(n_stdout);
2134 n = (readline_restart)(ifile, linebuf, linesize, n
2135 n_MEMORY_DEBUG_ARGSCALL);
2137 hold_all_sigs();
2139 if(n > 0 && nold > 0){
2140 char const *cp;
2141 int i;
2143 i = 0;
2144 cp = &(*linebuf)[nold];
2145 while(spacechar(*cp) && n - i >= nold)
2146 ++cp, ++i;
2147 if(i > 0){
2148 memmove(&(*linebuf)[nold], cp, n - nold - i);
2149 n -= i;
2150 (*linebuf)[n] = '\0';
2155 if(n <= 0)
2156 break;
2158 /* POSIX says:
2159 * An unquoted <backslash> at the end of a command line shall
2160 * be discarded and the next line shall continue the command */
2161 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\'){
2162 if(dotty)
2163 n_pstate |= n_PS_READLINE_NL;
2164 break;
2166 /* Definitely outside of quotes, thus the quoting rules are so that an
2167 * uneven number of successive reverse solidus at EOL is a continuation */
2168 if(n > 1){
2169 size_t i, j;
2171 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
2172 if((*linebuf)[i] != '\\')
2173 break;
2174 if(!(j & 1))
2175 break;
2177 (*linebuf)[nold = --n] = '\0';
2178 gif |= n_GO_INPUT_NL_FOLLOW;
2181 if(n < 0)
2182 goto jleave;
2183 (*linebuf)[*linesize = n] = '\0';
2185 jhave_dat:
2186 #if 0
2187 if(gif & n_GO_INPUT_DROP_TRAIL_SPC){
2188 char *cp, c;
2189 size_t i;
2191 for(cp = &(*linebuf)[i = (size_t)n];; --i){
2192 c = *--cp;
2193 if(!spacechar(c))
2194 break;
2196 (*linebuf)[n = (int)i] = '\0';
2199 if(gif & n_GO_INPUT_DROP_LEAD_SPC){
2200 char *cp, c;
2201 size_t j, i;
2203 for(cp = &(*linebuf)[0], j = (size_t)n, i = 0; i < j; ++i){
2204 c = *cp++;
2205 if(!spacechar(c))
2206 break;
2208 if(i > 0){
2209 memmove(&(*linebuf)[0], &(*linebuf)[i], j -= i);
2210 (*linebuf)[n = (int)j] = '\0';
2213 #endif /* 0 (notyet - must take care for reverse solidus escaped space) */
2215 if(n_poption & n_PO_D_VV)
2216 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
2217 jleave:
2218 if (n_pstate & n_PS_PSTATE_PENDMASK)
2219 a_go_update_pstate();
2221 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
2222 * TODO but only established from the outside and we need to drop this
2223 * TODO overlay context somehow */
2224 if(n < 0 && (a_go_ctx->gc_flags & a_GO_SPLICE))
2225 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
2227 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
2228 rele_all_sigs();
2229 NYD2_LEAVE;
2230 return n;
2233 FL char *
2234 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
2235 char const *string){
2236 struct n_sigman sm;
2237 size_t linesize;
2238 char *linebuf, * volatile rv;
2239 int n;
2240 NYD2_ENTER;
2242 linesize = 0;
2243 linebuf = NULL;
2244 rv = NULL;
2246 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
2247 case 0:
2248 break;
2249 default:
2250 goto jleave;
2253 n = n_go_input(gif, prompt, &linebuf, &linesize, string);
2254 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
2255 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE))
2256 n_tty_addhist(rv, ((gif & n_GO_INPUT_HIST_GABBY) != 0));
2258 n_sigman_cleanup_ping(&sm);
2259 jleave:
2260 if(linebuf != NULL)
2261 n_free(linebuf);
2262 NYD2_LEAVE;
2263 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
2264 return rv;
2267 FL bool_t
2268 n_go_load(char const *name){
2269 struct a_go_ctx *gcp;
2270 size_t i;
2271 FILE *fip;
2272 bool_t rv;
2273 NYD_ENTER;
2275 rv = TRU1;
2277 if(name == NULL || *name == '\0')
2278 goto jleave;
2279 else if((fip = Fopen(name, "r")) == NULL){
2280 if(n_poption & n_PO_D_V)
2281 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
2282 goto jleave;
2285 i = strlen(name) +1;
2286 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
2287 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2289 gcp->gc_file = fip;
2290 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
2291 memcpy(gcp->gc_name, name, i);
2293 if(n_poption & n_PO_D_V)
2294 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
2295 rv = a_go_load(gcp);
2296 jleave:
2297 NYD_LEAVE;
2298 return rv;
2301 FL bool_t
2302 n_go_Xargs(char const **lines, size_t cnt){
2303 static char const name[] = "-X";
2305 union{
2306 bool_t rv;
2307 ui64_t align;
2308 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
2309 } b;
2310 char const *srcp, *xsrcp;
2311 char *cp;
2312 size_t imax, i, len;
2313 struct a_go_ctx *gcp;
2314 NYD_ENTER;
2316 gcp = (void*)b.uf;
2317 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2319 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
2320 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
2321 memcpy(gcp->gc_name, name, sizeof name);
2323 /* The problem being that we want to support reverse solidus newline
2324 * escaping also within multiline -X, i.e., POSIX says:
2325 * An unquoted <backslash> at the end of a command line shall
2326 * be discarded and the next line shall continue the command
2327 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
2328 * entire lines array and set _MACRO_FREE_DATA */
2329 imax = cnt + 1;
2330 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
2332 /* For each of the input lines.. */
2333 for(i = len = 0, cp = NULL; cnt > 0;){
2334 bool_t keep;
2335 size_t j;
2337 if((j = strlen(srcp = *lines)) == 0){
2338 ++lines, --cnt;
2339 continue;
2342 /* Separate one line from a possible multiline input string */
2343 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
2344 *lines = &xsrcp[1];
2345 j = PTR2SIZE(xsrcp - srcp);
2346 }else
2347 ++lines, --cnt;
2349 /* The (separated) string may itself indicate soft newline escaping */
2350 if((keep = (srcp[j - 1] == '\\'))){
2351 size_t xj, xk;
2353 /* Need an uneven number of reverse solidus */
2354 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
2355 if(srcp[xj] != '\\')
2356 break;
2357 if(xk & 1)
2358 --j;
2359 else
2360 keep = FAL0;
2363 /* Strip any leading WS from follow lines, then */
2364 if(cp != NULL)
2365 while(j > 0 && spacechar(*srcp))
2366 ++srcp, --j;
2368 if(j > 0){
2369 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
2370 imax += 4;
2371 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
2372 imax);
2374 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
2375 memcpy(&cp[len], srcp, j);
2376 cp[len += j] = '\0';
2378 if(!keep)
2379 ++i;
2381 if(!keep)
2382 cp = NULL, len = 0;
2384 if(cp != NULL){
2385 assert(i + 1 < imax);
2386 gcp->gc_lines[i++] = cp;
2388 gcp->gc_lines[i] = NULL;
2390 b.rv = a_go_load(gcp);
2391 NYD_LEAVE;
2392 return b.rv;
2395 FL int
2396 c_source(void *v){
2397 int rv;
2398 NYD_ENTER;
2400 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
2401 NYD_LEAVE;
2402 return rv;
2405 FL int
2406 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
2407 int rv;
2408 NYD_ENTER;
2410 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
2411 NYD_LEAVE;
2412 return rv;
2415 FL bool_t
2416 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
2417 void (*on_finalize)(void*), void *finalize_arg){
2418 struct a_go_ctx *gcp;
2419 size_t i;
2420 int rv;
2421 sigset_t osigmask;
2422 NYD_ENTER;
2424 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2426 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2427 (i = strlen(name) +1));
2428 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2430 hold_all_sigs();
2432 gcp->gc_outer = a_go_ctx;
2433 gcp->gc_osigmask = osigmask;
2434 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
2435 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2436 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2437 gcp->gc_lines = lines;
2438 gcp->gc_on_finalize = on_finalize;
2439 gcp->gc_finalize_arg = finalize_arg;
2440 memcpy(gcp->gc_name, name, i);
2442 a_go_ctx = gcp;
2443 n_go_data = &gcp->gc_data;
2444 n_pstate |= n_PS_ROBOT;
2445 rv = a_go_event_loop(gcp, gif);
2447 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
2448 if(a_go_xcall != NULL){
2449 if(a_go_xcall == (struct a_go_xcall*)-1)
2450 a_go_xcall = NULL;
2451 else if(a_go_xcall->gx_upto == gcp){
2452 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
2453 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
2454 while(a_go_xcall != NULL){
2455 char *cp, **argv;
2456 struct a_go_xcall *gxp;
2458 hold_all_sigs();
2460 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
2461 gxp = a_go_xcall;
2462 a_go_xcall = NULL;
2464 /* Recreate the ARGV of this command on the LOFI memory of the
2465 * hosting a_go_ctx, so that it will become auto-reclaimed */
2466 /* C99 */{
2467 void *vp;
2469 vp = n_lofi_alloc(gxp->gx_buflen);
2470 cp = vp;
2471 argv = vp;
2473 cp += sizeof(*argv) * (gxp->gx_argc + 1);
2474 for(i = 0; i < gxp->gx_argc; ++i){
2475 argv[i] = cp;
2476 memcpy(cp, gxp->gx_argv[i].s, gxp->gx_argv[i].l +1);
2477 cp += gxp->gx_argv[i].l +1;
2479 argv[i] = NULL;
2480 n_free(gxp);
2482 rele_all_sigs();
2484 (void)c_call(argv);
2486 n_lofi_free(argv);
2488 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) != 0);
2489 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
2492 NYD_LEAVE;
2493 return rv;
2496 FL bool_t
2497 n_go_command(enum n_go_input_flags gif, char const *cmd){
2498 struct a_go_ctx *gcp;
2499 bool_t rv;
2500 size_t i, ial;
2501 sigset_t osigmask;
2502 NYD_ENTER;
2504 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2506 i = strlen(cmd) +1;
2507 ial = n_ALIGN(i);
2508 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2509 ial + 2*sizeof(char*));
2510 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2512 hold_all_sigs();
2514 gcp->gc_outer = a_go_ctx;
2515 gcp->gc_osigmask = osigmask;
2516 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
2517 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2518 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2519 gcp->gc_lines = (void*)&gcp->gc_name[ial];
2520 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
2521 gcp->gc_lines[1] = NULL;
2523 a_go_ctx = gcp;
2524 n_go_data = &gcp->gc_data;
2525 n_pstate |= n_PS_ROBOT;
2526 rv = a_go_event_loop(gcp, gif);
2527 NYD_LEAVE;
2528 return rv;
2531 FL void
2532 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
2533 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
2534 struct a_go_ctx *gcp;
2535 size_t i;
2536 sigset_t osigmask;
2537 NYD_ENTER;
2539 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2541 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2542 (i = strlen(cmd) +1));
2543 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2545 hold_all_sigs();
2547 gcp->gc_outer = a_go_ctx;
2548 gcp->gc_osigmask = osigmask;
2549 gcp->gc_file = new_stdin;
2550 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE;
2551 gcp->gc_on_finalize = on_finalize;
2552 gcp->gc_finalize_arg = finalize_arg;
2553 gcp->gc_splice_stdin = n_stdin;
2554 gcp->gc_splice_stdout = n_stdout;
2555 gcp->gc_splice_psonce = n_psonce;
2556 memcpy(gcp->gc_name, cmd, i);
2558 n_stdin = new_stdin;
2559 n_stdout = new_stdout;
2560 n_psonce = new_psonce;
2561 a_go_ctx = gcp;
2562 n_pstate |= n_PS_ROBOT;
2564 rele_all_sigs();
2565 NYD_LEAVE;
2568 FL void
2569 n_go_splice_hack_remove_after_jump(void){
2570 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2573 FL bool_t
2574 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2575 struct a_go_ctx *gcp;
2576 bool_t rv;
2577 NYD2_ENTER;
2579 rv = FAL0;
2581 /* Only when interactive and startup completed */
2582 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) !=
2583 (n_PSO_INTERACTIVE | n_PSO_STARTED))
2584 goto jleave;
2586 /* Not when running any hook */
2587 if(n_pstate & n_PS_HOOK_MASK)
2588 goto jleave;
2590 /* Traverse up the stack:
2591 * . not when controlled by a child process
2592 * TODO . not when there are pipes involved, we neither handle job control,
2593 * TODO nor process groups, that is, controlling terminal acceptably
2594 * . not when sourcing a file */
2595 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2596 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2597 goto jleave;
2600 rv = TRU1;
2601 jleave:
2602 NYD2_LEAVE;
2603 return rv;
2606 /* s-it-mode */