FIX off-buffer with only empty input (since 2016-09+!)
[s-mailx.git] / go.c
blob749f66ee864df553832c250472b1a08624d28bde
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 == '#')
915 goto jret0;
917 /* Handle ! differently to get the correct lexical conventions */
918 arglist[0] = cp;
919 if(*cp == '!')
920 ++cp;
921 /* Isolate the actual command; since it may not necessarily be
922 * separated from the arguments (as in `p1') we need to duplicate it to
923 * be able to create a NUL terminated version.
924 * We must be aware of several special one letter commands here */
925 else if((cp = a_go_isolate(cp)) == arglist[0] &&
926 (*cp == '|' || *cp == '~' || *cp == '?'))
927 ++cp;
928 c = (int)PTR2SIZE(cp - arglist[0]);
929 line.l -= c;
930 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
931 memcpy(word, arglist[0], c);
932 word[c] = '\0';
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 line.s = cp;
945 goto jrestart;
946 }else if(c == sizeof("wysh") -1 && !asccasecmp(word, "wysh")){
947 flags |= a_NOPREFIX | a_WYSH;
948 line.s = cp;
949 goto jrestart;
950 }else if(c == sizeof("vput") -1 && !asccasecmp(word, "vput")){
951 flags |= a_NOPREFIX | a_VPUT;
952 line.s = cp;
953 goto jrestart;
956 /* Look up the command; if not found, bitch.
957 * Normally, a blank command would map to the first command in the
958 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
959 * confusion; act just the same for aliases */
960 if(*word == '\0'){
961 jempty:
962 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
963 gap != NULL)
964 goto jret0;
965 gcdp = &a_go_cmd_tab[0];
966 goto jexec;
969 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
970 ui8_t expcnt;
972 expcnt = (flags & a_ALIAS_MASK);
973 ++expcnt;
974 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
976 /* Avoid self-recursion; yes, the user could use \ no-expansion, but.. */
977 if(gap != NULL && !strcmp(word, gap->ga_name)){
978 if(n_poption & n_PO_D_V)
979 n_err(_("Actively avoiding self-recursion of `commandalias': %s\n"),
980 word);
981 }else for(gap = a_go_aliases; gap != NULL; gap = gap->ga_next)
982 if(!strcmp(word, gap->ga_name)){
983 if(line.l > 0){
984 size_t i;
986 i = gap->ga_cmd.l;
987 line.s = n_autorec_alloc(i + line.l +1);
988 memcpy(line.s, gap->ga_cmd.s, i);
989 memcpy(line.s + i, cp, line.l);
990 line.s[i += line.l] = '\0';
991 line.l = i;
992 }else{
993 line.s = gap->ga_cmd.s;
994 line.l = gap->ga_cmd.l;
996 goto jrestart;
1000 if((gcdp = a_go__firstfit(word)) == NULL || gcdp->gcd_func == &c_cmdnotsupp){
1001 bool_t s;
1003 if(!(s = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
1004 n_err(_("Unknown command%s: `%s'\n"),
1005 (s ? _(" (ignored due to `if' condition)") : n_empty), word);
1006 if(s)
1007 goto jret0;
1008 if(gcdp != NULL){
1009 c_cmdnotsupp(NULL);
1010 gcdp = NULL;
1012 nerrn = n_ERR_NOSYS;
1013 goto jleave;
1016 /* See if we should execute the command -- if a conditional we always
1017 * execute it, otherwise, check the state of cond */
1018 jexec:
1019 if(!(gcdp->gcd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip())
1020 goto jret0;
1022 nerrn = n_ERR_INVAL;
1024 /* Process the arguments to the command, depending on the type it expects */
1025 if((gcdp->gcd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
1026 !(n_poption & n_PO_BATCH_FLAG)){
1027 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
1028 gcdp->gcd_name);
1029 goto jleave;
1031 if(!(gcdp->gcd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
1032 n_err(_("May not execute `%s' while sending\n"), gcdp->gcd_name);
1033 goto jleave;
1035 if(gcdp->gcd_caflags & n_CMD_ARG_R){
1036 if(n_pstate & n_PS_COMPOSE_MODE){
1037 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
1038 n_err(_("Cannot invoke `%s' when in compose mode\n"), gcdp->gcd_name);
1039 goto jleave;
1041 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
1042 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
1043 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING)){
1044 n_err(_("Cannot invoke `%s' from a macro or during file inclusion\n"),
1045 gcdp->gcd_name);
1046 goto jleave;
1049 if((gcdp->gcd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED)){
1050 n_err(_("May not execute `%s' during startup\n"), gcdp->gcd_name);
1051 goto jleave;
1053 if(!(gcdp->gcd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
1054 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
1055 gcdp->gcd_name);
1056 goto jleave;
1059 if((gcdp->gcd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
1060 n_err(_("Cannot execute `%s' without active mailbox\n"), gcdp->gcd_name);
1061 goto jleave;
1063 if((gcdp->gcd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
1064 n_err(_("May not execute `%s' -- message file is read only\n"),
1065 gcdp->gcd_name);
1066 goto jleave;
1069 if(gcdp->gcd_caflags & n_CMD_ARG_O)
1070 n_OBSOLETE2(_("this command will be removed"), gcdp->gcd_name);
1072 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
1073 * TODO doesn't use any of those list commands which strip this mask,
1074 * TODO and for now we misuse bits for checking relation to history;
1075 * TODO argument state should be property of a per-command carrier instead */
1076 n_pstate &= ~n_PS_ARGLIST_MASK;
1078 if((flags & a_WYSH) &&
1079 (gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
1080 n_err(_("`wysh' prefix does not affect `%s'\n"), gcdp->gcd_name);
1081 flags &= ~a_WYSH;
1084 if(flags & a_VPUT){
1085 if(gcdp->gcd_caflags & n_CMD_ARG_V){
1086 char const *xcp;
1088 xcp = cp;
1089 arglist[0] = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIMSPACE |
1090 n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_META_KEEP), &xcp);
1091 line.l -= PTR2SIZE(xcp - cp);
1092 cp = n_UNCONST(xcp);
1093 if(cp == NULL)
1094 xcp = N_("could not parse input token");
1095 else if(!n_shexp_is_valid_varname(arglist[0]))
1096 xcp = N_("not a valid variable name");
1097 else if(!n_var_is_user_writable(arglist[0]))
1098 xcp = N_("either not a user writable, or a boolean variable");
1099 else
1100 xcp = NULL;
1101 if(xcp != NULL){
1102 n_err("`%s': vput: %s: %s\n",
1103 gcdp->gcd_name, V_(xcp), n_shexp_quote_cp(arglist[0], FAL0));
1104 nerrn = n_ERR_NOTSUP;
1105 goto jleave;
1107 ++arglist;
1108 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
1109 * TODO on in getrawlist() etc., i.e., the argument vector producers,
1110 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
1111 }else{
1112 n_err(_("`vput' prefix does not affect `%s'\n"), gcdp->gcd_name);
1113 flags &= ~a_VPUT;
1117 switch(gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK){
1118 case n_CMD_ARG_TYPE_MSGLIST:
1119 /* Message list defaulting to nearest forward legal message */
1120 if(n_msgvec == NULL)
1121 goto jemsglist;
1122 if((c = getmsglist(cp, n_msgvec, gcdp->gcd_msgflag)) < 0){
1123 nerrn = n_ERR_NOMSG;
1124 flags |= a_NO_ERRNO;
1125 break;
1127 if(c == 0){
1128 if((n_msgvec[0] = first(gcdp->gcd_msgflag, gcdp->gcd_msgmask)) != 0)
1129 n_msgvec[1] = 0;
1131 if(n_msgvec[0] == 0){
1132 jemsglist:
1133 if(!(n_pstate & n_PS_HOOK_MASK))
1134 fprintf(n_stdout, _("No applicable messages\n"));
1135 nerrn = n_ERR_NOMSG;
1136 flags |= a_NO_ERRNO;
1137 break;
1139 rv = (*gcdp->gcd_func)(n_msgvec);
1140 break;
1142 case n_CMD_ARG_TYPE_NDMLIST:
1143 /* Message list with no defaults, but no error if none exist */
1144 if(n_msgvec == NULL)
1145 goto jemsglist;
1146 if((c = getmsglist(cp, n_msgvec, gcdp->gcd_msgflag)) < 0){
1147 nerrn = n_ERR_NOMSG;
1148 flags |= a_NO_ERRNO;
1149 break;
1151 rv = (*gcdp->gcd_func)(n_msgvec);
1152 break;
1154 case n_CMD_ARG_TYPE_STRING:
1155 /* Just the straight string, old style, with leading blanks removed */
1156 while(spacechar(*cp))
1157 ++cp;
1158 rv = (*gcdp->gcd_func)(cp);
1159 break;
1160 case n_CMD_ARG_TYPE_RAWDAT:
1161 /* Just the straight string, leading blanks removed, placed in argv[] */
1162 while(spacechar(*cp))
1163 ++cp;
1164 *arglist++ = cp;
1165 *arglist = NULL;
1166 rv = (*gcdp->gcd_func)(arglist_base);
1167 break;
1169 case n_CMD_ARG_TYPE_WYSH:
1170 c = 1;
1171 if(0){
1172 /* FALLTHRU */
1173 case n_CMD_ARG_TYPE_WYRA:
1174 c = (flags & a_WYSH) ? 1 : 0;
1175 if(0){
1176 case n_CMD_ARG_TYPE_RAWLIST:
1177 c = 0;
1180 if((c = getrawlist((c != 0), arglist,
1181 n_MAXARGC - PTR2SIZE(arglist - arglist_base),
1182 cp, line.l)) < 0){
1183 n_err(_("Invalid argument list\n"));
1184 flags |= a_NO_ERRNO;
1185 break;
1188 if(c < gcdp->gcd_minargs){
1189 n_err(_("`%s' requires at least %u arg(s)\n"),
1190 gcdp->gcd_name, (ui32_t)gcdp->gcd_minargs);
1191 flags |= a_NO_ERRNO;
1192 break;
1194 #undef gcd_minargs
1195 if(c > gcdp->gcd_maxargs){
1196 n_err(_("`%s' takes no more than %u arg(s)\n"),
1197 gcdp->gcd_name, (ui32_t)gcdp->gcd_maxargs);
1198 flags |= a_NO_ERRNO;
1199 break;
1201 #undef gcd_maxargs
1203 if(flags & a_VPUT)
1204 n_pstate |= n_PS_ARGMOD_VPUT;
1206 rv = (*gcdp->gcd_func)(arglist_base);
1207 if(a_go_xcall != NULL)
1208 goto jret0;
1209 break;
1211 default:
1212 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
1213 gcdp->gcd_caflags & n_CMD_ARG_TYPE_MASK); )
1214 nerrn = n_ERR_NOTOBACCO;
1215 nexn = 1;
1216 goto jret0;
1219 if(!(gcdp->gcd_caflags & n_CMD_ARG_H))
1220 gecp->gec_add_history = (((gcdp->gcd_caflags & n_CMD_ARG_G) ||
1221 (n_pstate & n_PS_MSGLIST_GABBY)) ? TRUM1 : TRU1);
1223 if(rv != 0){
1224 if(!(flags & a_NO_ERRNO)){
1225 if(gcdp->gcd_caflags & n_CMD_ARG_EM)
1226 flags |= a_NO_ERRNO;
1227 else if((nerrn = n_err_no) == 0)
1228 nerrn = n_ERR_INVAL;
1229 }else
1230 flags ^= a_NO_ERRNO;
1231 }else if(gcdp->gcd_caflags & n_CMD_ARG_EM)
1232 flags |= a_NO_ERRNO;
1233 else
1234 nerrn = n_ERR_NONE;
1235 jleave:
1236 nexn = rv;
1238 if(flags & a_IGNERR){
1239 n_pstate &= ~n_PS_ERR_EXIT_MASK;
1240 n_exit_status = n_EXIT_OK;
1241 }else if(rv != 0){
1242 bool_t bo;
1244 if((bo = ok_blook(batch_exit_on_error))){
1245 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
1246 if(!(n_poption & n_PO_BATCH_FLAG))
1247 bo = FAL0;
1249 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
1250 n_pstate |= n_PS_ERR_QUIT;
1251 else if(!(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) &&
1252 ok_blook(posix))
1253 n_pstate |= n_PS_ERR_XIT;
1254 else
1255 rv = 0;
1257 if(rv != 0){
1258 if(n_exit_status == n_EXIT_OK)
1259 n_exit_status = n_EXIT_ERR;
1260 if((n_poption & n_PO_D_V) &&
1261 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
1262 n_alert(_("Non-interactive, bailing out due to errors "
1263 "in startup load phase"));
1264 goto jret;
1268 if(gcdp == NULL)
1269 goto jret0;
1270 if((gcdp->gcd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
1271 if(visible(dot))
1272 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
1273 sizeof("\\type") -1);
1275 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
1276 !(gcdp->gcd_caflags & n_CMD_ARG_T))
1277 n_pstate |= n_PS_SAW_COMMAND;
1278 jret0:
1279 rv = 0;
1280 jret:
1281 if(!(flags & a_NO_ERRNO))
1282 n_pstate_err_no = nerrn;
1283 n_pstate_ex_no = nexn;
1284 NYD_LEAVE;
1285 return (rv == 0);
1288 static struct a_go_cmd_desc const *
1289 a_go__firstfit(char const *comm){ /* TODO *hashtable*! linear list search!!! */
1290 struct a_go_cmd_desc const *gcdp;
1291 NYD2_ENTER;
1293 for(gcdp = a_go_cmd_tab; gcdp < &a_go_cmd_tab[n_NELEM(a_go_cmd_tab)]; ++gcdp)
1294 if(*comm == *gcdp->gcd_name && is_prefix(comm, gcdp->gcd_name))
1295 goto jleave;
1296 gcdp = NULL;
1297 jleave:
1298 NYD2_LEAVE;
1299 return gcdp;
1302 static void
1303 a_go_hangup(int s){
1304 NYD_X; /* Signal handler */
1305 n_UNUSED(s);
1306 /* nothing to do? */
1307 exit(n_EXIT_ERR);
1310 static void
1311 a_go_onintr(int s){ /* TODO block signals while acting */
1312 NYD_X; /* Signal handler */
1313 n_UNUSED(s);
1315 safe_signal(SIGINT, a_go_onintr);
1317 termios_state_reset();
1319 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
1321 if(interrupts != 1)
1322 n_err_sighdl(_("Interrupt\n"));
1323 safe_signal(SIGPIPE, a_go_oldpipe);
1324 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
1327 static void
1328 a_go_cleanup(enum a_go_cleanup_mode gcm){
1329 /* Signals blocked */
1330 struct a_go_ctx *gcp;
1331 NYD_ENTER;
1333 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1334 hold_all_sigs();
1335 jrestart:
1336 gcp = a_go_ctx;
1338 /* Free input injections of this level first */
1339 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
1340 struct a_go_input_inject **giipp, *giip;
1342 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
1343 *giipp = giip->gii_next;
1344 n_free(giip);
1348 /* Cleanup non-crucial external stuff */
1349 n_COLOUR(
1350 if(gcp->gc_data.gdc_colour != NULL)
1351 n_colour_stack_del(NULL);
1354 /* Work the actual context (according to cleanup mode) */
1355 if(gcp->gc_outer == NULL){
1356 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
1357 a_go_xcall = NULL;
1358 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1359 n_pstate &= ~n_PS_ERR_EXIT_MASK;
1360 close_all_files();
1361 }else{
1362 if(!(n_pstate & n_PS_SOURCING))
1363 close_all_files();
1366 n_memory_reset();
1368 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1369 assert(a_go_xcall == NULL);
1370 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
1371 assert(gcp->gc_on_finalize == NULL);
1372 assert(gcp->gc_data.gdc_colour == NULL);
1373 goto jxleave;
1374 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
1375 n_memory_reset();
1376 goto jxleave;
1377 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
1378 n_stdin = gcp->gc_splice_stdin;
1379 n_stdout = gcp->gc_splice_stdout;
1380 n_psonce = gcp->gc_splice_psonce;
1381 goto jstackpop;
1384 /* Cleanup crucial external stuff */
1385 if(gcp->gc_data.gdc_ifcond != NULL){
1386 n_cnd_if_stack_del(gcp->gc_data.gdc_ifcond);
1387 if(!(gcp->gc_flags & a_GO_FORCE_EOF) &&
1388 !(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
1389 a_go_xcall == NULL)
1390 n_err(_("Unmatched `if' at end of %s %s\n"),
1391 ((gcp->gc_flags & a_GO_MACRO
1392 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
1393 : _("`source'd file"))),
1394 gcp->gc_name);
1395 gcm |= a_GO_CLEANUP_ERROR;
1398 /* Teardown context */
1399 if(gcp->gc_flags & a_GO_MACRO){
1400 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
1401 char **lp;
1403 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
1404 n_free(*lp);
1405 ++gcp->gc_loff;
1407 /* Part of gcp's memory chunk, then */
1408 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
1409 n_free(gcp->gc_lines);
1411 }else if(gcp->gc_flags & a_GO_PIPE)
1412 /* XXX command manager should -TERM then -KILL instead of hoping
1413 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
1414 Pclose(gcp->gc_file, TRU1);
1415 else if(gcp->gc_flags & a_GO_FILE)
1416 Fclose(gcp->gc_file);
1418 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
1419 if(gcp->gc_data.gdc_mempool != NULL)
1420 n_memory_pool_pop(NULL);
1421 }else
1422 n_memory_reset();
1424 jstackpop:
1425 n_go_data = &(a_go_ctx = gcp->gc_outer)->gc_data;
1426 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
1427 (a_GO_MACRO | a_GO_SUPER_MACRO)){
1428 n_pstate &= ~n_PS_SOURCING;
1429 assert(n_pstate & n_PS_ROBOT);
1430 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
1431 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1432 else
1433 assert(n_pstate & n_PS_ROBOT);
1435 if(gcp->gc_on_finalize != NULL)
1436 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
1438 if(gcm & a_GO_CLEANUP_ERROR){
1439 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
1440 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
1441 goto jerr;
1443 jleave:
1444 if(gcp->gc_flags & a_GO_FREE)
1445 n_free(gcp);
1447 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
1448 goto jrestart;
1450 jxleave:
1451 NYD_LEAVE;
1452 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1453 rele_all_sigs();
1454 return;
1456 jerr:
1457 /* With *posix* we follow what POSIX says:
1458 * Any errors in the start-up file shall either cause mailx to
1459 * terminate with a diagnostic message and a non-zero status or to
1460 * continue after writing a diagnostic message, ignoring the
1461 * remainder of the lines in the start-up file
1462 * Print the diagnostic only for the outermost resource unless the user
1463 * is debugging or in verbose mode */
1464 if((n_poption & n_PO_D_V) ||
1465 (!(n_psonce & n_PSO_STARTED) &&
1466 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1467 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
1468 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1469 n_alert(_("Stopped %s %s due to errors%s"),
1470 (n_psonce & n_PSO_STARTED
1471 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1472 : (gcp->gc_flags & a_GO_MACRO
1473 ? (gcp->gc_flags & a_GO_MACRO_CMD
1474 ? _("evaluating command") : _("evaluating macro"))
1475 : (gcp->gc_flags & a_GO_PIPE
1476 ? _("executing `source'd pipe")
1477 : (gcp->gc_flags & a_GO_FILE
1478 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1480 : (gcp->gc_flags & a_GO_MACRO
1481 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
1482 ? _("evaluating command line") : _("evaluating macro"))
1483 : _("loading initialization resource"))),
1484 gcp->gc_name,
1485 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
1486 goto jleave;
1489 static bool_t
1490 a_go_file(char const *file, bool_t silent_open_error){
1491 struct a_go_ctx *gcp;
1492 sigset_t osigmask;
1493 size_t nlen;
1494 char *nbuf;
1495 bool_t ispipe;
1496 FILE *fip;
1497 NYD_ENTER;
1499 fip = NULL;
1501 /* Being a command argument file is space-trimmed *//* TODO v15 with
1502 * TODO WYRALIST this is no longer necessary true, and for that we
1503 * TODO don't set _PARSE_TRIMSPACE because we cannot! -> cmd-tab.h!! */
1504 #if 0
1505 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
1506 file[--nlen] == '|')))
1507 #else
1508 ispipe = FAL0;
1509 if(!silent_open_error){
1510 for(nlen = strlen(file); nlen > 0;){
1511 char c;
1513 c = file[--nlen];
1514 if(!spacechar(c)){
1515 if(c == '|'){
1516 nbuf = savestrbuf(file, nlen);
1517 ispipe = TRU1;
1519 break;
1523 #endif
1525 if(ispipe){
1526 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
1527 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
1528 goto jeopencheck;
1529 }else if((nbuf = fexpand(file, FEXP_LOCAL)) == NULL)
1530 goto jeopencheck;
1531 else if((fip = Fopen(nbuf, "r")) == NULL){
1532 jeopencheck:
1533 if(!silent_open_error || (n_poption & n_PO_D_V))
1534 n_perr(nbuf, 0);
1535 if(silent_open_error)
1536 fip = (FILE*)-1;
1537 goto jleave;
1540 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1542 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1543 (nlen = strlen(nbuf) +1));
1544 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1546 hold_all_sigs();
1548 gcp->gc_outer = a_go_ctx;
1549 gcp->gc_osigmask = osigmask;
1550 gcp->gc_file = fip;
1551 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1552 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1553 memcpy(gcp->gc_name, nbuf, nlen);
1555 a_go_ctx = gcp;
1556 n_go_data = &gcp->gc_data;
1557 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1558 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1559 fip = NULL;
1560 jleave:
1561 NYD_LEAVE;
1562 return (fip != NULL);
1565 static bool_t
1566 a_go_load(struct a_go_ctx *gcp){
1567 NYD2_ENTER;
1569 assert(!(n_psonce & n_PSO_STARTED));
1570 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1572 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1573 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1575 hold_all_sigs();
1577 /* POSIX:
1578 * Any errors in the start-up file shall either cause mailx to terminate
1579 * with a diagnostic message and a non-zero status or to continue after
1580 * writing a diagnostic message, ignoring the remainder of the lines in
1581 * the start-up file. */
1582 gcp->gc_outer = a_go_ctx;
1583 a_go_ctx = gcp;
1584 n_go_data = &gcp->gc_data;
1585 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1586 n_pstate |= n_PS_ROBOT |
1587 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1589 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1591 rele_all_sigs();
1593 n_go_main_loop();
1594 NYD2_LEAVE;
1595 return (((n_psonce & n_PSO_EXIT_MASK) |
1596 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1599 static void
1600 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1601 NYD_X; /* Signal handler */
1602 n_UNUSED(sig);
1603 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1606 static bool_t
1607 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1608 sighandler_type soldhdl;
1609 struct a_go_eval_ctx gec;
1610 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1611 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1612 sigset_t osigmask;
1613 NYD2_ENTER;
1615 memset(&gec, 0, sizeof gec);
1616 osigmask = gcp->gc_osigmask;
1617 hadint = FAL0;
1618 f = a_RETOK;
1620 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1621 safe_signal(SIGINT, &a_go__eloopint);
1622 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1623 hold_all_sigs();
1624 hadint = TRU1;
1625 f &= ~a_RETOK;
1626 a_go_xcall = NULL;
1627 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1628 goto jjump;
1632 for(;; f |= a_TICKED){
1633 int n;
1635 if(f & a_TICKED)
1636 n_memory_reset();
1638 /* Read a line of commands and handle end of file specially */
1639 gec.gec_line.l = gec.gec_line_size;
1640 rele_all_sigs();
1641 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL);
1642 hold_all_sigs();
1643 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1644 gec.gec_line.l = (ui32_t)n;
1646 if(n < 0)
1647 break;
1649 rele_all_sigs();
1650 if(!a_go_evaluate(&gec))
1651 f &= ~a_RETOK;
1652 hold_all_sigs();
1654 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1655 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1656 break;
1659 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1660 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1661 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1662 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1664 if(gec.gec_line.s != NULL)
1665 n_free(gec.gec_line.s);
1667 if(soldhdl != SIG_IGN)
1668 safe_signal(SIGINT, soldhdl);
1669 NYD2_LEAVE;
1670 rele_all_sigs();
1671 if(hadint){
1672 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1673 n_raise(SIGINT);
1675 return (f & a_RETOK);
1678 static int
1679 a_go_c_read(void *v){ /* TODO IFS? how? -r */
1680 struct n_sigman sm;
1681 char const **argv, *cp, *cp2;
1682 char *linebuf;
1683 size_t linesize;
1684 int rv;
1685 NYD2_ENTER;
1687 rv = 0;
1688 linesize = 0;
1689 linebuf = NULL;
1690 argv = v;
1692 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1693 case 0:
1694 break;
1695 default:
1696 n_pstate_err_no = n_ERR_INTR;
1697 rv = 1;
1698 goto jleave;
1700 rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
1701 ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
1702 n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
1703 n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
1704 NULL, &linebuf, &linesize, NULL);
1705 n_pstate_err_no = n_ERR_NONE; /* TODO I/O error if rv<0! */
1706 if(rv < 0)
1707 goto jleave;
1709 if(rv > 0){
1710 cp = linebuf;
1712 for(rv = 0; *argv != NULL; ++argv){
1713 char c;
1715 while(spacechar(*cp))
1716 ++cp;
1717 if(*cp == '\0')
1718 break;
1720 /* The last variable gets the remaining line less trailing IFS */
1721 if(argv[1] == NULL){
1722 for(cp2 = cp; *cp2 != '\0'; ++cp2)
1724 for(; cp2 > cp; --cp2){
1725 c = cp2[-1];
1726 if(!spacechar(c))
1727 break;
1729 }else
1730 for(cp2 = cp; (c = *++cp2) != '\0';)
1731 if(spacechar(c))
1732 break;
1734 /* C99 xxx This is a CC warning workaround (-Wbad-function-cast) */{
1735 char *vcp;
1737 vcp = savestrbuf(cp, PTR2SIZE(cp2 - cp));
1738 if(!a_go__read_set(*argv, vcp)){
1739 n_pstate_err_no = n_ERR_NOTSUP;
1740 rv = 1;
1741 break;
1745 cp = cp2;
1749 /* Set the remains to the empty string */
1750 for(; *argv != NULL; ++argv)
1751 if(!a_go__read_set(*argv, n_empty)){
1752 n_pstate_err_no = n_ERR_NOTSUP;
1753 rv = 1;
1754 break;
1757 n_sigman_cleanup_ping(&sm);
1758 jleave:
1759 if(linebuf != NULL)
1760 n_free(linebuf);
1761 NYD2_LEAVE;
1762 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1763 return rv;
1766 static bool_t
1767 a_go__read_set(char const *cp, char const *value){
1768 bool_t rv;
1769 NYD2_ENTER;
1771 if(!n_shexp_is_valid_varname(cp))
1772 value = N_("not a valid variable name");
1773 else if(!n_var_is_user_writable(cp))
1774 value = N_("variable is read-only");
1775 else if(!n_var_vset(cp, (uintptr_t)value))
1776 value = N_("failed to update variable value");
1777 else{
1778 rv = TRU1;
1779 goto jleave;
1781 n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
1782 rv = FAL0;
1783 jleave:
1784 NYD2_LEAVE;
1785 return rv;
1788 FL int
1789 c_cmdnotsupp(void *vp){
1790 NYD_ENTER;
1791 n_UNUSED(vp);
1792 n_err(_("The requested feature is not compiled in\n"));
1793 NYD_LEAVE;
1794 return 1;
1797 FL void
1798 n_go_init(void){
1799 struct a_go_ctx *gcp;
1800 NYD2_ENTER;
1802 assert(n_stdin != NULL);
1804 gcp = (void*)a_go__mainctx_b.uf;
1805 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1806 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1807 gcp->gc_file = n_stdin;
1808 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1809 a_go_ctx = gcp;
1810 n_go_data = &gcp->gc_data;
1812 n_child_manager_start();
1813 NYD2_LEAVE;
1816 FL bool_t
1817 n_go_main_loop(void){ /* FIXME */
1818 struct a_go_eval_ctx gec;
1819 int n, eofcnt;
1820 bool_t volatile rv;
1821 NYD_ENTER;
1823 rv = TRU1;
1825 if (!(n_pstate & n_PS_SOURCING)) {
1826 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1827 safe_signal(SIGINT, &a_go_onintr);
1828 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1829 safe_signal(SIGHUP, &a_go_hangup);
1831 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1832 safe_signal(SIGPIPE, a_go_oldpipe);
1834 memset(&gec, 0, sizeof gec);
1836 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1837 hold_all_sigs();
1839 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1840 interrupts = 0;
1842 if(gec.gec_ever_seen)
1843 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1845 if (!(n_pstate & n_PS_SOURCING)) {
1846 char *cp;
1848 /* TODO Note: this buffer may contain a password. We should redefine
1849 * TODO the code flow which has to do that */
1850 if ((cp = termios_state.ts_linebuf) != NULL) {
1851 termios_state.ts_linebuf = NULL;
1852 termios_state.ts_linesize = 0;
1853 n_free(cp); /* TODO pool give-back */
1855 if (gec.gec_line.l > LINESIZE * 3) {
1856 n_free(gec.gec_line.s);
1857 gec.gec_line.s = NULL;
1858 gec.gec_line.l = gec.gec_line_size = 0;
1862 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1863 char *cp;
1865 if ((cp = ok_vlook(newmail)) != NULL) {
1866 struct stat st;
1868 /* FIXME TEST WITH NOPOLL ETC. !!! */
1869 n = (cp != NULL && strcmp(cp, "nopoll"));
1870 if ((mb.mb_type == MB_FILE && !stat(mailname, &st) &&
1871 st.st_size > mailsize) ||
1872 (mb.mb_type == MB_MAILDIR && n != 0)) {
1873 size_t odot = PTR2SIZE(dot - message);
1874 ui32_t odid = (n_pstate & n_PS_DID_PRINT_DOT);
1875 int i;
1877 rele_all_sigs();
1878 i = setfile(mailname,
1879 FEDIT_NEWMAIL |
1880 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY));
1881 hold_all_sigs();
1882 if(i < 0) {
1883 n_exit_status |= n_EXIT_ERR;
1884 rv = FAL0;
1885 break;
1887 dot = &message[odot];
1888 n_pstate |= odid;
1892 n_exit_status = n_EXIT_OK;
1895 /* Read a line of commands and handle end of file specially */
1896 gec.gec_line.l = gec.gec_line_size;
1897 rele_all_sigs();
1898 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1899 &gec.gec_line.s, &gec.gec_line.l, NULL);
1900 hold_all_sigs();
1901 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1902 gec.gec_line.l = (ui32_t)n;
1904 if (n < 0) {
1905 if (!(n_pstate & n_PS_ROBOT) &&
1906 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1907 ++eofcnt < 4) {
1908 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1909 n_go_input_clearerr();
1910 continue;
1912 break;
1915 n_pstate &= ~n_PS_HOOK_MASK;
1916 rele_all_sigs();
1917 rv = a_go_evaluate(&gec);
1918 hold_all_sigs();
1920 if(!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE) &&
1921 gec.gec_add_history)
1922 n_tty_addhist(gec.gec_line.s, (gec.gec_add_history != TRU1));
1924 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1925 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1926 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1927 default: break;
1929 if(n_psonce & n_PSO_EXIT_MASK)
1930 break;
1932 if(!rv)
1933 break;
1936 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1937 (rv ? 0 : a_GO_CLEANUP_ERROR));
1939 if (gec.gec_line.s != NULL)
1940 n_free(gec.gec_line.s);
1942 rele_all_sigs();
1943 NYD_LEAVE;
1944 return rv;
1947 FL void
1948 n_go_input_clearerr(void){
1949 FILE *fp;
1950 NYD2_ENTER;
1952 fp = NULL;
1954 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1955 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1956 fp = a_go_ctx->gc_file;
1958 if(fp != NULL)
1959 clearerr(fp);
1960 NYD2_LEAVE;
1963 FL void
1964 n_go_input_force_eof(void){
1965 NYD_ENTER;
1966 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1967 NYD_LEAVE;
1970 FL void
1971 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1972 size_t len){
1973 NYD_ENTER;
1974 if(len == UIZ_MAX)
1975 len = strlen(buf);
1977 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1978 len > 0){
1979 size_t i;
1980 struct a_go_input_inject *giip, **giipp;
1982 hold_all_sigs();
1984 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1985 ) + 1 + len +1);
1986 giipp = &a_go_ctx->gc_inject;
1987 giip->gii_next = *giipp;
1988 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1989 if(buf[i = 0] != ' ' && !(giif & n_GO_INPUT_INJECT_HISTORY))
1990 giip->gii_dat[i++] = ' '; /* TODO prim. hack to avoid history put! */
1991 memcpy(&giip->gii_dat[i], buf, len);
1992 i += len;
1993 giip->gii_dat[giip->gii_len = i] = '\0';
1994 *giipp = giip;
1996 rele_all_sigs();
1998 NYD_LEAVE;
2001 FL int
2002 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
2003 size_t *linesize, char const *string n_MEMORY_DEBUG_ARGS){
2004 /* TODO readline: linebuf pool!; n_go_input should return si64_t */
2005 struct n_string xprompt;
2006 FILE *ifile;
2007 bool_t doprompt, dotty;
2008 char const *iftype;
2009 int nold, n;
2010 NYD2_ENTER;
2012 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
2013 hold_all_sigs();
2015 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
2016 n = -1;
2017 goto jleave;
2020 if(gif & n_GO_INPUT_FORCE_STDIN)
2021 goto jforce_stdin;
2023 /* Special case macro mode: never need to prompt, lines have always been
2024 * unfolded already */
2025 if(a_go_ctx->gc_flags & a_GO_MACRO){
2026 struct a_go_input_inject *giip;
2028 if(*linebuf != NULL)
2029 n_free(*linebuf);
2031 /* Injection in progress? Don't care about the autocommit state here */
2032 if((giip = a_go_ctx->gc_inject) != NULL){
2033 a_go_ctx->gc_inject = giip->gii_next;
2035 *linesize = giip->gii_len;
2036 *linebuf = (char*)giip;
2037 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
2038 iftype = "INJECTION";
2039 }else{
2040 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
2041 *linesize = 0;
2042 n = -1;
2043 goto jleave;
2046 ++a_go_ctx->gc_loff;
2047 *linesize = strlen(*linebuf);
2048 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
2049 *linebuf = sbufdup(*linebuf, *linesize);
2051 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
2052 ? "-X OPTION"
2053 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
2055 n = (int)*linesize;
2056 n_pstate |= n_PS_READLINE_NL;
2057 goto jhave_dat;
2058 }else{
2059 /* Injection in progress? */
2060 struct a_go_input_inject **giipp, *giip;
2062 giipp = &a_go_ctx->gc_inject;
2064 if((giip = *giipp) != NULL){
2065 *giipp = giip->gii_next;
2067 if(giip->gii_commit){
2068 if(*linebuf != NULL)
2069 n_free(*linebuf);
2071 /* Simply reuse the buffer */
2072 n = (int)(*linesize = giip->gii_len);
2073 *linebuf = (char*)giip;
2074 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
2075 iftype = "INJECTION";
2076 n_pstate |= n_PS_READLINE_NL;
2077 goto jhave_dat;
2078 }else{
2079 string = savestrbuf(giip->gii_dat, giip->gii_len);
2080 n_free(giip);
2085 jforce_stdin:
2086 n_pstate &= ~n_PS_READLINE_NL;
2087 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
2088 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
2089 doprompt = ((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
2090 (n_PSO_INTERACTIVE | n_PSO_STARTED) && !(n_pstate & n_PS_ROBOT));
2091 dotty = (doprompt && !ok_blook(line_editor_disable));
2092 if(!doprompt)
2093 gif |= n_GO_INPUT_PROMPT_NONE;
2094 else{
2095 if(!dotty)
2096 n_string_creat_auto(&xprompt);
2097 if(prompt == NULL)
2098 gif |= n_GO_INPUT_PROMPT_EVAL;
2101 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
2102 if(!dotty && (gif & n_GO_INPUT_PROMPT_NONE))
2103 fflush(n_stdout);
2105 ifile = (gif & n_GO_INPUT_FORCE_STDIN) ? n_stdin : a_go_ctx->gc_file;
2106 if(ifile == NULL){
2107 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
2108 (a_go_ctx->gc_flags & a_GO_MACRO));
2109 ifile = n_stdin;
2112 for(nold = n = 0;;){
2113 if(dotty){
2114 assert(ifile == n_stdin);
2115 if(string != NULL && (n = (int)strlen(string)) > 0){
2116 if(*linesize > 0)
2117 *linesize += n +1;
2118 else
2119 *linesize = (size_t)n + LINESIZE +1;
2120 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
2121 memcpy(*linebuf, string, (size_t)n +1);
2123 string = NULL;
2125 rele_all_sigs();
2127 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n
2128 n_MEMORY_DEBUG_ARGSCALL);
2130 hold_all_sigs();
2131 }else{
2132 if(!(gif & n_GO_INPUT_PROMPT_NONE))
2133 n_tty_create_prompt(&xprompt, prompt, gif);
2135 rele_all_sigs();
2137 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
2138 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
2139 fflush(n_stdout);
2142 n = (readline_restart)(ifile, linebuf, linesize, n
2143 n_MEMORY_DEBUG_ARGSCALL);
2145 hold_all_sigs();
2147 if(n > 0 && nold > 0){
2148 char const *cp;
2149 int i;
2151 i = 0;
2152 cp = &(*linebuf)[nold];
2153 while(spacechar(*cp) && n - i >= nold)
2154 ++cp, ++i;
2155 if(i > 0){
2156 memmove(&(*linebuf)[nold], cp, n - nold - i);
2157 n -= i;
2158 (*linebuf)[n] = '\0';
2163 if(n <= 0)
2164 break;
2166 /* POSIX says:
2167 * An unquoted <backslash> at the end of a command line shall
2168 * be discarded and the next line shall continue the command */
2169 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\'){
2170 if(dotty)
2171 n_pstate |= n_PS_READLINE_NL;
2172 break;
2174 /* Definitely outside of quotes, thus the quoting rules are so that an
2175 * uneven number of successive reverse solidus at EOL is a continuation */
2176 if(n > 1){
2177 size_t i, j;
2179 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
2180 if((*linebuf)[i] != '\\')
2181 break;
2182 if(!(j & 1))
2183 break;
2185 (*linebuf)[nold = --n] = '\0';
2186 gif |= n_GO_INPUT_NL_FOLLOW;
2189 if(n < 0)
2190 goto jleave;
2191 (*linebuf)[*linesize = n] = '\0';
2193 jhave_dat:
2194 #if 0
2195 if(gif & n_GO_INPUT_DROP_TRAIL_SPC){
2196 char *cp, c;
2197 size_t i;
2199 for(cp = &(*linebuf)[i = (size_t)n];; --i){
2200 c = *--cp;
2201 if(!spacechar(c))
2202 break;
2204 (*linebuf)[n = (int)i] = '\0';
2207 if(gif & n_GO_INPUT_DROP_LEAD_SPC){
2208 char *cp, c;
2209 size_t j, i;
2211 for(cp = &(*linebuf)[0], j = (size_t)n, i = 0; i < j; ++i){
2212 c = *cp++;
2213 if(!spacechar(c))
2214 break;
2216 if(i > 0){
2217 memmove(&(*linebuf)[0], &(*linebuf)[i], j -= i);
2218 (*linebuf)[n = (int)j] = '\0';
2221 #endif /* 0 (notyet - must take care for reverse solidus escaped space) */
2223 if(n_poption & n_PO_D_VV)
2224 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
2225 jleave:
2226 if (n_pstate & n_PS_PSTATE_PENDMASK)
2227 a_go_update_pstate();
2229 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
2230 * TODO but only established from the outside and we need to drop this
2231 * TODO overlay context somehow */
2232 if(n < 0 && (a_go_ctx->gc_flags & a_GO_SPLICE))
2233 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
2235 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
2236 rele_all_sigs();
2237 NYD2_LEAVE;
2238 return n;
2241 FL char *
2242 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
2243 char const *string){
2244 struct n_sigman sm;
2245 size_t linesize;
2246 char *linebuf, * volatile rv;
2247 int n;
2248 NYD2_ENTER;
2250 linesize = 0;
2251 linebuf = NULL;
2252 rv = NULL;
2254 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
2255 case 0:
2256 break;
2257 default:
2258 goto jleave;
2261 n = n_go_input(gif, prompt, &linebuf, &linesize, string);
2262 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
2263 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE))
2264 n_tty_addhist(rv, ((gif & n_GO_INPUT_HIST_GABBY) != 0));
2266 n_sigman_cleanup_ping(&sm);
2267 jleave:
2268 if(linebuf != NULL)
2269 n_free(linebuf);
2270 NYD2_LEAVE;
2271 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
2272 return rv;
2275 FL bool_t
2276 n_go_load(char const *name){
2277 struct a_go_ctx *gcp;
2278 size_t i;
2279 FILE *fip;
2280 bool_t rv;
2281 NYD_ENTER;
2283 rv = TRU1;
2285 if(name == NULL || *name == '\0')
2286 goto jleave;
2287 else if((fip = Fopen(name, "r")) == NULL){
2288 if(n_poption & n_PO_D_V)
2289 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
2290 goto jleave;
2293 i = strlen(name) +1;
2294 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
2295 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2297 gcp->gc_file = fip;
2298 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
2299 memcpy(gcp->gc_name, name, i);
2301 if(n_poption & n_PO_D_V)
2302 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
2303 rv = a_go_load(gcp);
2304 jleave:
2305 NYD_LEAVE;
2306 return rv;
2309 FL bool_t
2310 n_go_Xargs(char const **lines, size_t cnt){
2311 static char const name[] = "-X";
2313 union{
2314 bool_t rv;
2315 ui64_t align;
2316 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
2317 } b;
2318 char const *srcp, *xsrcp;
2319 char *cp;
2320 size_t imax, i, len;
2321 struct a_go_ctx *gcp;
2322 NYD_ENTER;
2324 gcp = (void*)b.uf;
2325 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2327 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
2328 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
2329 memcpy(gcp->gc_name, name, sizeof name);
2331 /* The problem being that we want to support reverse solidus newline
2332 * escaping also within multiline -X, i.e., POSIX says:
2333 * An unquoted <backslash> at the end of a command line shall
2334 * be discarded and the next line shall continue the command
2335 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
2336 * entire lines array and set _MACRO_FREE_DATA */
2337 imax = cnt + 1;
2338 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
2340 /* For each of the input lines.. */
2341 for(i = len = 0, cp = NULL; cnt > 0;){
2342 bool_t keep;
2343 size_t j;
2345 if((j = strlen(srcp = *lines)) == 0){
2346 ++lines, --cnt;
2347 continue;
2350 /* Separate one line from a possible multiline input string */
2351 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
2352 *lines = &xsrcp[1];
2353 j = PTR2SIZE(xsrcp - srcp);
2354 }else
2355 ++lines, --cnt;
2357 /* The (separated) string may itself indicate soft newline escaping */
2358 if((keep = (srcp[j - 1] == '\\'))){
2359 size_t xj, xk;
2361 /* Need an uneven number of reverse solidus */
2362 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
2363 if(srcp[xj] != '\\')
2364 break;
2365 if(xk & 1)
2366 --j;
2367 else
2368 keep = FAL0;
2371 /* Strip any leading WS from follow lines, then */
2372 if(cp != NULL)
2373 while(j > 0 && spacechar(*srcp))
2374 ++srcp, --j;
2376 if(j > 0){
2377 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
2378 imax += 4;
2379 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
2380 imax);
2382 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
2383 memcpy(&cp[len], srcp, j);
2384 cp[len += j] = '\0';
2386 if(!keep)
2387 ++i;
2389 if(!keep)
2390 cp = NULL, len = 0;
2392 if(cp != NULL){
2393 assert(i + 1 < imax);
2394 gcp->gc_lines[i++] = cp;
2396 gcp->gc_lines[i] = NULL;
2398 b.rv = a_go_load(gcp);
2399 NYD_LEAVE;
2400 return b.rv;
2403 FL int
2404 c_source(void *v){
2405 int rv;
2406 NYD_ENTER;
2408 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
2409 NYD_LEAVE;
2410 return rv;
2413 FL int
2414 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
2415 int rv;
2416 NYD_ENTER;
2418 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
2419 NYD_LEAVE;
2420 return rv;
2423 FL bool_t
2424 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
2425 void (*on_finalize)(void*), void *finalize_arg){
2426 struct a_go_ctx *gcp;
2427 size_t i;
2428 int rv;
2429 sigset_t osigmask;
2430 NYD_ENTER;
2432 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2434 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2435 (i = strlen(name) +1));
2436 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2438 hold_all_sigs();
2440 gcp->gc_outer = a_go_ctx;
2441 gcp->gc_osigmask = osigmask;
2442 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
2443 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2444 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2445 gcp->gc_lines = lines;
2446 gcp->gc_on_finalize = on_finalize;
2447 gcp->gc_finalize_arg = finalize_arg;
2448 memcpy(gcp->gc_name, name, i);
2450 a_go_ctx = gcp;
2451 n_go_data = &gcp->gc_data;
2452 n_pstate |= n_PS_ROBOT;
2453 rv = a_go_event_loop(gcp, gif);
2455 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
2456 if(a_go_xcall != NULL){
2457 if(a_go_xcall == (struct a_go_xcall*)-1)
2458 a_go_xcall = NULL;
2459 else if(a_go_xcall->gx_upto == gcp){
2460 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
2461 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
2462 while(a_go_xcall != NULL){
2463 char *cp, **argv;
2464 struct a_go_xcall *gxp;
2466 hold_all_sigs();
2468 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
2469 gxp = a_go_xcall;
2470 a_go_xcall = NULL;
2472 /* Recreate the ARGV of this command on the LOFI memory of the
2473 * hosting a_go_ctx, so that it will become auto-reclaimed */
2474 /* C99 */{
2475 void *vp;
2477 vp = n_lofi_alloc(gxp->gx_buflen);
2478 cp = vp;
2479 argv = vp;
2481 cp += sizeof(*argv) * (gxp->gx_argc + 1);
2482 for(i = 0; i < gxp->gx_argc; ++i){
2483 argv[i] = cp;
2484 memcpy(cp, gxp->gx_argv[i].s, gxp->gx_argv[i].l +1);
2485 cp += gxp->gx_argv[i].l +1;
2487 argv[i] = NULL;
2488 n_free(gxp);
2490 rele_all_sigs();
2492 (void)c_call(argv);
2494 n_lofi_free(argv);
2496 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) != 0);
2497 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
2500 NYD_LEAVE;
2501 return rv;
2504 FL bool_t
2505 n_go_command(enum n_go_input_flags gif, char const *cmd){
2506 struct a_go_ctx *gcp;
2507 bool_t rv;
2508 size_t i, ial;
2509 sigset_t osigmask;
2510 NYD_ENTER;
2512 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2514 i = strlen(cmd) +1;
2515 ial = n_ALIGN(i);
2516 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2517 ial + 2*sizeof(char*));
2518 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2520 hold_all_sigs();
2522 gcp->gc_outer = a_go_ctx;
2523 gcp->gc_osigmask = osigmask;
2524 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
2525 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2526 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2527 gcp->gc_lines = (void*)&gcp->gc_name[ial];
2528 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
2529 gcp->gc_lines[1] = NULL;
2531 a_go_ctx = gcp;
2532 n_go_data = &gcp->gc_data;
2533 n_pstate |= n_PS_ROBOT;
2534 rv = a_go_event_loop(gcp, gif);
2535 NYD_LEAVE;
2536 return rv;
2539 FL void
2540 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
2541 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
2542 struct a_go_ctx *gcp;
2543 size_t i;
2544 sigset_t osigmask;
2545 NYD_ENTER;
2547 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2549 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2550 (i = strlen(cmd) +1));
2551 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2553 hold_all_sigs();
2555 gcp->gc_outer = a_go_ctx;
2556 gcp->gc_osigmask = osigmask;
2557 gcp->gc_file = new_stdin;
2558 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE;
2559 gcp->gc_on_finalize = on_finalize;
2560 gcp->gc_finalize_arg = finalize_arg;
2561 gcp->gc_splice_stdin = n_stdin;
2562 gcp->gc_splice_stdout = n_stdout;
2563 gcp->gc_splice_psonce = n_psonce;
2564 memcpy(gcp->gc_name, cmd, i);
2566 n_stdin = new_stdin;
2567 n_stdout = new_stdout;
2568 n_psonce = new_psonce;
2569 a_go_ctx = gcp;
2570 n_pstate |= n_PS_ROBOT;
2572 rele_all_sigs();
2573 NYD_LEAVE;
2576 FL void
2577 n_go_splice_hack_remove_after_jump(void){
2578 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2581 FL bool_t
2582 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2583 struct a_go_ctx *gcp;
2584 bool_t rv;
2585 NYD2_ENTER;
2587 rv = FAL0;
2589 /* Only when interactive and startup completed */
2590 if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) !=
2591 (n_PSO_INTERACTIVE | n_PSO_STARTED))
2592 goto jleave;
2594 /* Not when running any hook */
2595 if(n_pstate & n_PS_HOOK_MASK)
2596 goto jleave;
2598 /* Traverse up the stack:
2599 * . not when controlled by a child process
2600 * TODO . not when there are pipes involved, we neither handle job control,
2601 * TODO nor process groups, that is, controlling terminal acceptably
2602 * . not when sourcing a file */
2603 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2604 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2605 goto jleave;
2608 rv = TRU1;
2609 jleave:
2610 NYD2_LEAVE;
2611 return rv;
2614 /* s-it-mode */