`read': trim_ifs(), $?/$! semantics rewrite; fix -1 input error case
[s-mailx.git] / go.c
blob1f64a00515dfea5b768db08a4f0db52f8db43edb
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.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE go
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 enum a_go_flags{
44 a_GO_NONE,
45 a_GO_FREE = 1u<<0, /* Structure was allocated, n_free() it */
46 a_GO_PIPE = 1u<<1, /* Open on a pipe */
47 a_GO_FILE = 1u<<2, /* Loading or sourcing a file */
48 a_GO_MACRO = 1u<<3, /* Running a macro */
49 a_GO_MACRO_FREE_DATA = 1u<<4, /* Lines are allocated, n_free() once done */
50 /* TODO For simplicity this is yet _MACRO plus specialization overlay
51 * TODO (_X_OPTION, _CMD) -- these should be types on their own! */
52 a_GO_MACRO_X_OPTION = 1u<<5, /* Macro indeed command line -X option */
53 a_GO_MACRO_CMD = 1u<<6, /* Macro indeed single-line: ~:COMMAND */
54 /* TODO a_GO_SPLICE: the right way to support *on-compose-splice(-shell)?*
55 * TODO would be a command_loop object that emits an on_read_line event, and
56 * TODO have a special handler for the compose mode; with that, then,
57 * TODO _event_loop() would not call _evaluate() but CTX->on_read_line,
58 * TODO and _evaluate() would be the standard impl.,
59 * TODO whereas the COMMAND ESCAPE switch in collect.c would be another one.
60 * TODO With this generic accmacvar.c:temporary_compose_mode_hook_call()
61 * TODO could be dropped, and n_go_macro() could become extended,
62 * TODO and/or we would add a n_go_anything(), which would allow special
63 * TODO input handlers, special I/O input and output, special `localopts'
64 * TODO etc., to be glued to the new execution context. And all I/O all
65 * TODO over this software should not use stdin/stdout, but CTX->in/out.
66 * TODO The pstate must be a property of the current execution context, too.
67 * TODO This not today. :( For now we invent a special SPLICE execution
68 * TODO context overlay that at least allows to temporarily modify the
69 * TODO global pstate, and the global stdin and stdout pointers. HACK!
70 * TODO This splice thing is very special and has to go again. HACK!!
71 * TODO a_go_input() will drop it once it sees EOF (HACK!), but care for
72 * TODO jumps must be taken by splice creators. HACK!!! But works. ;} */
73 a_GO_SPLICE = 1u<<7,
74 /* If it is none of those, it must be the outermost, the global one */
75 a_GO_TYPE_MASK = a_GO_PIPE | a_GO_FILE | a_GO_MACRO |
76 /* a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD | */ a_GO_SPLICE,
78 a_GO_FORCE_EOF = 1u<<8, /* go_input() shall return EOF next */
79 a_GO_IS_EOF = 1u<<9,
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 enum a_go_hist_flags{
118 a_GO_HIST_NONE = 0,
119 a_GO_HIST_ADD = 1u<<0,
120 a_GO_HIST_GABBY = 1u<<1,
121 a_GO_HIST_INIT = 1u<<2
124 struct a_go_eval_ctx{
125 struct str gec_line; /* The terminated data to _evaluate() */
126 ui32_t gec_line_size; /* May be used to store line memory size */
127 bool_t gec_ever_seen; /* Has ever been used (main_loop() only) */
128 ui8_t gec__dummy[2];
129 ui8_t gec_hist_flags; /* enum a_go_hist_flags */
130 char const *gec_hist_cmd; /* If a_GO_HIST_ADD only, cmd and args */
131 char const *gec_hist_args;
134 struct a_go_input_inject{
135 struct a_go_input_inject *gii_next;
136 size_t gii_len;
137 bool_t gii_commit;
138 bool_t gii_no_history;
139 char gii_dat[n_VFIELD_SIZE(6)];
142 struct a_go_ctx{
143 struct a_go_ctx *gc_outer;
144 sigset_t gc_osigmask;
145 ui32_t gc_flags; /* enum a_go_flags */
146 ui32_t gc_loff; /* Pseudo (macro): index in .gc_lines */
147 char **gc_lines; /* Pseudo content, lines unfolded */
148 FILE *gc_file; /* File we were in, if applicable */
149 struct a_go_input_inject *gc_inject; /* To be consumed first */
150 void (*gc_on_finalize)(void *);
151 void *gc_finalize_arg;
152 sigjmp_buf gc_eloop_jmp; /* TODO one day... for _event_loop() */
153 /* SPLICE hacks: saved stdin/stdout, saved pstate */
154 FILE *gc_splice_stdin;
155 FILE *gc_splice_stdout;
156 ui32_t gc_splice_psonce;
157 ui8_t gc_splice__dummy[4];
158 struct n_go_data_ctx gc_data;
159 char gc_name[n_VFIELD_SIZE(0)]; /* Name of file or macro */
162 static sighandler_type a_go_oldpipe;
163 /* a_go_cmd_tab[] after fun protos */
165 /* Our current execution context, and the buffer backing the outermost level */
166 static struct a_go_ctx *a_go_ctx;
168 #define a_GO_MAINCTX_NAME "Main event loop"
169 static union{
170 ui64_t align;
171 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
172 sizeof(a_GO_MAINCTX_NAME)];
173 } a_go__mainctx_b;
175 /* `xcall' stack-avoidance bypass optimization. This actually is
176 * a n_cmd_arg_save_to_heap() buffer with n_cmd_arg_ctx.cac_indat misused to
177 * point to the a_go_ctx to unroll up to */
178 static void *a_go_xcall;
180 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
182 /* n_PS_STATE_PENDMASK requires some actions */
183 static void a_go_update_pstate(void);
185 /* Evaluate a single command */
186 static bool_t a_go_evaluate(struct a_go_eval_ctx *gecp);
188 /* Branch here on hangup signal and simulate "exit" */
189 static void a_go_hangup(int s);
191 /* The following gets called on receipt of an interrupt */
192 static void a_go_onintr(int s);
194 /* Cleanup current execution context, update the program state.
195 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
196 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we hold_all_sigs() */
197 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
199 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
200 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
201 * upon evaluation error, and TRU1 on success */
202 static bool_t a_go_file(char const *file, bool_t silent_open_error);
204 /* System resource file load()ing or -X command line option array traversal */
205 static bool_t a_go_load(struct a_go_ctx *gcp);
207 /* A simplified command loop for recursed state machines */
208 static bool_t a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
210 static void
211 a_go_update_pstate(void){
212 bool_t act;
213 NYD_ENTER;
215 act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
216 n_pstate &= ~n_PS_PSTATE_PENDMASK;
218 if(act){
219 char buf[32];
221 snprintf(buf, sizeof buf, "%d", n_scrnwidth);
222 ok_vset(COLUMNS, buf);
223 snprintf(buf, sizeof buf, "%d", n_scrnheight);
224 ok_vset(LINES, buf);
226 NYD_LEAVE;
229 static bool_t
230 a_go_evaluate(struct a_go_eval_ctx *gecp){
231 /* xxx old style(9), but also old code */
232 /* TODO a_go_evaluate() should be splitted in multiple subfunctions,
233 * TODO `eval' should be a prefix, etc., a Ctx should be passed along */
234 struct str line;
235 struct n_string s, *sp;
236 struct str const *alias_exp;
237 char _wordbuf[2], **arglist_base/*[n_MAXARGC]*/, **arglist, *cp, *word;
238 char const *alias_name;
239 struct n_cmd_desc const *cdp;
240 si32_t nerrn, nexn; /* TODO n_pstate_ex_no -> si64_t! */
241 int rv, c;
242 enum{
243 a_NONE = 0,
244 a_ALIAS_MASK = n_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
245 a_NOPREFIX = 1u<<4, /* Modifier prefix not allowed right now */
246 a_NOALIAS = 1u<<5, /* "No alias!" expansion modifier */
247 /* New modifier prefixes must be reflected in a_go_c_alias()! */
248 a_IGNERR = 1u<<6, /* ignerr modifier prefix */
249 a_WYSH = 1u<<7, /* XXX v15+ drop wysh modifier prefix */
250 a_VPUT = 1u<<8, /* vput modifier prefix */
251 a_MODE_MASK = n_BITENUM_MASK(5, 8),
252 a_NO_ERRNO = 1u<<16 /* Don't set n_pstate_err_no */
253 } flags;
254 NYD_ENTER;
256 flags = a_NONE;
257 rv = 1;
258 nerrn = n_ERR_NONE;
259 nexn = n_EXIT_OK;
260 cdp = NULL;
261 alias_name = NULL;
262 n_UNINIT(alias_exp, NULL);
263 arglist =
264 arglist_base = n_autorec_alloc(sizeof(*arglist_base) * n_MAXARGC);
265 line = gecp->gec_line; /* TODO const-ify original (buffer)! */
266 assert(line.s[line.l] == '\0');
268 if(line.l > 0 && spacechar(line.s[0]))
269 gecp->gec_hist_flags = a_GO_HIST_NONE;
270 else if(gecp->gec_hist_flags & a_GO_HIST_ADD)
271 gecp->gec_hist_cmd = gecp->gec_hist_args = NULL;
272 sp = NULL;
274 /* Aliases that refer to shell commands or macro expansion restart */
275 jrestart:
276 if(n_str_trim_ifs(&line, TRU1)->l == 0){
277 line.s[0] = '\0';
278 gecp->gec_hist_flags = a_GO_HIST_NONE;
279 goto jempty;
281 (cp = line.s)[line.l] = '\0';
283 /* No-expansion modifier? */
284 if(!(flags & a_NOPREFIX) && *cp == '\\'){
285 line.s = ++cp;
286 --line.l;
287 flags |= a_NOALIAS;
290 /* Note: adding more special treatments must be reflected in the `help' etc.
291 * output in cmd-tab.c! */
293 /* Ignore null commands (comments) */
294 if(*cp == '#'){
295 gecp->gec_hist_flags = a_GO_HIST_NONE;
296 goto jret0;
299 /* Handle ! differently to get the correct lexical conventions */
300 if(*cp == '!')
301 ++cp;
302 /* Isolate the actual command; since it may not necessarily be
303 * separated from the arguments (as in `p1') we need to duplicate it to
304 * be able to create a NUL terminated version.
305 * We must be aware of several special one letter commands here */
306 else if((cp = n_UNCONST(n_cmd_isolate(cp))) == line.s &&
307 (*cp == '|' || *cp == '?'))
308 ++cp;
309 c = (int)PTR2SIZE(cp - line.s);
310 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
311 memcpy(word, arglist[0] = line.s, c);
312 word[c] = '\0';
313 line.l -= c;
314 line.s = cp;
316 /* It may be a modifier.
317 * Note: adding modifiers must be reflected in commandalias handling code */
318 if(c == sizeof("ignerr") -1 && !asccasecmp(word, "ignerr")){
319 flags |= a_NOPREFIX | a_IGNERR;
320 goto jrestart;
321 }else if(c == sizeof("wysh") -1 && !asccasecmp(word, "wysh")){
322 flags |= a_NOPREFIX | a_WYSH;
323 goto jrestart;
324 }else if(c == sizeof("vput") -1 && !asccasecmp(word, "vput")){
325 flags |= a_NOPREFIX | a_VPUT;
326 goto jrestart;
329 /* We need to trim for a possible history entry, but do it anyway and insert
330 * a space for argument separation in case of alias expansion. Also, do
331 * terminate again because nothing prevents aliases from introducing WS */
332 n_str_trim_ifs(&line, TRU1);
333 line.s[line.l] = '\0';
335 /* Lengthy history entry setup, possibly even redundant. But having
336 * normalized history entries is a good thing, and this is maybe still
337 * cheaper than parsing a StrList of words per se */
338 if((gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
339 ) == a_GO_HIST_ADD){
340 if(line.l > 0){
341 sp = n_string_creat_auto(&s);
342 sp = n_string_assign_buf(sp, line.s, line.l);
343 gecp->gec_hist_args = n_string_cp(sp);
346 sp = n_string_creat_auto(&s);
347 sp = n_string_reserve(sp, 32);
349 if(flags & a_NOALIAS)
350 sp = n_string_push_c(sp, '\\');
351 if(flags & a_IGNERR)
352 sp = n_string_push_buf(sp, "ignerr ", sizeof("ignerr ") -1);
353 if(flags & a_WYSH)
354 sp = n_string_push_buf(sp, "wysh ", sizeof("wysh ") -1);
355 if(flags & a_VPUT)
356 sp = n_string_push_buf(sp, "vput ", sizeof("vput ") -1);
357 gecp->gec_hist_flags = a_GO_HIST_ADD | a_GO_HIST_INIT;
360 /* Look up the command; if not found, bitch.
361 * Normally, a blank command would map to the first command in the
362 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
363 * confusion; act just the same for aliases */
364 if(*word == '\0'){
365 jempty:
366 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
367 alias_name != NULL){
368 gecp->gec_hist_flags = a_GO_HIST_NONE;
369 goto jret0;
371 cdp = n_cmd_default();
372 goto jexec;
375 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
376 ui8_t expcnt;
378 expcnt = (flags & a_ALIAS_MASK);
379 ++expcnt;
380 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
382 /* Avoid self-recursion; yes, the user could use \ no-expansion, but.. */
383 if(alias_name != NULL && !strcmp(word, alias_name)){
384 if(n_poption & n_PO_D_V)
385 n_err(_("Actively avoiding self-recursion of `commandalias': %s\n"),
386 word);
387 }else if((alias_name = n_commandalias_exists(word, &alias_exp)) != NULL){
388 size_t i;
390 if(sp != NULL){
391 sp = n_string_push_cp(sp, word);
392 gecp->gec_hist_cmd = n_string_cp(sp);
393 sp = NULL;
396 /* And join arguments onto alias expansion */
397 alias_name = word;
398 i = alias_exp->l;
399 cp = line.s;
400 line.s = n_autorec_alloc(i + 1 + line.l +1);
401 memcpy(line.s, alias_exp->s, i);
402 if(line.l > 0){
403 line.s[i++] = ' ';
404 memcpy(&line.s[i], cp, line.l);
406 line.s[i += line.l] = '\0';
407 line.l = i;
408 goto jrestart;
412 if((cdp = n_cmd_firstfit(word)) == NULL){
413 bool_t doskip;
415 if(!(doskip = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
416 n_err(_("Unknown command%s: `%s'\n"),
417 (doskip ? _(" (ignored due to `if' condition)") : n_empty),
418 prstr(word));
419 gecp->gec_hist_flags = a_GO_HIST_NONE;
420 if(doskip)
421 goto jret0;
422 nerrn = n_ERR_NOSYS;
423 goto jleave;
426 /* See if we should execute the command -- if a conditional we always
427 * execute it, otherwise, check the state of cond */
428 jexec:
429 if(!(cdp->cd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip())
430 goto jret0;
432 if(sp != NULL){
433 sp = n_string_push_cp(sp, cdp->cd_name);
434 gecp->gec_hist_cmd = n_string_cp(sp);
435 sp = NULL;
438 nerrn = n_ERR_INVAL;
440 /* Process the arguments to the command, depending on the type it expects */
441 if((cdp->cd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
442 !(n_poption & n_PO_BATCH_FLAG)){
443 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
444 cdp->cd_name);
445 goto jleave;
447 if(!(cdp->cd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
448 n_err(_("May not execute `%s' while sending\n"), cdp->cd_name);
449 goto jleave;
451 if(cdp->cd_caflags & n_CMD_ARG_R){
452 if(n_pstate & n_PS_COMPOSE_MODE){
453 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
454 n_err(_("Cannot invoke `%s' when in compose mode\n"), cdp->cd_name);
455 goto jleave;
457 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
458 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
459 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING)){
460 n_err(_("Cannot invoke `%s' from a macro or during file inclusion\n"),
461 cdp->cd_name);
462 goto jleave;
465 if((cdp->cd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED)){
466 n_err(_("May not execute `%s' during startup\n"), cdp->cd_name);
467 goto jleave;
469 if(!(cdp->cd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
470 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
471 cdp->cd_name);
472 goto jleave;
475 if((cdp->cd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
476 n_err(_("Cannot execute `%s' without active mailbox\n"), cdp->cd_name);
477 goto jleave;
479 if((cdp->cd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
480 n_err(_("May not execute `%s' -- message file is read only\n"),
481 cdp->cd_name);
482 goto jleave;
485 if(cdp->cd_caflags & n_CMD_ARG_O)
486 n_OBSOLETE2(_("this command will be removed"), cdp->cd_name);
488 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
489 * TODO doesn't use any of those list commands which strip this mask,
490 * TODO and for now we misuse bits for checking relation to history;
491 * TODO argument state should be property of a per-command carrier instead */
492 n_pstate &= ~n_PS_ARGLIST_MASK;
494 if((flags & a_WYSH) &&
495 (cdp->cd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
496 n_err(_("`wysh' prefix does not affect `%s'\n"), cdp->cd_name);
497 flags &= ~a_WYSH;
500 if(flags & a_VPUT){
501 if(cdp->cd_caflags & n_CMD_ARG_V){
502 char const *emsg;
504 emsg = line.s; /* xxx Cannot pass &char* as char const**, so no cp */
505 arglist[0] = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIM_SPACE |
506 n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_LOG |
507 n_SHEXP_PARSE_META_KEEP), &emsg);
508 line.l -= PTR2SIZE(emsg - line.s);
509 line.s = cp = n_UNCONST(emsg);
510 if(cp == NULL)
511 emsg = N_("could not parse input token");
512 else if(!n_shexp_is_valid_varname(arglist[0]))
513 emsg = N_("not a valid variable name");
514 else if(!n_var_is_user_writable(arglist[0]))
515 emsg = N_("either not a user writable, or a boolean variable");
516 else
517 emsg = NULL;
518 if(emsg != NULL){
519 n_err("`%s': vput: %s: %s\n",
520 cdp->cd_name, V_(emsg), n_shexp_quote_cp(arglist[0], FAL0));
521 nerrn = n_ERR_NOTSUP;
522 goto jleave;
524 ++arglist;
525 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
526 * TODO on in getrawlist() etc., i.e., the argument vector producers,
527 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
528 }else{
529 n_err(_("`vput' prefix does not affect `%s'\n"), cdp->cd_name);
530 flags &= ~a_VPUT;
534 switch(cdp->cd_caflags & n_CMD_ARG_TYPE_MASK){
535 case n_CMD_ARG_TYPE_MSGLIST:
536 /* Message list defaulting to nearest forward legal message */
537 if(n_msgvec == NULL)
538 goto jemsglist;
539 if((c = getmsglist(line.s, n_msgvec, cdp->cd_msgflag)) < 0){
540 nerrn = n_ERR_NOMSG;
541 flags |= a_NO_ERRNO;
542 break;
544 if(c == 0){
545 if((n_msgvec[0] = first(cdp->cd_msgflag, cdp->cd_msgmask)) != 0)
546 n_msgvec[1] = 0;
548 if(n_msgvec[0] == 0){
549 jemsglist:
550 if(!(n_pstate & n_PS_HOOK_MASK))
551 fprintf(n_stdout, _("No applicable messages\n"));
552 nerrn = n_ERR_NOMSG;
553 flags |= a_NO_ERRNO;
554 break;
556 rv = (*cdp->cd_func)(n_msgvec);
557 break;
559 case n_CMD_ARG_TYPE_NDMLIST:
560 /* Message list with no defaults, but no error if none exist */
561 if(n_msgvec == NULL)
562 goto jemsglist;
563 if((c = getmsglist(line.s, n_msgvec, cdp->cd_msgflag)) < 0){
564 nerrn = n_ERR_NOMSG;
565 flags |= a_NO_ERRNO;
566 break;
568 rv = (*cdp->cd_func)(n_msgvec);
569 break;
571 case n_CMD_ARG_TYPE_STRING:
572 /* Just the straight string, old style, with leading blanks removed */
573 for(cp = line.s; spacechar(*cp);)
574 ++cp;
575 rv = (*cdp->cd_func)(cp);
576 break;
577 case n_CMD_ARG_TYPE_RAWDAT:
578 /* Just the straight string, placed in argv[] */
579 *arglist++ = line.s;
580 *arglist = NULL;
581 rv = (*cdp->cd_func)(arglist_base);
582 break;
584 case n_CMD_ARG_TYPE_WYSH:
585 c = 1;
586 if(0){
587 /* FALLTHRU */
588 case n_CMD_ARG_TYPE_WYRA:
589 c = (flags & a_WYSH) ? 1 : 0;
590 if(0){
591 case n_CMD_ARG_TYPE_RAWLIST:
592 c = 0;
595 if((c = getrawlist((c != 0), arglist,
596 n_MAXARGC - PTR2SIZE(arglist - arglist_base), line.s, line.l)) < 0){
597 n_err(_("Invalid argument list\n"));
598 flags |= a_NO_ERRNO;
599 break;
602 if(c < cdp->cd_minargs){
603 n_err(_("`%s' requires at least %u arg(s)\n"),
604 cdp->cd_name, (ui32_t)cdp->cd_minargs);
605 flags |= a_NO_ERRNO;
606 break;
608 #undef cd_minargs
609 if(c > cdp->cd_maxargs){
610 n_err(_("`%s' takes no more than %u arg(s)\n"),
611 cdp->cd_name, (ui32_t)cdp->cd_maxargs);
612 flags |= a_NO_ERRNO;
613 break;
615 #undef cd_maxargs
617 if(flags & a_VPUT)
618 n_pstate |= n_PS_ARGMOD_VPUT;
620 rv = (*cdp->cd_func)(arglist_base);
621 if(a_go_xcall != NULL)
622 goto jret0;
623 break;
625 case n_CMD_ARG_TYPE_ARG:{
626 /* TODO The _ARG_TYPE_ARG is preliminary, in the end we should have a
627 * TODO per command-ctx carrier that also has slots for it arguments,
628 * TODO and that should be passed along all the way. No more arglists
629 * TODO here, etc. */
630 struct n_cmd_arg_ctx cac;
632 cac.cac_desc = cdp->cd_cadp;
633 cac.cac_indat = line.s;
634 cac.cac_inlen = line.l;
635 if(!n_cmd_arg_parse(&cac)){
636 flags |= a_NO_ERRNO;
637 break;
640 if(flags & a_VPUT){
641 cac.cac_vput = *arglist_base;
642 n_pstate |= n_PS_ARGMOD_VPUT;
643 }else
644 cac.cac_vput = NULL;
646 rv = (*cdp->cd_func)(&cac);
647 if(a_go_xcall != NULL)
648 goto jret0;
649 } break;
651 default:
652 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
653 cdp->cd_caflags & n_CMD_ARG_TYPE_MASK); )
654 nerrn = n_ERR_NOTOBACCO;
655 nexn = 1;
656 goto jret0;
659 if(gecp->gec_hist_flags & a_GO_HIST_ADD){
660 if(cdp->cd_caflags & n_CMD_ARG_H)
661 gecp->gec_hist_flags = a_GO_HIST_NONE;
662 else if((cdp->cd_caflags & n_CMD_ARG_G) ||
663 (n_pstate & n_PS_MSGLIST_GABBY))
664 gecp->gec_hist_flags |= a_GO_HIST_GABBY;
667 if(rv != 0){
668 if(!(flags & a_NO_ERRNO)){
669 if(cdp->cd_caflags & n_CMD_ARG_EM)
670 flags |= a_NO_ERRNO;
671 else if((nerrn = n_err_no) == 0)
672 nerrn = n_ERR_INVAL;
673 }else
674 flags ^= a_NO_ERRNO;
675 }else if(cdp->cd_caflags & n_CMD_ARG_EM)
676 flags |= a_NO_ERRNO;
677 else
678 nerrn = n_ERR_NONE;
679 jleave:
680 nexn = rv;
682 if(flags & a_IGNERR){
683 n_pstate &= ~n_PS_ERR_EXIT_MASK;
684 n_exit_status = n_EXIT_OK;
685 }else if(rv != 0){
686 bool_t bo;
688 if((bo = ok_blook(batch_exit_on_error))){
689 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
690 if(!(n_poption & n_PO_BATCH_FLAG))
691 bo = FAL0;
693 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
694 n_pstate |= n_PS_ERR_QUIT;
695 else if(ok_blook(posix)){
696 if(n_psonce & n_PSO_STARTED)
697 rv = 0;
698 else if(!(n_psonce & n_PSO_INTERACTIVE))
699 n_pstate |= n_PS_ERR_XIT;
700 }else
701 rv = 0;
703 if(rv != 0){
704 if(n_exit_status == n_EXIT_OK)
705 n_exit_status = n_EXIT_ERR;
706 if((n_poption & n_PO_D_V) &&
707 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
708 n_alert(_("Non-interactive, bailing out due to errors "
709 "in startup load phase"));
710 goto jret;
714 if(cdp == NULL)
715 goto jret0;
716 if((cdp->cd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
717 if(visible(dot))
718 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
719 sizeof("\\type") -1);
721 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
722 !(cdp->cd_caflags & n_CMD_ARG_T))
723 n_pstate |= n_PS_SAW_COMMAND;
724 jret0:
725 rv = 0;
726 jret:
727 if(!(flags & a_NO_ERRNO))
728 n_pstate_err_no = nerrn;
729 n_pstate_ex_no = nexn;
730 NYD_LEAVE;
731 return (rv == 0);
734 static void
735 a_go_hangup(int s){
736 NYD_X; /* Signal handler */
737 n_UNUSED(s);
738 /* nothing to do? */
739 exit(n_EXIT_ERR);
742 static void
743 a_go_onintr(int s){ /* TODO block signals while acting */
744 NYD_X; /* Signal handler */
745 n_UNUSED(s);
747 safe_signal(SIGINT, a_go_onintr);
749 termios_state_reset();
751 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
753 if(interrupts != 1)
754 n_err_sighdl(_("Interrupt\n"));
755 safe_signal(SIGPIPE, a_go_oldpipe);
756 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
759 static void
760 a_go_cleanup(enum a_go_cleanup_mode gcm){
761 /* Signals blocked */
762 struct a_go_ctx *gcp;
763 NYD_ENTER;
765 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
766 hold_all_sigs();
767 jrestart:
768 gcp = a_go_ctx;
770 /* Free input injections of this level first */
771 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
772 struct a_go_input_inject **giipp, *giip;
774 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
775 *giipp = giip->gii_next;
776 n_free(giip);
780 /* Cleanup non-crucial external stuff */
781 n_COLOUR(
782 if(gcp->gc_data.gdc_colour != NULL)
783 n_colour_stack_del(NULL);
786 /* Work the actual context (according to cleanup mode) */
787 if(gcp->gc_outer == NULL){
788 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
789 if(a_go_xcall != NULL){
790 n_free(a_go_xcall);
791 a_go_xcall = NULL;
793 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
794 n_pstate &= ~n_PS_ERR_EXIT_MASK;
795 close_all_files();
796 }else{
797 if(!(n_pstate & n_PS_SOURCING))
798 close_all_files();
801 n_memory_reset();
803 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
804 assert(a_go_xcall == NULL);
805 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
806 assert(gcp->gc_on_finalize == NULL);
807 assert(gcp->gc_data.gdc_colour == NULL);
808 goto jxleave;
809 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
810 n_memory_reset();
811 goto jxleave;
812 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
813 n_stdin = gcp->gc_splice_stdin;
814 n_stdout = gcp->gc_splice_stdout;
815 n_psonce = gcp->gc_splice_psonce;
816 goto jstackpop;
819 /* Cleanup crucial external stuff */
820 if(gcp->gc_data.gdc_ifcond != NULL){
821 n_cnd_if_stack_del(gcp->gc_data.gdc_ifcond);
822 if(!(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
823 !(gcp->gc_flags & a_GO_FORCE_EOF) && a_go_xcall == NULL){
824 n_err(_("Unmatched `if' at end of %s %s\n"),
825 ((gcp->gc_flags & a_GO_MACRO
826 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
827 : _("`source'd file"))),
828 gcp->gc_name);
829 gcm |= a_GO_CLEANUP_ERROR;
833 /* Teardown context */
834 if(gcp->gc_flags & a_GO_MACRO){
835 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
836 char **lp;
838 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
839 n_free(*lp);
840 ++gcp->gc_loff;
842 /* Part of gcp's memory chunk, then */
843 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
844 n_free(gcp->gc_lines);
846 }else if(gcp->gc_flags & a_GO_PIPE)
847 /* XXX command manager should -TERM then -KILL instead of hoping
848 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
849 Pclose(gcp->gc_file, TRU1);
850 else if(gcp->gc_flags & a_GO_FILE)
851 Fclose(gcp->gc_file);
853 jstackpop:
854 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
855 if(gcp->gc_data.gdc_mempool != NULL)
856 n_memory_pool_pop(NULL);
857 }else
858 n_memory_reset();
860 n_go_data = &(a_go_ctx = gcp->gc_outer)->gc_data;
861 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
862 (a_GO_MACRO | a_GO_SUPER_MACRO)){
863 n_pstate &= ~n_PS_SOURCING;
864 assert(n_pstate & n_PS_ROBOT);
865 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
866 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
867 else
868 assert(n_pstate & n_PS_ROBOT);
870 if(gcp->gc_on_finalize != NULL)
871 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
873 if(gcm & a_GO_CLEANUP_ERROR){
874 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
875 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
876 goto jerr;
878 jleave:
879 if(gcp->gc_flags & a_GO_FREE)
880 n_free(gcp);
882 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
883 goto jrestart;
885 jxleave:
886 NYD_LEAVE;
887 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
888 rele_all_sigs();
889 return;
891 jerr:
892 /* With *posix* we follow what POSIX says:
893 * Any errors in the start-up file shall either cause mailx to
894 * terminate with a diagnostic message and a non-zero status or to
895 * continue after writing a diagnostic message, ignoring the
896 * remainder of the lines in the start-up file
897 * Print the diagnostic only for the outermost resource unless the user
898 * is debugging or in verbose mode */
899 if((n_poption & n_PO_D_V) ||
900 (!(n_psonce & n_PSO_STARTED) &&
901 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
902 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
903 /* I18N: file inclusion, macro etc. evaluation has been stopped */
904 n_alert(_("Stopped %s %s due to errors%s"),
905 (n_psonce & n_PSO_STARTED
906 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
907 : (gcp->gc_flags & a_GO_MACRO
908 ? (gcp->gc_flags & a_GO_MACRO_CMD
909 ? _("evaluating command") : _("evaluating macro"))
910 : (gcp->gc_flags & a_GO_PIPE
911 ? _("executing `source'd pipe")
912 : (gcp->gc_flags & a_GO_FILE
913 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
915 : (gcp->gc_flags & a_GO_MACRO
916 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
917 ? _("evaluating command line") : _("evaluating macro"))
918 : _("loading initialization resource"))),
919 gcp->gc_name,
920 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
921 goto jleave;
924 static bool_t
925 a_go_file(char const *file, bool_t silent_open_error){
926 struct a_go_ctx *gcp;
927 sigset_t osigmask;
928 size_t nlen;
929 char *nbuf;
930 bool_t ispipe;
931 FILE *fip;
932 NYD_ENTER;
934 fip = NULL;
936 /* Being a command argument file is space-trimmed *//* TODO v15 with
937 * TODO WYRALIST this is no longer necessary true, and for that we
938 * TODO don't set _PARSE_TRIM_SPACE because we cannot! -> cmd-tab.h!! */
939 #if 0
940 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
941 file[--nlen] == '|')))
942 #else
943 ispipe = FAL0;
944 if(!silent_open_error){
945 for(nlen = strlen(file); nlen > 0;){
946 char c;
948 c = file[--nlen];
949 if(!spacechar(c)){
950 if(c == '|'){
951 nbuf = savestrbuf(file, nlen);
952 ispipe = TRU1;
954 break;
958 #endif
960 if(ispipe){
961 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
962 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
963 goto jeopencheck;
964 }else if((nbuf = fexpand(file, FEXP_LOCAL)) == NULL)
965 goto jeopencheck;
966 else if((fip = Fopen(nbuf, "r")) == NULL){
967 jeopencheck:
968 if(!silent_open_error || (n_poption & n_PO_D_V))
969 n_perr(nbuf, 0);
970 if(silent_open_error)
971 fip = (FILE*)-1;
972 goto jleave;
975 sigprocmask(SIG_BLOCK, NULL, &osigmask);
977 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
978 (nlen = strlen(nbuf) +1));
979 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
981 hold_all_sigs();
983 gcp->gc_outer = a_go_ctx;
984 gcp->gc_osigmask = osigmask;
985 gcp->gc_file = fip;
986 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
987 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
988 memcpy(gcp->gc_name, nbuf, nlen);
990 a_go_ctx = gcp;
991 n_go_data = &gcp->gc_data;
992 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
993 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
994 fip = NULL;
995 jleave:
996 NYD_LEAVE;
997 return (fip != NULL);
1000 static bool_t
1001 a_go_load(struct a_go_ctx *gcp){
1002 NYD2_ENTER;
1004 assert(!(n_psonce & n_PSO_STARTED));
1005 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1007 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1008 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1010 hold_all_sigs();
1012 /* POSIX:
1013 * Any errors in the start-up file shall either cause mailx to terminate
1014 * with a diagnostic message and a non-zero status or to continue after
1015 * writing a diagnostic message, ignoring the remainder of the lines in
1016 * the start-up file. */
1017 gcp->gc_outer = a_go_ctx;
1018 a_go_ctx = gcp;
1019 n_go_data = &gcp->gc_data;
1020 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1021 n_pstate |= n_PS_ROBOT |
1022 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1024 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1026 rele_all_sigs();
1028 n_go_main_loop();
1029 NYD2_LEAVE;
1030 return (((n_psonce & n_PSO_EXIT_MASK) |
1031 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1034 static void
1035 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1036 NYD_X; /* Signal handler */
1037 n_UNUSED(sig);
1038 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1041 static bool_t
1042 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1043 sighandler_type soldhdl;
1044 struct a_go_eval_ctx gec;
1045 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1046 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1047 sigset_t osigmask;
1048 NYD2_ENTER;
1050 memset(&gec, 0, sizeof gec);
1051 osigmask = gcp->gc_osigmask;
1052 hadint = FAL0;
1053 f = a_RETOK;
1055 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1056 safe_signal(SIGINT, &a_go__eloopint);
1057 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1058 hold_all_sigs();
1059 hadint = TRU1;
1060 f &= ~a_RETOK;
1061 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1062 goto jjump;
1066 for(;; f |= a_TICKED){
1067 int n;
1069 if(f & a_TICKED)
1070 n_memory_reset();
1072 /* Read a line of commands and handle end of file specially */
1073 gec.gec_line.l = gec.gec_line_size;
1074 rele_all_sigs();
1075 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL, NULL);
1076 hold_all_sigs();
1077 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1078 gec.gec_line.l = (ui32_t)n;
1080 if(n < 0)
1081 break;
1083 rele_all_sigs();
1084 assert(gec.gec_hist_flags == a_GO_HIST_NONE);
1085 if(!a_go_evaluate(&gec))
1086 f &= ~a_RETOK;
1087 hold_all_sigs();
1089 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1090 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1091 break;
1094 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1095 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1096 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1097 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1099 if(gec.gec_line.s != NULL)
1100 n_free(gec.gec_line.s);
1102 if(soldhdl != SIG_IGN)
1103 safe_signal(SIGINT, soldhdl);
1104 NYD2_LEAVE;
1105 rele_all_sigs();
1106 if(hadint){
1107 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1108 n_raise(SIGINT);
1110 return (f & a_RETOK);
1113 FL void
1114 n_go_init(void){
1115 struct a_go_ctx *gcp;
1116 NYD2_ENTER;
1118 assert(n_stdin != NULL);
1120 gcp = (void*)a_go__mainctx_b.uf;
1121 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1122 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1123 gcp->gc_file = n_stdin;
1124 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1125 a_go_ctx = gcp;
1126 n_go_data = &gcp->gc_data;
1128 n_child_manager_start();
1129 NYD2_LEAVE;
1132 FL bool_t
1133 n_go_main_loop(void){ /* FIXME */
1134 struct a_go_eval_ctx gec;
1135 int n, eofcnt;
1136 bool_t volatile rv;
1137 NYD_ENTER;
1139 rv = TRU1;
1141 if (!(n_pstate & n_PS_SOURCING)) {
1142 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1143 safe_signal(SIGINT, &a_go_onintr);
1144 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1145 safe_signal(SIGHUP, &a_go_hangup);
1147 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1148 safe_signal(SIGPIPE, a_go_oldpipe);
1150 memset(&gec, 0, sizeof gec);
1152 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1153 hold_all_sigs();
1155 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1156 interrupts = 0;
1158 if(gec.gec_ever_seen)
1159 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1161 if (!(n_pstate & n_PS_SOURCING)) {
1162 char *cp;
1164 /* TODO Note: this buffer may contain a password. We should redefine
1165 * TODO the code flow which has to do that */
1166 if ((cp = termios_state.ts_linebuf) != NULL) {
1167 termios_state.ts_linebuf = NULL;
1168 termios_state.ts_linesize = 0;
1169 n_free(cp); /* TODO pool give-back */
1171 if (gec.gec_line.l > LINESIZE * 3) {
1172 n_free(gec.gec_line.s);
1173 gec.gec_line.s = NULL;
1174 gec.gec_line.l = gec.gec_line_size = 0;
1178 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1179 char *cp;
1181 if ((cp = ok_vlook(newmail)) != NULL) {
1182 struct stat st;
1184 /* FIXME TEST WITH NOPOLL ETC. !!! */
1185 n = (cp != NULL && strcmp(cp, "nopoll"));
1186 if ((mb.mb_type == MB_FILE && !stat(mailname, &st) &&
1187 st.st_size > mailsize) ||
1188 (mb.mb_type == MB_MAILDIR && n != 0)) {
1189 size_t odot = PTR2SIZE(dot - message);
1190 ui32_t odid = (n_pstate & n_PS_DID_PRINT_DOT);
1191 int i;
1193 rele_all_sigs();
1194 i = setfile(mailname,
1195 FEDIT_NEWMAIL |
1196 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY));
1197 hold_all_sigs();
1198 if(i < 0) {
1199 n_exit_status |= n_EXIT_ERR;
1200 rv = FAL0;
1201 break;
1203 dot = &message[odot];
1204 n_pstate |= odid;
1208 n_exit_status = n_EXIT_OK;
1211 /* Read a line of commands and handle end of file specially */
1212 gec.gec_line.l = gec.gec_line_size;
1213 /* C99 */{
1214 bool_t histadd;
1216 histadd = (!(n_pstate & n_PS_SOURCING) &&
1217 (n_psonce & n_PSO_INTERACTIVE));
1218 rele_all_sigs();
1219 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1220 &gec.gec_line.s, &gec.gec_line.l, NULL, &histadd);
1221 hold_all_sigs();
1223 gec.gec_hist_flags = histadd ? a_GO_HIST_ADD : a_GO_HIST_NONE;
1225 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1226 gec.gec_line.l = (ui32_t)n;
1228 if (n < 0) {
1229 if (!(n_pstate & n_PS_ROBOT) &&
1230 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1231 ++eofcnt < 4) {
1232 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1233 n_go_input_clearerr();
1234 continue;
1236 break;
1239 n_pstate &= ~n_PS_HOOK_MASK;
1240 rele_all_sigs();
1241 rv = a_go_evaluate(&gec);
1242 hold_all_sigs();
1244 if(gec.gec_hist_flags & a_GO_HIST_ADD){
1245 char const *cc, *ca;
1247 cc = gec.gec_hist_cmd;
1248 ca = gec.gec_hist_args;
1249 if(cc != NULL && ca != NULL)
1250 cc = savecatsep(cc, ' ', ca);
1251 else if(ca != NULL)
1252 cc = ca;
1253 n_tty_addhist(cc, ((gec.gec_hist_flags & a_GO_HIST_GABBY) != 0));
1256 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1257 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1258 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1259 default: break;
1261 if(n_psonce & n_PSO_EXIT_MASK)
1262 break;
1264 if(!rv)
1265 break;
1268 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1269 (rv ? 0 : a_GO_CLEANUP_ERROR));
1271 if (gec.gec_line.s != NULL)
1272 n_free(gec.gec_line.s);
1274 rele_all_sigs();
1275 NYD_LEAVE;
1276 return rv;
1279 FL void
1280 n_go_input_clearerr(void){
1281 FILE *fp;
1282 NYD2_ENTER;
1284 fp = NULL;
1286 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1287 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1288 fp = a_go_ctx->gc_file;
1290 if(fp != NULL){
1291 a_go_ctx->gc_flags &= ~a_GO_IS_EOF;
1292 clearerr(fp);
1294 NYD2_LEAVE;
1297 FL void
1298 n_go_input_force_eof(void){
1299 NYD2_ENTER;
1300 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1301 NYD2_LEAVE;
1304 FL bool_t
1305 n_go_input_is_eof(void){
1306 bool_t rv;
1307 NYD2_ENTER;
1309 rv = ((a_go_ctx->gc_flags & a_GO_IS_EOF) != 0);
1310 NYD2_LEAVE;
1311 return rv;
1314 FL void
1315 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1316 size_t len){
1317 NYD_ENTER;
1318 if(len == UIZ_MAX)
1319 len = strlen(buf);
1321 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1322 len > 0){
1323 struct a_go_input_inject *giip, **giipp;
1325 hold_all_sigs();
1327 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1328 ) + 1 + len +1);
1329 giipp = &a_go_ctx->gc_inject;
1330 giip->gii_next = *giipp;
1331 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1332 giip->gii_no_history = ((giif & n_GO_INPUT_INJECT_HISTORY) == 0);
1333 memcpy(&giip->gii_dat[0], buf, len);
1334 giip->gii_dat[giip->gii_len = len] = '\0';
1335 *giipp = giip;
1337 rele_all_sigs();
1339 NYD_LEAVE;
1342 FL int
1343 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1344 size_t *linesize, char const *string, bool_t *histok_or_null
1345 n_MEMORY_DEBUG_ARGS){
1346 /* TODO readline: linebuf pool!; n_go_input should return si64_t */
1347 struct n_string xprompt;
1348 FILE *ifile;
1349 bool_t doprompt, dotty;
1350 char const *iftype;
1351 struct a_go_input_inject *giip;
1352 int nold, n;
1353 bool_t histok;
1354 NYD2_ENTER;
1356 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1357 hold_all_sigs();
1359 histok = TRU1;
1361 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
1362 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1363 n = -1;
1364 goto jleave;
1367 if(gif & n_GO_INPUT_FORCE_STDIN)
1368 goto jforce_stdin;
1370 /* Special case macro mode: never need to prompt, lines have always been
1371 * unfolded already */
1372 if(a_go_ctx->gc_flags & a_GO_MACRO){
1373 if(*linebuf != NULL)
1374 n_free(*linebuf);
1376 /* Injection in progress? Don't care about the autocommit state here */
1377 if((giip = a_go_ctx->gc_inject) != NULL){
1378 a_go_ctx->gc_inject = giip->gii_next;
1380 /* Simply "reuse" allocation, copy string to front of it */
1381 jinject:
1382 histok = !giip->gii_no_history;
1383 *linesize = giip->gii_len;
1384 *linebuf = (char*)giip;
1385 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
1386 iftype = "INJECTION";
1387 }else{
1388 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
1389 *linesize = 0;
1390 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1391 n = -1;
1392 goto jleave;
1395 ++a_go_ctx->gc_loff;
1396 *linesize = strlen(*linebuf);
1397 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
1398 *linebuf = sbufdup(*linebuf, *linesize);
1400 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
1401 ? "-X OPTION"
1402 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
1403 histok = FAL0;
1405 n = (int)*linesize;
1406 n_pstate |= n_PS_READLINE_NL;
1407 goto jhave_dat;
1408 }else{
1409 /* Injection in progress? */
1410 struct a_go_input_inject **giipp;
1412 giipp = &a_go_ctx->gc_inject;
1414 if((giip = *giipp) != NULL){
1415 *giipp = giip->gii_next;
1417 if(giip->gii_commit){
1418 if(*linebuf != NULL)
1419 n_free(*linebuf);
1420 goto jinject; /* (above) */
1421 }else{
1422 string = savestrbuf(giip->gii_dat, giip->gii_len);
1423 n_free(giip);
1428 jforce_stdin:
1429 n_pstate &= ~n_PS_READLINE_NL;
1430 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
1431 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
1432 doprompt = ((n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
1433 (n_PSO_INTERACTIVE | n_PSO_STARTED) && !(n_pstate & n_PS_ROBOT));
1434 dotty = (doprompt && !ok_blook(line_editor_disable));
1435 if(!doprompt)
1436 gif |= n_GO_INPUT_PROMPT_NONE;
1437 else{
1438 if(!dotty)
1439 n_string_creat_auto(&xprompt);
1440 if(prompt == NULL)
1441 gif |= n_GO_INPUT_PROMPT_EVAL;
1444 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
1445 if(!dotty && (gif & n_GO_INPUT_PROMPT_NONE))
1446 fflush(n_stdout);
1448 ifile = (gif & n_GO_INPUT_FORCE_STDIN) ? n_stdin : a_go_ctx->gc_file;
1449 if(ifile == NULL){
1450 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
1451 (a_go_ctx->gc_flags & a_GO_MACRO));
1452 ifile = n_stdin;
1455 for(nold = n = 0;;){
1456 if(dotty){
1457 assert(ifile == n_stdin);
1458 if(string != NULL && (n = (int)strlen(string)) > 0){
1459 if(*linesize > 0)
1460 *linesize += n +1;
1461 else
1462 *linesize = (size_t)n + LINESIZE +1;
1463 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
1464 memcpy(*linebuf, string, (size_t)n +1);
1466 string = NULL;
1468 rele_all_sigs();
1470 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n, histok_or_null
1471 n_MEMORY_DEBUG_ARGSCALL);
1473 hold_all_sigs();
1474 }else{
1475 if(!(gif & n_GO_INPUT_PROMPT_NONE))
1476 n_tty_create_prompt(&xprompt, prompt, gif);
1478 rele_all_sigs();
1480 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
1481 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
1482 fflush(n_stdout);
1485 n = (readline_restart)(ifile, linebuf, linesize, n
1486 n_MEMORY_DEBUG_ARGSCALL);
1488 hold_all_sigs();
1490 if(n < 0 && feof(ifile))
1491 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1493 if(n > 0 && nold > 0){
1494 char const *cp;
1495 int i;
1497 i = 0;
1498 cp = &(*linebuf)[nold];
1499 while(spacechar(*cp) && n - i >= nold)
1500 ++cp, ++i;
1501 if(i > 0){
1502 memmove(&(*linebuf)[nold], cp, n - nold - i);
1503 n -= i;
1504 (*linebuf)[n] = '\0';
1509 if(n <= 0)
1510 break;
1512 /* POSIX says:
1513 * An unquoted <backslash> at the end of a command line shall
1514 * be discarded and the next line shall continue the command */
1515 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\'){
1516 if(dotty)
1517 n_pstate |= n_PS_READLINE_NL;
1518 break;
1520 /* Definitely outside of quotes, thus the quoting rules are so that an
1521 * uneven number of successive reverse solidus at EOL is a continuation */
1522 if(n > 1){
1523 size_t i, j;
1525 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
1526 if((*linebuf)[i] != '\\')
1527 break;
1528 if(!(j & 1))
1529 break;
1531 (*linebuf)[nold = --n] = '\0';
1532 gif |= n_GO_INPUT_NL_FOLLOW;
1535 if(n < 0)
1536 goto jleave;
1537 (*linebuf)[*linesize = n] = '\0';
1539 jhave_dat:
1540 if(n_poption & n_PO_D_VV)
1541 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
1542 jleave:
1543 if (n_pstate & n_PS_PSTATE_PENDMASK)
1544 a_go_update_pstate();
1546 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
1547 * TODO but only established from the outside and we need to drop this
1548 * TODO overlay context somehow */
1549 if(n < 0 && (a_go_ctx->gc_flags & a_GO_SPLICE))
1550 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
1552 if(histok_or_null != NULL && !histok)
1553 *histok_or_null = FAL0;
1555 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1556 rele_all_sigs();
1557 NYD2_LEAVE;
1558 return n;
1561 FL char *
1562 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
1563 char const *string){
1564 struct n_sigman sm;
1565 bool_t histadd;
1566 size_t linesize;
1567 char *linebuf, * volatile rv;
1568 int n;
1569 NYD2_ENTER;
1571 linesize = 0;
1572 linebuf = NULL;
1573 rv = NULL;
1575 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1576 case 0:
1577 break;
1578 default:
1579 goto jleave;
1582 histadd = TRU1;
1583 n = n_go_input(gif, prompt, &linebuf, &linesize, string, &histadd);
1584 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
1585 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE) &&
1586 histadd)
1587 n_tty_addhist(rv, ((gif & n_GO_INPUT_HIST_GABBY) != 0));
1589 n_sigman_cleanup_ping(&sm);
1590 jleave:
1591 if(linebuf != NULL)
1592 n_free(linebuf);
1593 NYD2_LEAVE;
1594 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1595 return rv;
1598 FL bool_t
1599 n_go_load(char const *name){
1600 struct a_go_ctx *gcp;
1601 size_t i;
1602 FILE *fip;
1603 bool_t rv;
1604 NYD_ENTER;
1606 rv = TRU1;
1608 if(name == NULL || *name == '\0')
1609 goto jleave;
1610 else if((fip = Fopen(name, "r")) == NULL){
1611 if(n_poption & n_PO_D_V)
1612 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
1613 goto jleave;
1616 i = strlen(name) +1;
1617 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
1618 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1620 gcp->gc_file = fip;
1621 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
1622 memcpy(gcp->gc_name, name, i);
1624 if(n_poption & n_PO_D_V)
1625 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
1626 rv = a_go_load(gcp);
1627 jleave:
1628 NYD_LEAVE;
1629 return rv;
1632 FL bool_t
1633 n_go_Xargs(char const **lines, size_t cnt){
1634 static char const name[] = "-X";
1636 union{
1637 bool_t rv;
1638 ui64_t align;
1639 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
1640 } b;
1641 char const *srcp, *xsrcp;
1642 char *cp;
1643 size_t imax, i, len;
1644 struct a_go_ctx *gcp;
1645 NYD_ENTER;
1647 gcp = (void*)b.uf;
1648 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1650 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
1651 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
1652 memcpy(gcp->gc_name, name, sizeof name);
1654 /* The problem being that we want to support reverse solidus newline
1655 * escaping also within multiline -X, i.e., POSIX says:
1656 * An unquoted <backslash> at the end of a command line shall
1657 * be discarded and the next line shall continue the command
1658 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
1659 * entire lines array and set _MACRO_FREE_DATA */
1660 imax = cnt + 1;
1661 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
1663 /* For each of the input lines.. */
1664 for(i = len = 0, cp = NULL; cnt > 0;){
1665 bool_t keep;
1666 size_t j;
1668 if((j = strlen(srcp = *lines)) == 0){
1669 ++lines, --cnt;
1670 continue;
1673 /* Separate one line from a possible multiline input string */
1674 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
1675 *lines = &xsrcp[1];
1676 j = PTR2SIZE(xsrcp - srcp);
1677 }else
1678 ++lines, --cnt;
1680 /* The (separated) string may itself indicate soft newline escaping */
1681 if((keep = (srcp[j - 1] == '\\'))){
1682 size_t xj, xk;
1684 /* Need an uneven number of reverse solidus */
1685 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
1686 if(srcp[xj] != '\\')
1687 break;
1688 if(xk & 1)
1689 --j;
1690 else
1691 keep = FAL0;
1694 /* Strip any leading WS from follow lines, then */
1695 if(cp != NULL)
1696 while(j > 0 && spacechar(*srcp))
1697 ++srcp, --j;
1699 if(j > 0){
1700 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
1701 imax += 4;
1702 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
1703 imax);
1705 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
1706 memcpy(&cp[len], srcp, j);
1707 cp[len += j] = '\0';
1709 if(!keep)
1710 ++i;
1712 if(!keep)
1713 cp = NULL, len = 0;
1715 if(cp != NULL){
1716 assert(i + 1 < imax);
1717 gcp->gc_lines[i++] = cp;
1719 gcp->gc_lines[i] = NULL;
1721 b.rv = a_go_load(gcp);
1722 NYD_LEAVE;
1723 return b.rv;
1726 FL int
1727 c_source(void *v){
1728 int rv;
1729 NYD_ENTER;
1731 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
1732 NYD_LEAVE;
1733 return rv;
1736 FL int
1737 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
1738 int rv;
1739 NYD_ENTER;
1741 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
1742 NYD_LEAVE;
1743 return rv;
1746 FL bool_t
1747 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
1748 void (*on_finalize)(void*), void *finalize_arg){
1749 struct a_go_ctx *gcp;
1750 size_t i;
1751 int rv;
1752 sigset_t osigmask;
1753 NYD_ENTER;
1755 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1757 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1758 (i = strlen(name) +1));
1759 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1761 hold_all_sigs();
1763 gcp->gc_outer = a_go_ctx;
1764 gcp->gc_osigmask = osigmask;
1765 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
1766 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1767 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
1768 gcp->gc_lines = lines;
1769 gcp->gc_on_finalize = on_finalize;
1770 gcp->gc_finalize_arg = finalize_arg;
1771 memcpy(gcp->gc_name, name, i);
1773 a_go_ctx = gcp;
1774 n_go_data = &gcp->gc_data;
1775 n_pstate |= n_PS_ROBOT;
1776 rv = a_go_event_loop(gcp, gif);
1778 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
1779 if(a_go_xcall != NULL){
1780 void *vp;
1781 struct n_cmd_arg_ctx *cacp;
1783 if(a_go_xcall == (void*)-1)
1784 a_go_xcall = NULL;
1785 else if(((void const*)(cacp = a_go_xcall)->cac_indat) == gcp){
1786 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
1787 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
1788 while(a_go_xcall != NULL){
1789 hold_all_sigs();
1791 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
1793 vp = a_go_xcall;
1794 a_go_xcall = NULL;
1795 cacp = n_cmd_arg_restore_from_heap(vp);
1796 n_free(vp);
1798 rele_all_sigs();
1800 (void)c_call(cacp);
1802 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) != 0);
1803 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1806 NYD_LEAVE;
1807 return rv;
1810 FL bool_t
1811 n_go_command(enum n_go_input_flags gif, char const *cmd){
1812 struct a_go_ctx *gcp;
1813 bool_t rv;
1814 size_t i, ial;
1815 sigset_t osigmask;
1816 NYD_ENTER;
1818 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1820 i = strlen(cmd) +1;
1821 ial = n_ALIGN(i);
1822 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1823 ial + 2*sizeof(char*));
1824 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1826 hold_all_sigs();
1828 gcp->gc_outer = a_go_ctx;
1829 gcp->gc_osigmask = osigmask;
1830 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
1831 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1832 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
1833 gcp->gc_lines = (void*)&gcp->gc_name[ial];
1834 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
1835 gcp->gc_lines[1] = NULL;
1837 a_go_ctx = gcp;
1838 n_go_data = &gcp->gc_data;
1839 n_pstate |= n_PS_ROBOT;
1840 rv = a_go_event_loop(gcp, gif);
1841 NYD_LEAVE;
1842 return rv;
1845 FL void
1846 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
1847 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
1848 struct a_go_ctx *gcp;
1849 size_t i;
1850 sigset_t osigmask;
1851 NYD_ENTER;
1853 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1855 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1856 (i = strlen(cmd) +1));
1857 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1859 hold_all_sigs();
1861 gcp->gc_outer = a_go_ctx;
1862 gcp->gc_osigmask = osigmask;
1863 gcp->gc_file = new_stdin;
1864 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE;
1865 gcp->gc_on_finalize = on_finalize;
1866 gcp->gc_finalize_arg = finalize_arg;
1867 gcp->gc_splice_stdin = n_stdin;
1868 gcp->gc_splice_stdout = n_stdout;
1869 gcp->gc_splice_psonce = n_psonce;
1870 memcpy(gcp->gc_name, cmd, i);
1872 n_stdin = new_stdin;
1873 n_stdout = new_stdout;
1874 n_psonce = new_psonce;
1875 a_go_ctx = gcp;
1876 n_pstate |= n_PS_ROBOT;
1878 rele_all_sigs();
1879 NYD_LEAVE;
1882 FL void
1883 n_go_splice_hack_remove_after_jump(void){
1884 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
1887 FL bool_t
1888 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
1889 struct a_go_ctx *gcp;
1890 bool_t rv;
1891 NYD2_ENTER;
1893 rv = FAL0;
1895 /* Only when startup completed */
1896 if(!(n_psonce & n_PSO_STARTED))
1897 goto jleave;
1898 /* Only interactive or batch mode (assuming that is ok) */
1899 if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
1900 goto jleave;
1902 /* Not when running any hook */
1903 if(n_pstate & n_PS_HOOK_MASK)
1904 goto jleave;
1906 /* Traverse up the stack:
1907 * . not when controlled by a child process
1908 * TODO . not when there are pipes involved, we neither handle job control,
1909 * TODO nor process groups, that is, controlling terminal acceptably
1910 * . not when sourcing a file */
1911 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
1912 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
1913 goto jleave;
1916 rv = TRU1;
1917 jleave:
1918 NYD2_LEAVE;
1919 return rv;
1922 FL int
1923 c_eval(void *vp){
1924 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
1925 * TODO ARGV with shell rules, but if that is not possible then simply
1926 * TODO adjust argv/argc of "the CmdCtx" that we will have "exec" real cmd */
1927 struct a_go_eval_ctx gec;
1928 struct n_string s_b, *sp;
1929 size_t i, j;
1930 char const **argv, *cp;
1931 NYD_ENTER;
1933 argv = vp;
1935 for(j = i = 0; (cp = argv[i]) != NULL; ++i)
1936 j += strlen(cp);
1938 sp = n_string_creat_auto(&s_b);
1939 sp = n_string_reserve(sp, j);
1941 for(i = 0; (cp = argv[i]) != NULL; ++i){
1942 if(i > 0)
1943 sp = n_string_push_c(sp, ' ');
1944 sp = n_string_push_cp(sp, cp);
1947 memset(&gec, 0, sizeof gec);
1948 gec.gec_line.s = n_string_cp(sp);
1949 gec.gec_line.l = sp->s_len;
1950 (void)/* XXX */a_go_evaluate(&gec);
1951 NYD_LEAVE;
1952 return (a_go_xcall != NULL ? 0 : n_pstate_ex_no);
1955 FL int
1956 c_xcall(void *vp){
1957 int rv;
1958 struct a_go_ctx *gcp;
1959 NYD2_ENTER;
1961 /* The context can only be a macro context, except that possibly a single
1962 * level of `eval' (TODO: yet) was used to double-expand our arguments */
1963 if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
1964 gcp = gcp->gc_outer;
1965 if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_CMD)) != a_GO_MACRO)
1966 goto jerr;
1968 /* Try to roll up the stack as much as possible.
1969 * See a_GO_XCALL_LOOP flag description for more */
1970 if(gcp->gc_outer != NULL){
1971 if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
1972 gcp = gcp->gc_outer;
1973 }else{
1974 /* Otherwise this macro is invoked from the top level, in which case we
1975 * silently act as if we were `call'... */
1976 rv = c_call(vp);
1977 /* ...which means we must ensure the rest of the macro that was us
1978 * doesn't become evaluated! */
1979 a_go_xcall = (void*)-1;
1980 goto jleave;
1983 /* C99 */{
1984 struct n_cmd_arg_ctx *cacp;
1986 cacp = n_cmd_arg_save_to_heap(vp);
1987 cacp->cac_indat = (char*)gcp;
1988 a_go_xcall = cacp;
1990 rv = 0;
1991 jleave:
1992 NYD2_LEAVE;
1993 return rv;
1994 jerr:
1995 n_err(_("`xcall': can only be used inside a macro\n"));
1996 n_pstate_err_no = n_ERR_INVAL;
1997 rv = 1;
1998 goto jleave;
2001 FL int
2002 c_exit(void *vp){
2003 NYD_ENTER;
2004 n_UNUSED(vp);
2005 n_psonce |= n_PSO_XIT;
2006 NYD_LEAVE;
2007 return 0;
2010 FL int
2011 c_quit(void *vp){
2012 NYD_ENTER;
2013 n_UNUSED(vp);
2014 n_psonce |= n_PSO_QUIT;
2015 NYD_LEAVE;
2016 return 0;
2019 /* s-it-mode */