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 - _PS_ERR_EXIT_* and _PSO_EXIT_* mixup is a mess: TERRIBLE!
4 *@ TODO - sigs_hold_all() most often on, especially robot mode: TERRIBLE!
5 *@ TODO - go_input(): with IO::Device we could have CStringListDevice, for
6 *@ TODO example to handle injections, and also `readctl' channels!
7 *@ TODO (Including sh(1)ell HERE strings and such.)
8 *
9 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
10 * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
11 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
12 */
13 /*
14 * Copyright (c) 1980, 1993
15 * The Regents of the University of California. All rights reserved.
16 *
17 * Redistribution and use in source and binary forms, with or without
18 * modification, are permitted provided that the following conditions
19 * are met:
20 * 1. Redistributions of source code must retain the above copyright
21 * notice, this list of conditions and the following disclaimer.
22 * 2. Redistributions in binary form must reproduce the above copyright
23 * notice, this list of conditions and the following disclaimer in the
24 * documentation and/or other materials provided with the distribution.
25 * 3. Neither the name of the University nor the names of its contributors
26 * may be used to endorse or promote products derived from this software
27 * without specific prior written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 * SUCH DAMAGE.
40 */
41 #undef su_FILE
42 #define su_FILE go
43 #define mx_SOURCE
44
45 #ifndef mx_HAVE_AMALGAMATION
46 # include "mx/nail.h"
47 #endif
48
49 #include <su/cs.h>
50 #include <su/cs-dict.h>
51 #include <su/icodec.h>
52 #include <su/mem.h>
53
54 #include "mx/child.h"
55 #include "mx/cmd.h"
56 #include "mx/cmd-commandalias.h"
57 #include "mx/colour.h"
58 #include "mx/dig-msg.h"
59 #include "mx/file-streams.h"
60 #include "mx/sigs.h"
61 #include "mx/termios.h"
62 #include "mx/tty.h"
63 #include "mx/ui-str.h"
64
65 /* TODO fake */
66 #include "su/code-in.h"
67
68 enum a_go_flags{
69 a_GO_NONE,
70 a_GO_FREE = 1u<<0, /* Structure was allocated, n_free() it */
71 a_GO_PIPE = 1u<<1, /* Open on a pipe */
72 a_GO_FILE = 1u<<2, /* Loading or sourcing a file */
73 a_GO_MACRO = 1u<<3, /* Running a macro */
74 a_GO_MACRO_FREE_DATA = 1u<<4, /* Lines are allocated, n_free() once done */
75 /* TODO For simplicity this is yet _MACRO plus specialization overlay
76 * TODO (_X_OPTION, _BLTIN_RC, _CMD) -- should be types on their own! */
77 a_GO_MACRO_X_OPTION = 1u<<5, /* Macro indeed command line -X option */
78 a_GO_MACRO_BLTIN_RC = 1u<<6, /* Macro indeed command line -:x option */
79 a_GO_MACRO_CMD = 1u<<7, /* Macro indeed single-line: ~:COMMAND */
80 /* TODO a_GO_SPLICE: the right way to support *on-compose-splice(-shell)?*
81 * TODO would be a command_loop object that emits an on_read_line event, and
82 * TODO have a special handler for the compose mode; with that, then,
83 * TODO _event_loop() would not call _evaluate() but CTX->on_read_line,
84 * TODO and _evaluate() would be the standard impl.,
85 * TODO whereas the COMMAND ESCAPE switch in collect.c would be another one.
86 * TODO With this generic accmacvar.c:temporary_compose_mode_hook_call()
87 * TODO could be dropped, and n_go_macro() could become extended,
88 * TODO and/or we would add a n_go_anything(), which would allow special
89 * TODO input handlers, special I/O input and output, special `localopts'
90 * TODO etc., to be glued to the new execution context. And all I/O all
91 * TODO over this software should not use stdin/stdout, but CTX->in/out.
92 * TODO The pstate must be a property of the current execution context, too.
93 * TODO This not today. :( For now we invent a special SPLICE execution
94 * TODO context overlay that at least allows to temporarily modify the
95 * TODO global pstate, and the global stdin and stdout pointers. HACK!
96 * TODO This splice thing is very special and has to go again. HACK!!
97 * TODO a_go_input() will drop it once it sees EOF (HACK!), but care for
98 * TODO jumps must be taken by splice creators. HACK!!! But works. ;} */
99 a_GO_SPLICE = 1u<<8,
100 /* If it is none of those, it must be the outermost, the global one */
101 a_GO_TYPE_MASK = a_GO_PIPE | a_GO_FILE | a_GO_MACRO |
102 /* a_GO_MACRO_X_OPTION | a_GO_MACRO_BLTIN_RC | a_GO_MACRO_CMD | */
103 a_GO_SPLICE,
104
105 a_GO_FORCE_EOF = 1u<<14, /* go_input() shall return EOF next */
106 a_GO_IS_EOF = 1u<<15,
107
108 a_GO_SUPER_MACRO = 1u<<16, /* *Not* inheriting n_PS_SOURCING state */
109 /* This context has inherited the memory bag from its parent.
110 * In practice only used for resource file loading and -X args, which enter
111 * a top level n_go_main_loop() and should (re)use the in practice already
112 * allocated memory bag of the global context.
113 * The bag memory is reset after use. */
114 a_GO_MEMBAG_INHERITED = 1u<<17,
115
116 /* This context has inherited the entire data context from its parent */
117 a_GO_DATACTX_INHERITED = 1u<<18,
118
119 a_GO_XCALL_IS_CALL = 1u<<24, /* n_GO_INPUT_NO_XCALL */
120 /* `xcall' optimization barrier: n_go_macro() has been finished with
121 * a `xcall' request, and `xcall' set this in the parent a_go_input of the
122 * said n_go_macro() to indicate a barrier: we teardown the a_go_input of
123 * the n_go_macro() away after leaving its _event_loop(), but then,
124 * back in n_go_macro(), that enters a for(;;) loop that directly calls
125 * c_call() -- our `xcall' stack avoidance optimization --, yet this call
126 * will itself end up in a new n_go_macro(), and if that again ends up with
127 * `xcall' this should teardown and leave its own n_go_macro(), unrolling
128 * the stack "up to the barrier level", but which effectively still is the
129 * n_go_macro() that lost its a_go_input and is looping the `xcall'
130 * optimization loop. If no `xcall' is desired that loop is simply left and
131 * the _event_loop() of the outer a_go_ctx will perform a loop tick and
132 * clear this bit again OR become teardown itself */
133 a_GO_XCALL_LOOP = 1u<<25, /* `xcall' optimization barrier level */
134 a_GO_XCALL_LOOP_ERROR = 1u<<26, /* .. state machine error transporter */
135 a_GO_XCALL_LOOP_MASK = a_GO_XCALL_LOOP | a_GO_XCALL_LOOP_ERROR
136 };
137
138 enum a_go_cleanup_mode{
139 a_GO_CLEANUP_UNWIND = 1u<<0, /* Teardown all ctxs except outermost */
140 a_GO_CLEANUP_TEARDOWN = 1u<<1, /* Teardown current context */
141 a_GO_CLEANUP_LOOPTICK = 1u<<2, /* Normal looptick cleanup */
142 a_GO_CLEANUP_MODE_MASK = su_BITENUM_MASK(0, 2),
143
144 a_GO_CLEANUP_ERROR = 1u<<8, /* Error occurred on level */
145 a_GO_CLEANUP_SIGINT = 1u<<9, /* Interrupt signal received */
146 a_GO_CLEANUP_HOLDALLSIGS = 1u<<10 /* sigs_all_hol() active TODO */
147 };
148
149 enum a_go_hist_flags{
150 a_GO_HIST_NONE = 0,
151 a_GO_HIST_ADD = 1u<<0,
152 a_GO_HIST_GABBY = 1u<<1,
153 a_GO_HIST_GABBY_ERROR = 1u<<2,
154 a_GO_HIST_INIT = 1u<<3
155 };
156
157 struct a_go_eval_ctx{
158 struct str gec_line; /* The terminated data to _evaluate() */
159 u32 gec_line_size; /* May be used to store line memory size */
160 boole gec_ever_seen; /* Has ever been used (main_loop() only) */
161 boole gec_ignerr; /* Implicit `ignerr' prefix */
162 u8 gec__dummy[1];
163 u8 gec_hist_flags; /* enum a_go_hist_flags */
164 char const *gec_hist_cmd; /* If a_GO_HIST_ADD only, cmd and args */
165 char const *gec_hist_args;
166 };
167
168 struct a_go_input_inject{
169 struct a_go_input_inject *gii_next;
170 uz gii_len;
171 boole gii_commit;
172 boole gii_no_history;
173 char gii_dat[VFIELD_SIZE(6)];
174 };
175
176 struct a_go_ctx{
177 struct a_go_ctx *gc_outer;
178 sigset_t gc_osigmask;
179 u32 gc_flags; /* enum a_go_flags */
180 u32 gc_loff; /* Pseudo (macro): index in .gc_lines */
181 char **gc_lines; /* Pseudo content, lines unfolded */
182 FILE *gc_file; /* File we were in, if applicable */
183 struct a_go_input_inject *gc_inject; /* To be consumed first */
184 void (*gc_on_finalize)(void *);
185 void *gc_finalize_arg;
186 sigjmp_buf gc_eloop_jmp; /* TODO one day... for _event_loop() */
187 /* SPLICE hacks: saved stdin/stdout, saved pstate */
188 FILE *gc_splice_stdin;
189 FILE *gc_splice_stdout;
190 u32 gc_splice_psonce;
191 u8 gc_splice__dummy[4];
192 struct n_go_data_ctx gc_data;
193 char gc_name[VFIELD_SIZE(0)]; /* Name of file or macro */
194 };
195
196 struct a_go_readctl_ctx{ /* TODO localize readctl_read_overlay: OnForkEvent! */
197 struct a_go_readctl_ctx *grc_last;
198 struct a_go_readctl_ctx *grc_next;
199 char const *grc_expand; /* If filename based, expanded string */
200 FILE *grc_fp;
201 s32 grc_fd; /* Based upon file-descriptor */
202 char grc_name[VFIELD_SIZE(4)]; /* User input for identification purposes */
203 };
204
205 static char const * const a_go_bltin_rc_lines[] = {
206 #include "gen-bltin-rc.h" /* */
207 };
208
209 static n_sighdl_t a_go_oldpipe;
210
211 /* Our current execution context, and the buffer backing the outermost level */
212 static struct a_go_ctx *a_go_ctx;
213
214 #define a_GO_MAINCTX_NAME "top level/main loop"
215 static union{
216 u64 align;
217 char uf[VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
218 sizeof(a_GO_MAINCTX_NAME)];
219 } a_go__mainctx_b;
220
221 /* `xcall' stack-avoidance bypass optimization. This actually is
222 * a cmd_arg_save_to_heap() buffer with cmd_arg_ctx.cac_indat misused to
223 * point to the a_go_ctx to unroll up to */
224 static void *a_go_xcall;
225
226 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
227
228 /* n_PS_STATE_PENDMASK requires some actions */
229 static void a_go_update_pstate(void);
230
231 /* Evaluate a single command */
232 static boole a_go_evaluate(struct a_go_eval_ctx *gecp);
233
234 /* Branch here on hangup signal and simulate "exit" */
235 static void a_go_hangup(int s);
236
237 /* The following gets called on receipt of an interrupt */
238 static void a_go_onintr(int s);
239
240 /* Cleanup current execution context, update the program state.
241 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
242 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we sigs_all_hold() */
243 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
244
245 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
246 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
247 * upon evaluation error, and TRU1 on success */
248 static boole a_go_file(char const *file, boole silent_open_error);
249
250 /* System resource file load()ing or -X command line option array traversal */
251 static boole a_go_load(struct a_go_ctx *gcp);
252
253 /* A simplified command loop for recursed state machines */
254 static boole a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
255
256 static void
a_go_update_pstate(void)257 a_go_update_pstate(void){
258 boole act;
259 NYD_IN;
260
261 act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
262 n_pstate &= ~n_PS_PSTATE_PENDMASK;
263
264 if(act){
265 char buf[32];
266
267 snprintf(buf, sizeof buf, "%u", mx_termios_dimen.tiosd_real_width);
268 ok_vset(COLUMNS, buf);
269 snprintf(buf, sizeof buf, "%u", mx_termios_dimen.tiosd_real_height);
270 ok_vset(LINES, buf);
271 }
272 NYD_OU;
273 }
274
275 static boole
a_go_evaluate(struct a_go_eval_ctx * gecp)276 a_go_evaluate(struct a_go_eval_ctx *gecp){
277 /* TODO old style(9), but also old code */
278 /* TODO a_go_evaluate() should be split in multiple subfunctions,
279 * TODO `eval' should be a prefix, etc., a cmd_ctx should be passed along;
280 * TODO this also affects history handling, as below! etc etc */
281 struct str line;
282 struct n_string s_b, *s;
283 char _wordbuf[2], *argv_stack[3], **argv_base, **argvp, *vput, *cp, *word;
284 char const *alias_name, *emsg;
285 struct mx_cmd_desc const *cdp;
286 s32 nerrn, nexn; /* TODO n_pstate_ex_no -> s64! */
287 int rv, c;
288 enum{
289 a_NONE = 0,
290 a_ALIAS_MASK = su_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
291 a_NOPREFIX = 1u<<4, /* Modifier prefix not allowed right now */
292 a_NOALIAS = 1u<<5, /* "No alias!" expansion modifier */
293 a_IGNERR = 1u<<6, /* ignerr modifier prefix */
294 a_LOCAL = 1u<<7, /* local modifier prefix */
295 a_SCOPE = 1u<<8, /* TODO scope modifier prefix */
296 a_U = 1u<<9, /* TODO UTF-8 modifier prefix */
297 a_VPUT = 1u<<10, /* vput modifier prefix */
298 a_WYSH = 1u<<11, /* XXX v15+ drop wysh modifier prefix */
299 a_MODE_MASK = su_BITENUM_MASK(5, 11),
300 a_NO_ERRNO = 1u<<16, /* Don't set n_pstate_err_no */
301 a_IS_SKIP = 1u<<17, /* Conditional active, is skipping */
302 a_IS_EMPTY = 1u<<18 /* The empty command */
303 } flags;
304 NYD_IN;
305
306 if(!(n_psonce & n_PSO_EXIT_MASK) && !(n_pstate & n_PS_ERR_EXIT_MASK))
307 n_exit_status = n_EXIT_OK;
308
309 flags = ((n_cnd_if_exists() == TRUM1 ? a_IS_SKIP : a_NONE) |
310 (gecp->gec_ignerr ? a_IGNERR : a_NONE));
311 rv = 1;
312 nerrn = su_ERR_NONE;
313 nexn = n_EXIT_OK;
314 cdp = NULL;
315 vput = NULL;
316 alias_name = NULL;
317 line = gecp->gec_line; /* TODO const-ify original (buffer)! */
318 ASSERT(line.s[line.l] == '\0');
319
320 if(line.l > 0 && su_cs_is_space(line.s[0]))
321 gecp->gec_hist_flags = a_GO_HIST_NONE;
322 else if(gecp->gec_hist_flags & a_GO_HIST_ADD)
323 gecp->gec_hist_cmd = gecp->gec_hist_args = NULL;
324 s = NULL;
325
326 /* Aliases that refer to shell commands or macro expansion restart */
327 jrestart:
328 if(n_str_trim_ifs(&line, TRU1)->l == 0){
329 line.s[0] = '\0';
330 flags |= a_IS_EMPTY;
331 cdp = mx_cmd_default();
332 gecp->gec_hist_flags = a_GO_HIST_NONE;
333 goto jexec;
334 }
335 (cp = line.s)[line.l] = '\0';
336
337 /* No-expansion modifier? */
338 if(!(flags & a_NOPREFIX) && *cp == '\\'){
339 line.s = ++cp;
340 --line.l;
341 flags |= a_NOALIAS;
342 }
343
344 /* Note: adding more special treatments must be reflected in the `help' etc.
345 * output in cmd.c! */
346
347 /* Ignore null commands (comments) */
348 if(*cp == '#'){
349 gecp->gec_hist_flags = a_GO_HIST_NONE;
350 goto jret0;
351 }
352
353 /* Handle ! differently to get the correct lexical conventions */
354 if(*cp == '!')
355 ++cp;
356 /* Isolate the actual command; since it may not necessarily be
357 * separated from the arguments (as in `p1') we need to duplicate it to
358 * be able to create a NUL terminated version.
359 * We must be aware of several special one letter commands here */
360 else if((cp = n_UNCONST(mx_cmd_isolate_name(cp))) == line.s &&
361 (*cp == '|' || *cp == '?'))
362 ++cp;
363 c = (int)P2UZ(cp - line.s);
364 word = UCMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
365 su_mem_copy(word, line.s, c);
366 word[c] = '\0';
367 line.l -= c;
368 line.s = cp;
369
370 /* It may be a modifier.
371 * NOTE: changing modifiers must be reflected in cmd_is_valid_name() */
372 switch(c){
373 default:
374 break;
375 case sizeof("ignerr") -1:
376 if(!su_cs_cmp_case(word, "ignerr")){
377 flags |= a_NOPREFIX | a_IGNERR;
378 goto jrestart;
379 }
380 break;
381 /*case sizeof("scope") -1:*/
382 case sizeof("local") -1:
383 if(!su_cs_cmp_case(word, "local")){
384 flags |= a_NOPREFIX | a_LOCAL;
385 goto jrestart;
386 }else if(!su_cs_cmp_case(word, "scope")){
387 /* This will be an extended per-command `localopts' */
388 n_err(_("Ignoring yet unused `scope' command modifier!"));
389 flags |= a_NOPREFIX | a_SCOPE;
390 goto jrestart;
391 }
392 break;
393 case sizeof("u") -1:
394 if(!su_cs_cmp_case(word, "u")){
395 n_err(_("Ignoring yet unused `u' command modifier!"));
396 flags |= a_NOPREFIX | a_U;
397 goto jrestart;
398 }
399 break;
400 /*case sizeof("vput") -1:*/
401 case sizeof("wysh") -1:
402 if(!su_cs_cmp_case(word, "wysh")){
403 flags |= a_NOPREFIX | a_WYSH;
404 goto jrestart;
405 }else if(!su_cs_cmp_case(word, "vput")){
406 flags |= a_NOPREFIX | a_VPUT;
407 goto jrestart;
408 }
409 break;
410 }
411
412 /* We need to trim for a possible history entry, but do it anyway and insert
413 * a space for argument separation in case of alias expansion. Also, do
414 * terminate again because nothing prevents aliases from introducing WS */
415 n_str_trim_ifs(&line, TRU1);
416 line.s[line.l] = '\0';
417
418 /* Lengthy history entry setup, possibly even redundant. But having
419 * normalized history entries is a good thing, and this is maybe still
420 * cheaper than parsing a StrList of words per se
421 * TODO In v15 the history entry will be deduced from the argument vector,
422 * TODO possibly modified by the command itself, i.e., from the cmd_ctx
423 * TODO structure which is passed along. And only if we have to do it */
424 if((gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
425 ) == a_GO_HIST_ADD){
426 if(line.l > 0){
427 s = n_string_creat_auto(&s_b);
428 s = n_string_assign_buf(s, line.s, line.l);
429 gecp->gec_hist_args = n_string_cp(s);
430 /* n_string_gut(n_string_drop_ownership(s)); */
431 }
432
433 s = n_string_creat_auto(&s_b);
434 s = n_string_reserve(s, 32);
435
436 if(flags & a_NOALIAS)
437 s = n_string_push_c(s, '\\');
438 if(flags & a_IGNERR)
439 s = n_string_push_buf(s, "ignerr ", sizeof("ignerr ") -1);
440 if(flags & a_WYSH)
441 s = n_string_push_buf(s, "wysh ", sizeof("wysh ") -1);
442 if(flags & a_VPUT)
443 s = n_string_push_buf(s, "vput ", sizeof("vput ") -1);
444 gecp->gec_hist_flags = a_GO_HIST_ADD | a_GO_HIST_INIT;
445 }
446
447 /* Look up the command; if not found, bitch. An empty cmd maps to the first
448 * command table entry.. */
449 if(*word == '\0'){
450 flags |= a_IS_EMPTY;
451 cdp = mx_cmd_default();
452 goto jexec;
453 }
454
455 /* Can we expand an alias from what we have? */
456 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
457 char const *alias_exp;
458 u8 expcnt;
459
460 expcnt = (flags & a_ALIAS_MASK);
461 ++expcnt;
462 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
463
464 /* Avoid self-recursion; since a commandalias can shadow a command of
465 * equal name allow one level of expansion to return an equal result:
466 * "commandalias q q;commandalias x q;x" should be "x->q->q->quit".
467 * P.S.: should also work for "help x" ... */
468 if(alias_name != NULL && !su_cs_cmp(word, alias_name))
469 flags |= a_NOALIAS;
470
471 if((alias_name = mx_commandalias_exists(word, &alias_exp)) != NULL){
472 uz i;
473
474 if(s != NULL){
475 s = n_string_push_cp(s, word);
476 gecp->gec_hist_cmd = n_string_cp(s);
477 s = NULL;
478 }
479
480 /* And join arguments onto alias expansion */
481 alias_name = word;
482 i = strlen(alias_exp);
483 cp = line.s;
484 line.s = n_autorec_alloc(i + 1 + line.l +1);
485 su_mem_copy(line.s, alias_exp, i);
486 if(line.l > 0){
487 line.s[i++] = ' ';
488 su_mem_copy(&line.s[i], cp, line.l);
489 }
490 line.s[i += line.l] = '\0';
491 line.l = i;
492 goto jrestart;
493 }
494 }
495
496 if((cdp = mx_cmd_firstfit(word)) == NIL){
497 if(!(flags & a_IS_SKIP) || (n_poption & n_PO_D_V))
498 n_err(_("%s: unknown command%s\n"),
499 prstr(word), ((flags & a_IS_SKIP)
500 ? _(" (ignored due to `if' condition)") : su_empty));
501 gecp->gec_hist_flags = a_GO_HIST_NONE;
502 if(flags & a_IS_SKIP)
503 goto jret0;
504 nerrn = su_ERR_NOSYS;
505 goto jleave;
506 }
507
508 jexec:
509 /* The default command is not executed in a macro or when sourcing, when
510 * having expanded an alias etc. To be able to deal with ";reply;~." we
511 * need to perform the shell expansion anyway, however */
512 if(UNLIKELY(flags & a_IS_EMPTY) &&
513 ((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
514 alias_name != NIL))
515 goto jwhite;
516
517 /* See if we should execute the command -- if a conditional we always
518 * execute it, otherwise, check the state of cond.
519 * To allow "if 0; echo no; else; echo yes;end" we need to be able to
520 * perform input line sequentiation / rest injection even in whiteout
521 * situations. See if we can do that. */
522 if(UNLIKELY(flags & a_IS_SKIP) && !(cdp->cd_caflags & mx_CMD_ARG_F)){
523 jwhite:
524 gecp->gec_hist_flags = a_GO_HIST_NONE;
525
526 switch(cdp->cd_caflags & mx_CMD_ARG_TYPE_MASK){
527 case mx_CMD_ARG_TYPE_WYRA:{
528 char const *v15compat;
529
530 if((v15compat = ok_vlook(v15_compat)) == su_NIL ||
531 *v15compat == '\0')
532 break;
533 }
534 /* FALLTHRU */
535 case mx_CMD_ARG_TYPE_MSGLIST:
536 case mx_CMD_ARG_TYPE_NDMLIST:
537 case mx_CMD_ARG_TYPE_WYSH:
538 case mx_CMD_ARG_TYPE_ARG:{
539 boole once;
540
541 emsg = line.s;
542 for(once = FAL0, s = n_string_creat_auto(&s_b);; once = TRU1){
543 su_u32 shs;
544
545 shs = n_shexp_parse_token((n_SHEXP_PARSE_META_SEMICOLON |
546 n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_TRIM_SPACE |
547 n_SHEXP_PARSE_TRIM_IFSSPACE), s, &line,
548 NULL);
549 if(!once && (flags & a_IS_EMPTY) && s->s_len != 0)
550 n_err(_("The empty (default) command is ignored here, "
551 "but has arguments: %s\n"), emsg);
552 if(line.l == 0)
553 break;
554 if(shs & n_SHEXP_STATE_META_SEMICOLON){
555 ASSERT(shs & n_SHEXP_STATE_STOP);
556 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, line.s, line.l);
557 break;
558 }
559 }
560 }break;
561 case mx_CMD_ARG_TYPE_RAWDAT:
562 case mx_CMD_ARG_TYPE_STRING:
563 case mx_CMD_ARG_TYPE_RAWLIST:
564 break;
565 }
566 goto jret0;
567 }
568
569 if(s != NIL && gecp->gec_hist_flags != a_GO_HIST_NONE){
570 s = n_string_push_cp(s, cdp->cd_name);
571 gecp->gec_hist_cmd = n_string_cp(s);
572 /* n_string_gut(n_string_drop_ownership(s)); */
573 s = NIL;
574 }
575
576 nerrn = su_ERR_INVAL;
577
578 /* Process the arguments to the command, depending on the type it expects */
579 UNINIT(emsg, NIL);
580 if((cdp->cd_caflags & mx_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
581 !(n_poption & n_PO_BATCH_FLAG)){
582 emsg = N_("%s: can only be used batch or interactive mode\n");
583 goto jeflags;
584 }
585 if(!(cdp->cd_caflags & mx_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
586 emsg = N_("%s: cannot be used while sending\n");
587 goto jeflags;
588 }
589 if(cdp->cd_caflags & mx_CMD_ARG_R){
590 if(n_pstate & n_PS_COMPOSE_MODE){
591 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
592 emsg = N_("%s: cannot be used in compose mode\n");
593 goto jeflags;
594 }
595 /* TODO Nothing should prevent mx_CMD_ARG_R in conjunction with
596 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
597 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING) && !n_go_may_yield_control()){
598 emsg = N_("%s: cannot be used in this program state\n");
599 goto jeflags;
600 }
601 }
602 if((cdp->cd_caflags & mx_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED_CONFIG)){
603 emsg = N_("%s: cannot be used during startup\n");
604 goto jeflags;
605 }
606 if(!(cdp->cd_caflags & mx_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
607 emsg = N_("%s: cannot be used in a hook running in a child process\n");
608 goto jeflags;
609 }
610
611 if((cdp->cd_caflags & mx_CMD_ARG_A) && mb.mb_type == MB_VOID){
612 emsg = N_("%s: needs an active mailbox\n");
613 goto jeflags;
614 }
615 if((cdp->cd_caflags & mx_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
616 emsg = N_("%s: cannot be used in read-only mailbox\n");
617 jeflags:
618 n_err(V_(emsg), cdp->cd_name);
619 goto jleave;
620 }
621
622 if((cdp->cd_caflags & mx_CMD_ARG_O) && /* XXX Remove! -> within command! */
623 !su_state_has(su_STATE_REPRODUCIBLE)){
624 static struct su_cs_dict a_go__obsol, *a_go_obsol;
625
626 if(UNLIKELY(a_go_obsol == NIL)) /* XXX atexit cleanup */
627 a_go_obsol = su_cs_dict_set_treshold_shift(
628 su_cs_dict_create(&a_go__obsol, (su_CS_DICT_POW2_SPACED |
629 su_CS_DICT_HEAD_RESORT | su_CS_DICT_ERR_PASS), NIL), 2);
630
631 if(UNLIKELY(!su_cs_dict_has_key(a_go_obsol, cdp->cd_name))){
632 su_cs_dict_insert(a_go_obsol, cdp->cd_name, NIL);
633 n_err(_("Obsoletion warning: command will be removed: %s\n"),
634 cdp->cd_name);
635 }
636 }
637
638 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
639 * TODO doesn't use any of those list commands which strip this mask,
640 * TODO and for now we misuse bits for checking relation to history;
641 * TODO argument state should be property of a per-cmd carrier instead */
642 n_pstate &= ~n_PS_ARGLIST_MASK;
643
644 if(flags & a_WYSH){
645 switch(cdp->cd_caflags & mx_CMD_ARG_TYPE_MASK){
646 case mx_CMD_ARG_TYPE_MSGLIST:
647 case mx_CMD_ARG_TYPE_NDMLIST:
648 case mx_CMD_ARG_TYPE_WYSH:
649 case mx_CMD_ARG_TYPE_ARG:
650 n_OBSOLETE2(cdp->cd_name, _("`wysh' modifier redundant/needless"));
651 flags ^= a_WYSH;
652 /* FALLTHRU */
653 case mx_CMD_ARG_TYPE_WYRA:
654 break;
655 case mx_CMD_ARG_TYPE_RAWDAT:
656 case mx_CMD_ARG_TYPE_STRING:
657 case mx_CMD_ARG_TYPE_RAWLIST:
658 n_err(_("%s: wysh: command modifier not supported\n"), cdp->cd_name);
659 goto jleave;
660 }
661 }
662
663 if(flags & a_LOCAL){
664 /* TODO a_LOCAL should affect !CMD_ARG_L commands if `vput' is used!! */
665 if(!(cdp->cd_caflags & mx_CMD_ARG_L)){
666 emsg = N_("%s: local: command modifier not supported\n");
667 goto jeflags; /* above */
668 }
669 flags |= a_WYSH;
670 n_pstate |= n_PS_ARGMOD_LOCAL; /* TODO YET useless since stripped later
671 * TODO on in getrawlist() etc., i.e., the argument vector producers,
672 * TODO therefore yet needs to be set again based on flags&a_LOCAL! */
673 }
674
675 if(flags & a_VPUT){
676 if(cdp->cd_caflags & mx_CMD_ARG_V){
677 emsg = line.s; /* xxx Cannot pass &char* as char const**, so no cp */
678 vput = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIM_SPACE |
679 n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_LOG |
680 n_SHEXP_PARSE_META_SEMICOLON | n_SHEXP_PARSE_META_KEEP), &emsg);
681 line.l -= P2UZ(emsg - line.s);
682 line.s = n_UNCONST(emsg);
683 if(emsg == NULL)
684 emsg = N_("could not parse input token");
685 else if(!n_shexp_is_valid_varname(vput, FAL0))
686 emsg = N_("not a valid variable name");
687 else if(!n_var_is_user_writable(vput))
688 emsg = N_("either not a user writable, or a boolean variable");
689 else
690 emsg = NULL;
691 if(emsg != NULL){
692 n_err("%s: vput: %s: %s\n",
693 cdp->cd_name, V_(emsg), n_shexp_quote_cp(vput, FAL0));
694 nerrn = su_ERR_NOTSUP;
695 rv = -1;
696 goto jleave;
697 }
698 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
699 * TODO on in getrawlist() etc., i.e., the argument vector producers,
700 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
701 }else{
702 n_err(_("%s: %s: wysh: command modifier not supported\n"),
703 n_ERROR, cdp->cd_name);
704 mx_cmd_print_synopsis(cdp, NIL);
705 flags &= ~a_VPUT;
706 }
707 }
708
709 if(n_poption & n_PO_D_VV)
710 n_err(_("COMMAND <%s> %s\n"), cdp->cd_name, line.s);
711
712 switch(cdp->cd_caflags & mx_CMD_ARG_TYPE_MASK){
713 case mx_CMD_ARG_TYPE_MSGLIST:
714 /* Message list defaulting to nearest forward legal message */
715 if(n_msgvec == NULL)
716 goto jmsglist_err;
717 if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_mflags_o_minargs, NULL)
718 ) < 0){
719 nerrn = su_ERR_NOMSG;
720 flags |= a_NO_ERRNO;
721 break;
722 }
723 if(c == 0){
724 if((n_msgvec[0] = first(cdp->cd_mflags_o_minargs,
725 cdp->cd_mmask_o_maxargs)) != 0){
726 c = 1;
727 n_msgmark1 = &message[n_msgvec[0] - 1];
728 }else{
729 jmsglist_err:
730 if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
731 (n_poption & n_PO_D_V))
732 n_err(_("No applicable messages\n"));
733 nerrn = su_ERR_NOMSG;
734 /* flags |= a_NO_ERRNO;*/
735 break;
736 }
737 }
738 jmsglist_go:
739 /* C99 */{
740 int *mvp;
741
742 mvp = n_autorec_calloc(c +1, sizeof *mvp);
743 while(c-- > 0)
744 mvp[c] = n_msgvec[c];
745 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM))/*XXX*/
746 su_err_set_no(su_ERR_NONE);
747 rv = (*cdp->cd_func)(mvp);
748 }
749 break;
750
751 case mx_CMD_ARG_TYPE_NDMLIST:
752 /* Message list with no defaults, but no error if none exist */
753 if(n_msgvec == NULL)
754 goto jmsglist_err;
755 if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_mflags_o_minargs, NIL)
756 ) < 0){
757 nerrn = su_ERR_NOMSG;
758 flags |= a_NO_ERRNO;
759 break;
760 }
761 goto jmsglist_go;
762
763 case mx_CMD_ARG_TYPE_STRING:
764 /* Just the straight string, old style, with leading blanks removed */
765 for(cp = line.s; su_cs_is_space(*cp);)
766 ++cp;
767 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM)) /* XXX */
768 su_err_set_no(su_ERR_NONE);
769 rv = (*cdp->cd_func)(cp);
770 break;
771
772 case mx_CMD_ARG_TYPE_RAWDAT:
773 /* Just the straight string, placed in argv[] */
774 argvp = argv_stack;
775 if(flags & a_VPUT)
776 *argvp++ = vput;
777 *argvp++ = line.s;
778 *argvp = NULL;
779 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM)) /* XXX */
780 su_err_set_no(su_ERR_NONE);
781 rv = (*cdp->cd_func)(argv_stack);
782 break;
783
784 case mx_CMD_ARG_TYPE_WYSH:
785 c = 1;
786 if(0){
787 /* FALLTHRU */
788 case mx_CMD_ARG_TYPE_WYRA:
789 /* C99 */{
790 char const *v15compat;
791
792 if((v15compat = ok_vlook(v15_compat)) != su_NIL &&
793 *v15compat != '\0')
794 flags |= a_WYSH;
795 }
796 c = (flags & a_WYSH) ? 1 : 0;
797 if(0){
798 case mx_CMD_ARG_TYPE_RAWLIST:
799 c = 0;
800 }
801 }
802 argvp = argv_base = n_autorec_alloc(sizeof(*argv_base) * n_MAXARGC);
803 if(flags & a_VPUT)
804 *argvp++ = vput;
805 if((c = getrawlist((c != 0), argvp,
806 (n_MAXARGC - ((flags & a_VPUT) != 0)), line.s, line.l)) < 0){
807 n_err(_("%s: invalid argument list\n"), cdp->cd_name);
808 flags |= a_NO_ERRNO;
809 break;
810 }
811
812 if(UCMP(32, c, <, cdp->cd_mflags_o_minargs) ||
813 UCMP(32, c, >, cdp->cd_mmask_o_maxargs)){
814 n_err(_("%s: %s: takes at least %u, and no more than %u arg(s)\n"),
815 n_ERROR, cdp->cd_name, S(u32,cdp->cd_mflags_o_minargs),
816 S(u32,cdp->cd_mmask_o_maxargs));
817 mx_cmd_print_synopsis(cdp, NIL);
818 flags |= a_NO_ERRNO;
819 break;
820 }
821
822 if(flags & a_LOCAL)
823 n_pstate |= n_PS_ARGMOD_LOCAL;
824 if(flags & a_VPUT)
825 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO due to getrawlist(), as above */
826 if(flags & a_WYSH)
827 n_pstate |= n_PS_ARGMOD_WYSH;
828
829 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM)) /* XXX */
830 su_err_set_no(su_ERR_NONE);
831 rv = (*cdp->cd_func)(argv_base);
832 if(a_go_xcall != NULL)
833 goto jret0;
834 break;
835
836 case mx_CMD_ARG_TYPE_ARG:{
837 /* TODO The _ARG_TYPE_ARG is preliminary, in the end we should have a
838 * TODO per command-ctx carrier that also has slots for it arguments,
839 * TODO and that should be passed along all the way. No more arglists
840 * TODO here, etc. */
841 struct mx_cmd_arg_ctx cac;
842
843 cac.cac_desc = cdp->cd_cadp;
844 cac.cac_indat = line.s;
845 cac.cac_inlen = line.l;
846 cac.cac_msgflag = cdp->cd_mflags_o_minargs;
847 cac.cac_msgmask = cdp->cd_mmask_o_maxargs;
848 if(!mx_cmd_arg_parse(&cac)){
849 flags |= a_NO_ERRNO;
850 break;
851 }
852
853 if(flags & a_VPUT){
854 cac.cac_vput = vput;
855 /* Global "hack" not used: n_pstate |= n_PS_ARGMOD_VPUT; */
856 }else
857 cac.cac_vput = NULL;
858
859 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & mx_CMD_ARG_EM)) /* XXX */
860 su_err_set_no(su_ERR_NONE);
861 rv = (*cdp->cd_func)(&cac);
862 if(a_go_xcall != NULL)
863 goto jret0;
864 }break;
865
866 default:
867 su_DBG( n_panic(_("Implementation error: unknown argument type: %d"),
868 cdp->cd_caflags & mx_CMD_ARG_TYPE_MASK); )
869 nerrn = su_ERR_NOTOBACCO;
870 nexn = 1;
871 goto jret0;
872 }
873
874 if(gecp->gec_hist_flags & a_GO_HIST_ADD){
875 if(cdp->cd_caflags & mx_CMD_ARG_H)
876 gecp->gec_hist_flags = a_GO_HIST_NONE;
877 else if((cdp->cd_caflags & mx_CMD_ARG_G) ||
878 (n_pstate & n_PS_MSGLIST_GABBY))
879 gecp->gec_hist_flags |= a_GO_HIST_GABBY;
880 }
881
882 if(rv != 0){
883 if(!(flags & a_NO_ERRNO)){
884 if(cdp->cd_caflags & mx_CMD_ARG_EM)
885 flags |= a_NO_ERRNO;
886 else if((nerrn = su_err_no()) == 0)
887 nerrn = su_ERR_INVAL;
888 }/*else
889 flags ^= a_NO_ERRNO;*/
890 }else if(cdp->cd_caflags & mx_CMD_ARG_EM)
891 flags |= a_NO_ERRNO;
892 else
893 nerrn = su_ERR_NONE;
894
895 jleave:
896 if((nexn = rv) != 0 &&
897 (gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
898 ) == (a_GO_HIST_ADD | a_GO_HIST_INIT))
899 gecp->gec_hist_flags |= a_GO_HIST_GABBY_ERROR;
900
901 if(flags & a_IGNERR){
902 if(!(n_psonce & n_PSO_EXIT_MASK) && !(n_pstate & n_PS_ERR_EXIT_MASK))
903 n_exit_status = n_EXIT_OK;
904 n_pstate &= ~n_PS_ERR_EXIT_MASK;
905 }else if(rv != 0){
906 boole bo;
907
908 if((bo = ok_blook(batch_exit_on_error))){
909 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
910 if(!(n_poption & n_PO_BATCH_FLAG))
911 bo = FAL0;
912 }
913 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
914 n_pstate |= n_PS_ERR_QUIT;
915 else if(ok_blook(posix)){
916 if(n_psonce & n_PSO_STARTED)
917 rv = 0;
918 else if(!(n_psonce & n_PSO_INTERACTIVE))
919 n_pstate |= n_PS_ERR_XIT;
920 }else
921 rv = 0;
922
923 if(rv != 0){
924 if(n_exit_status == n_EXIT_OK)
925 n_exit_status = n_EXIT_ERR;
926 if((n_poption & n_PO_D_V) &&
927 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
928 n_alert(_("Non-interactive, bailing out due to errors "
929 "in startup load phase"));
930 goto jret;
931 }
932 }
933
934 if(cdp == NULL)
935 goto jret0;
936 if((cdp->cd_caflags & mx_CMD_ARG_P) && ok_blook(autoprint) && visible(dot))
937 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
938 sizeof("\\type") -1);
939
940 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
941 !(cdp->cd_caflags & mx_CMD_ARG_T))
942 n_pstate |= n_PS_SAW_COMMAND;
943 jret0:
944 rv = 0;
945 jret:
946 if(!(flags & a_NO_ERRNO))
947 n_pstate_err_no = nerrn;
948 n_pstate_ex_no = nexn;
949 NYD_OU;
950 return (rv == 0);
951 }
952
953 static void
a_go_hangup(int s)954 a_go_hangup(int s){
955 NYD; /* Signal handler */
956 UNUSED(s);
957 /* nothing to do? */
958 exit(n_EXIT_ERR);
959 }
960
961 #ifdef mx_HAVE_IMAP
n_go_onintr_for_imap(void)962 FL void n_go_onintr_for_imap(void){a_go_onintr(0);}
963 #endif
964 static void
a_go_onintr(int s)965 a_go_onintr(int s){ /* TODO block signals while acting */
966 NYD; /* Signal handler */
967 UNUSED(s);
968
969 safe_signal(SIGINT, a_go_onintr);
970
971 mx_termios_cmdx(mx_TERMIOS_CMD_RESET);
972
973 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
974
975 if(interrupts != 1)
976 n_err_sighdl(_("Interrupt\n"));
977 safe_signal(SIGPIPE, a_go_oldpipe);
978 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
979 }
980
981 static void
a_go_cleanup(enum a_go_cleanup_mode gcm)982 a_go_cleanup(enum a_go_cleanup_mode gcm){
983 /* Signals blocked */
984 struct a_go_ctx *gcp;
985 NYD_IN;
986
987 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
988 mx_sigs_all_holdx();
989 jrestart:
990 gcp = a_go_ctx;
991
992 /* Free input injections of this level first */
993 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
994 struct a_go_input_inject **giipp, *giip;
995
996 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
997 *giipp = giip->gii_next;
998 n_free(giip);
999 }
1000 }
1001
1002 /* Cleanup non-crucial external stuff */
1003 mx_COLOUR(
1004 if(gcp->gc_data.gdc_colour != NIL)
1005 mx_colour_stack_del(&gcp->gc_data);
1006 )
1007
1008 /* Cleanup crucial external stuff as necessary */
1009 if(gcp->gc_data.gdc_ifcond != NIL &&
1010 ((gcp->gc_outer == NIL && (gcm & a_GO_CLEANUP_UNWIND)) ||
1011 !(gcm & a_GO_CLEANUP_LOOPTICK))){
1012 n_cnd_if_stack_del(&gcp->gc_data);
1013 if(!(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
1014 !(gcp->gc_flags & a_GO_FORCE_EOF) && a_go_xcall == NULL &&
1015 !(n_psonce & n_PSO_EXIT_MASK)){
1016 n_err(_("Unmatched `if' at end of %s%s\n"),
1017 (gcp->gc_outer == NIL ? su_empty
1018 : ((gcp->gc_flags & a_GO_MACRO
1019 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _(" command") : _(" macro"))
1020 : _(" `source'd file")))),
1021 gcp->gc_name);
1022 gcm |= a_GO_CLEANUP_ERROR;
1023 }
1024 }
1025
1026 /* Work the actual context (according to cleanup mode) */
1027 if(gcp->gc_outer == NULL){
1028 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
1029 if(a_go_xcall != NULL){
1030 n_free(a_go_xcall);
1031 a_go_xcall = NULL;
1032 }
1033 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1034 n_pstate &= ~n_PS_ERR_EXIT_MASK;
1035 mx_fs_close_all();
1036 }else{
1037 if(!(n_pstate & n_PS_SOURCING))
1038 mx_fs_close_all();
1039 }
1040
1041 su_mem_bag_reset(gcp->gc_data.gdc_membag);
1042 su_DBG( su_mem_set_conf(su_MEM_CONF_LINGER_FREE_RELEASE, 0); )
1043
1044 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1045 ASSERT(a_go_xcall == NULL);
1046 ASSERT(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
1047 ASSERT(gcp->gc_on_finalize == NULL);
1048 mx_COLOUR( ASSERT(gcp->gc_data.gdc_colour == NIL); )
1049
1050 if(gcm & a_GO_CLEANUP_ERROR)
1051 goto jerr;
1052 goto jxleave;
1053 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
1054 su_mem_bag_reset(gcp->gc_data.gdc_membag);
1055 su_DBG( su_mem_set_conf(su_MEM_CONF_LINGER_FREE_RELEASE, 0); )
1056 goto jxleave;
1057 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
1058 n_stdin = gcp->gc_splice_stdin;
1059 n_stdout = gcp->gc_splice_stdout;
1060 n_psonce = gcp->gc_splice_psonce;
1061 goto jstackpop;
1062 }
1063
1064 /* Teardown context */
1065 if(gcp->gc_flags & a_GO_MACRO){
1066 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
1067 char **lp;
1068
1069 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
1070 n_free(*lp);
1071 ++gcp->gc_loff;
1072 }
1073 /* Part of gcp's memory chunk, then */
1074 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
1075 n_free(gcp->gc_lines);
1076 }
1077 }else if(gcp->gc_flags & a_GO_PIPE)
1078 /* XXX command manager should -TERM then -KILL instead of hoping
1079 * XXX for exit of provider due to su_ERR_PIPE / SIGPIPE */
1080 mx_fs_pipe_close(gcp->gc_file, TRU1);
1081 else if(gcp->gc_flags & a_GO_FILE)
1082 mx_fs_close(gcp->gc_file);
1083
1084 if(!(gcp->gc_flags & a_GO_MEMBAG_INHERITED))
1085 su_mem_bag_gut(gcp->gc_data.gdc_membag);
1086 else
1087 su_mem_bag_reset(gcp->gc_data.gdc_membag);
1088
1089 jstackpop:
1090 /* Update a_go_ctx and n_go_data, n_pstate ... */
1091 a_go_ctx = gcp->gc_outer;
1092 ASSERT(a_go_ctx != NULL);
1093 /* C99 */{
1094 struct a_go_ctx *x;
1095
1096 for(x = a_go_ctx; x->gc_flags & a_GO_DATACTX_INHERITED;){
1097 x = x->gc_outer;
1098 ASSERT(x != NULL);
1099 }
1100 n_go_data = &x->gc_data;
1101 }
1102
1103 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
1104 (a_GO_MACRO | a_GO_SUPER_MACRO)){
1105 n_pstate &= ~n_PS_SOURCING;
1106 ASSERT(n_pstate & n_PS_ROBOT);
1107 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
1108 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
1109 else
1110 ASSERT(n_pstate & n_PS_ROBOT);
1111
1112 if(gcp->gc_on_finalize != NULL)
1113 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
1114
1115 if(gcm & a_GO_CLEANUP_ERROR){
1116 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
1117 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
1118 goto jerr;
1119 }
1120 jleave:
1121 if(gcp->gc_flags & a_GO_FREE)
1122 n_free(gcp);
1123
1124 if(UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
1125 goto jrestart;
1126
1127 jxleave:
1128 NYD_OU;
1129 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1130 mx_sigs_all_rele();
1131 return;
1132
1133 jerr:
1134 /* With *posix* we follow what POSIX says:
1135 * Any errors in the start-up file shall either cause mailx to
1136 * terminate with a diagnostic message and a non-zero status or to
1137 * continue after writing a diagnostic message, ignoring the
1138 * remainder of the lines in the start-up file
1139 * Print the diagnostic only for the outermost resource unless the user
1140 * is debugging or in verbose mode */
1141 if((n_poption & n_PO_D_V) ||
1142 (!(n_psonce & n_PSO_STARTED) &&
1143 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1144 (gcp->gc_outer == NIL ||
1145 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK))))
1146 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1147 n_alert(_("Stopped %s %s due to errors%s"),
1148 (n_psonce & n_PSO_STARTED
1149 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1150 : (gcp->gc_flags & a_GO_MACRO
1151 ? (gcp->gc_flags & a_GO_MACRO_CMD
1152 ? _("evaluating command") : _("evaluating macro"))
1153 : (gcp->gc_flags & a_GO_PIPE
1154 ? _("executing `source'd pipe")
1155 : (gcp->gc_flags & a_GO_FILE
1156 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1157 )
1158 : (((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_BLTIN_RC)
1159 ) == a_GO_MACRO)
1160 ? ((gcp->gc_flags & a_GO_MACRO_X_OPTION)
1161 ? _("evaluating command line")
1162 : _("evaluating macro"))
1163 : _("loading initialization resource"))),
1164 n_shexp_quote_cp(gcp->gc_name, FAL0),
1165 (n_poption & n_PO_D ? n_empty : _(" (enable *debug* for trace)")));
1166 goto jleave;
1167 }
1168
1169 static boole
a_go_file(char const * file,boole silent_open_error)1170 a_go_file(char const *file, boole silent_open_error){
1171 struct a_go_ctx *gcp;
1172 sigset_t osigmask;
1173 uz nlen;
1174 char *nbuf;
1175 boole ispipe;
1176 FILE *fip;
1177 NYD_IN;
1178
1179 fip = NIL;
1180 UNINIT(nbuf, NIL);
1181
1182 /* Being a command argument file is space-trimmed *//* TODO v15 with
1183 * TODO WYRALIST this is no longer necessary true, and for that we
1184 * TODO don't set _PARSE_TRIM_SPACE because we cannot! -> cmd.h!! */
1185 #if 0
1186 ((ispipe = (!silent_open_error && (nlen = su_cs_len(file)) > 0 &&
1187 file[--nlen] == '|')))
1188 #else
1189 ispipe = FAL0;
1190 if(!silent_open_error){
1191 for(nlen = su_cs_len(file); nlen > 0;){
1192 char c;
1193
1194 c = file[--nlen];
1195 if(!su_cs_is_space(c)){
1196 if(c == '|'){
1197 nbuf = savestrbuf(file, nlen);
1198 ispipe = TRU1;
1199 }
1200 break;
1201 }
1202 }
1203 }
1204 #endif
1205
1206 if(ispipe){
1207 if((fip = mx_fs_pipe_open(nbuf /* #if 0 above = savestrbuf(file, nlen)*/,
1208 "r", ok_vlook(SHELL), NIL, -1)) == NIL)
1209 goto jeopencheck;
1210 }else if((nbuf = fexpand(file, FEXP_LOCAL_FILE | FEXP_NVAR)) == NIL)
1211 goto jeopencheck;
1212 else if((fip = mx_fs_open(nbuf, "r")) == NIL){
1213 jeopencheck:
1214 if(!silent_open_error || (n_poption & n_PO_D_V))
1215 n_perr(nbuf, 0);
1216 if(silent_open_error)
1217 fip = (FILE*)-1;
1218 goto jleave;
1219 }
1220
1221 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1222
1223 gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1224 (nlen = su_cs_len(nbuf) +1));
1225 su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1226 gcp->gc_data.gdc_membag =
1227 su_mem_bag_create(&gcp->gc_data.gdc__membag_buf[0], 0);
1228
1229 mx_sigs_all_holdx();
1230
1231 gcp->gc_outer = a_go_ctx;
1232 gcp->gc_osigmask = osigmask;
1233 gcp->gc_file = fip;
1234 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1235 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1236 su_mem_copy(gcp->gc_name, nbuf, nlen);
1237
1238 a_go_ctx = gcp;
1239 n_go_data = &gcp->gc_data;
1240 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1241 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1242 fip = NULL;
1243 jleave:
1244 NYD_OU;
1245 return (fip != NULL);
1246 }
1247
1248 static boole
a_go_load(struct a_go_ctx * gcp)1249 a_go_load(struct a_go_ctx *gcp){
1250 NYD2_IN;
1251
1252 ASSERT(!(n_psonce & n_PSO_STARTED));
1253 ASSERT(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1254
1255 gcp->gc_flags |= a_GO_MEMBAG_INHERITED;
1256 gcp->gc_data.gdc_membag = n_go_data->gdc_membag;
1257
1258 mx_sigs_all_holdx();
1259
1260 /* POSIX:
1261 * Any errors in the start-up file shall either cause mailx to terminate
1262 * with a diagnostic message and a non-zero status or to continue after
1263 * writing a diagnostic message, ignoring the remainder of the lines in
1264 * the start-up file. */
1265 gcp->gc_outer = a_go_ctx;
1266 a_go_ctx = gcp;
1267 n_go_data = &gcp->gc_data;
1268 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1269 n_pstate |= n_PS_ROBOT |
1270 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1271 */
1272 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1273
1274 mx_sigs_all_rele();
1275
1276 n_go_main_loop();
1277 NYD2_OU;
1278 return (((n_psonce & n_PSO_EXIT_MASK) |
1279 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1280 }
1281
1282 static void
a_go__eloopint(int sig)1283 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1284 NYD; /* Signal handler */
1285 UNUSED(sig);
1286 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1287 }
1288
1289 static boole
a_go_event_loop(struct a_go_ctx * gcp,enum n_go_input_flags gif)1290 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1291 n_sighdl_t soldhdl;
1292 struct a_go_eval_ctx gec;
1293 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1294 volatile int hadint;/* TODO get rid of shitty signal stuff (see signal.c) */
1295 sigset_t osigmask;
1296 NYD2_IN;
1297
1298 su_mem_set(&gec, 0, sizeof gec);
1299 if(gif & n_GO_INPUT_IGNERR)
1300 gec.gec_ignerr = TRU1;
1301 mx_fs_linepool_aquire(&gec.gec_line.s, &gec.gec_line.l);
1302
1303 osigmask = gcp->gc_osigmask;
1304 hadint = FAL0;
1305 f = a_RETOK;
1306
1307 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1308 safe_signal(SIGINT, &a_go__eloopint);
1309 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1310 mx_sigs_all_holdx();
1311 hadint = TRU1;
1312 f &= ~a_RETOK;
1313 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1314 goto jjump;
1315 }
1316 }
1317
1318 for(;; f |= a_TICKED){
1319 int n;
1320
1321 if(f & a_TICKED){
1322 su_mem_bag_reset(gcp->gc_data.gdc_membag);
1323 su_DBG( su_mem_set_conf(su_MEM_CONF_LINGER_FREE_RELEASE, 0); )
1324 }
1325
1326 /* Read a line of commands and handle end of file specially */
1327 gec.gec_line.l = gec.gec_line_size;
1328 mx_sigs_all_rele();
1329 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL, NULL);
1330 mx_sigs_all_holdx();
1331 gec.gec_line_size = S(u32,gec.gec_line.l);
1332 gec.gec_line.l = S(u32,n);
1333
1334 if(n < 0)
1335 break;
1336
1337 mx_sigs_all_rele();
1338 ASSERT(gec.gec_hist_flags == a_GO_HIST_NONE);
1339 if(!a_go_evaluate(&gec))
1340 f &= ~a_RETOK;
1341 mx_sigs_all_holdx();
1342
1343 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1344 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1345 break;
1346 }
1347
1348 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1349 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1350 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1351 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1352
1353 mx_fs_linepool_release(gec.gec_line.s, gec.gec_line_size);
1354
1355 if(soldhdl != SIG_IGN)
1356 safe_signal(SIGINT, soldhdl);
1357
1358 NYD2_OU;
1359 mx_sigs_all_rele();
1360 if(hadint){
1361 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1362 n_raise(SIGINT);
1363 }
1364 return (f & a_RETOK);
1365 }
1366
1367 FL void
n_go_init(void)1368 n_go_init(void){
1369 struct a_go_ctx *gcp;
1370 NYD2_IN;
1371
1372 ASSERT(n_stdin != NULL);
1373
1374 gcp = (void*)a_go__mainctx_b.uf;
1375 su_DBGOR( su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1376 su_mem_set(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1377 gcp->gc_data.gdc_membag =
1378 su_mem_bag_create(&gcp->gc_data.gdc__membag_buf[0], 0);
1379 gcp->gc_file = n_stdin;
1380 su_mem_copy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1381
1382 a_go_ctx = gcp;
1383 n_go_data = &gcp->gc_data;
1384
1385 mx_termios_controller_setup(mx_TERMIOS_SETUP_STARTUP);
1386 mx_child_controller_setup();
1387 NYD2_OU;
1388 }
1389
1390 FL boole
n_go_main_loop(void)1391 n_go_main_loop(void){ /* FIXME */
1392 struct a_go_eval_ctx gec;
1393 int n, eofcnt;
1394 boole volatile rv;
1395 NYD_IN;
1396
1397 rv = TRU1;
1398
1399 if (!(n_pstate & n_PS_SOURCING)) {
1400 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1401 safe_signal(SIGINT, &a_go_onintr);
1402 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1403 safe_signal(SIGHUP, &a_go_hangup);
1404 }
1405 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1406 safe_signal(SIGPIPE, a_go_oldpipe);
1407
1408 su_mem_set(&gec, 0, sizeof gec);
1409
1410 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1411 mx_sigs_all_holdx();
1412
1413 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1414 interrupts = 0;
1415 DVL(su_nyd_reset_level(1);)
1416
1417 if(gec.gec_ever_seen)
1418 /* TODO too expensive, just do the membag (++?) here.
1419 * TODO in fact all other conditions would be an error, no? */
1420 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1421
1422 /* TODO This condition test may not be here: if the condition is not true
1423 * TODO a recursive mainloop object without that cruft should be used! */
1424 if(!(n_pstate & (n_PS_ROBOT | n_PS_SOURCING))){
1425 if(a_go_ctx->gc_inject == su_NIL)
1426 mx_fs_linepool_cleanup(FAL0);
1427
1428 /* TODO We need a regular on_tick_event, to which this one, the
1429 * TODO *newmail* thing below, and possibly other caches
1430 * TODO (mime.types, mta-aliases, mailcap, netrc; if not yet:
1431 * TODO convert!!) can attach: they should trigger a switch and
1432 * TODO update cache state only once per mainloop tick!! */
1433 /* C99 */{
1434 char const *ccp;
1435
1436 if((ccp = ok_vlook(on_main_loop_tick)) != NIL)
1437 temporary_on_xy_hook_caller("on-main-loop-tick", ccp, TRU1);
1438 }
1439
1440 /* Do not check newmail with active injections, wait for prompt */
1441 if(a_go_ctx->gc_inject == su_NIL && (n_psonce & n_PSO_INTERACTIVE)){
1442 char *cp;
1443
1444 if ((cp = ok_vlook(newmail)) != NULL) { /* TODO on_tick_event! */
1445 struct stat st;
1446
1447 if(mb.mb_type == MB_FILE){
1448 if(!stat(mailname, &st) && st.st_size > mailsize)
1449 #if defined mx_HAVE_MAILDIR || defined mx_HAVE_IMAP
1450 Jnewmail:
1451 #endif
1452 {
1453 u32 odid;
1454 uz odot;
1455
1456 odot = P2UZ(dot - message);
1457 odid = (n_pstate & n_PS_DID_PRINT_DOT);
1458
1459 mx_sigs_all_rele();
1460 n = setfile(mailname,
1461 (FEDIT_NEWMAIL |
1462 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)));
1463 mx_sigs_all_holdx();
1464
1465 if(n < 0) {
1466 n_exit_status |= n_EXIT_ERR;
1467 rv = FAL0;
1468 break;
1469 }
1470 #ifdef mx_HAVE_IMAP
1471 if(mb.mb_type != MB_IMAP){
1472 #endif
1473 dot = &message[odot];
1474 n_pstate |= odid;
1475 #ifdef mx_HAVE_IMAP
1476 }
1477 #endif
1478 }
1479 }else{
1480 #if defined mx_HAVE_MAILDIR || defined mx_HAVE_IMAP
1481 n = (cp != NULL && su_cs_cmp(cp, "nopoll"));
1482 #endif
1483
1484 #ifdef mx_HAVE_MAILDIR
1485 if(mb.mb_type == MB_MAILDIR){
1486 if(n != 0)
1487 goto Jnewmail;
1488 }
1489 #endif
1490 #ifdef mx_HAVE_IMAP
1491 if(mb.mb_type == MB_IMAP){
1492 if(!n)
1493 n = (cp != NULL && su_cs_cmp(cp, "noimap"));
1494
1495 if(imap_newmail(n) > (cp == NULL))
1496 goto Jnewmail;
1497 }
1498 #endif
1499 }
1500 }
1501 }
1502 }
1503
1504 /* Read a line of commands and handle end of file specially */
1505 n_pstate |= n_PS_ERRORS_NEED_PRINT_ONCE;
1506
1507 mx_fs_linepool_aquire(&gec.gec_line.s, &gec.gec_line.l);
1508 gec.gec_line_size = S(u32,gec.gec_line.l);
1509 /* C99 */{
1510 boole histadd;
1511
1512 histadd = ((n_psonce & n_PSO_INTERACTIVE) &&
1513 !(n_pstate & (n_PS_ROBOT | n_PS_SOURCING)) &&
1514 a_go_ctx->gc_inject == su_NIL); /* xxx really injection? */
1515 mx_sigs_all_rele();
1516 ASSERT(!gec.gec_ignerr);
1517 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1518 &gec.gec_line.s, &gec.gec_line.l, NULL, &histadd);
1519 mx_sigs_all_holdx();
1520
1521 gec.gec_hist_flags = histadd ? a_GO_HIST_ADD : a_GO_HIST_NONE;
1522 }
1523 gec.gec_line_size = S(u32,gec.gec_line.l);
1524 gec.gec_line.l = S(u32,n);
1525
1526 if(n < 0){
1527 mx_fs_linepool_release(gec.gec_line.s, gec.gec_line_size);
1528 if(!(n_pstate & n_PS_ROBOT) &&
1529 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1530 ++eofcnt < 4){
1531 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1532 n_go_input_clearerr();
1533 continue;
1534 }
1535 break;
1536 }
1537
1538 n_pstate &= ~n_PS_HOOK_MASK;
1539 mx_sigs_all_rele();
1540 rv = a_go_evaluate(&gec);
1541 mx_sigs_all_holdx();
1542
1543 n_pstate &= ~n_PS_ERRORS_NEED_PRINT_ONCE;
1544 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1545 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1546 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1547 default: break;
1548 }
1549
1550 if(gec.gec_hist_flags & a_GO_HIST_ADD){
1551 char const *cc, *ca;
1552
1553 /* TODO history handling is terrible; this should pass the command
1554 * TODO evaluation context carrier all along the way, so that commands
1555 * TODO can alter the "add history" behaviour at will; also the
1556 * TODO arguments as passed into ARGV should be passed along to
1557 * TODO addhist, see *on-history-addition* for the why of this */
1558 cc = gec.gec_hist_cmd;
1559 ca = gec.gec_hist_args;
1560 if(cc != NULL && ca != NULL)
1561 cc = savecatsep(cc, ' ', ca);
1562 else if(ca != NULL)
1563 cc = ca;
1564 ASSERT(cc != NULL);
1565 mx_tty_addhist(cc, (n_GO_INPUT_CTX_DEFAULT |
1566 (gec.gec_hist_flags & a_GO_HIST_GABBY
1567 ? n_GO_INPUT_HIST_GABBY : n_GO_INPUT_NONE) |
1568 (gec.gec_hist_flags & a_GO_HIST_GABBY_ERROR
1569 ? n_GO_INPUT_HIST_GABBY | n_GO_INPUT_HIST_ERROR
1570 : n_GO_INPUT_NONE)));
1571 }
1572
1573 mx_fs_linepool_release(gec.gec_line.s, gec.gec_line_size);
1574
1575 if((n_psonce & n_PSO_EXIT_MASK) || !rv)
1576 break;
1577 }
1578
1579 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1580 (rv ? 0 : a_GO_CLEANUP_ERROR));
1581 mx_fs_linepool_cleanup(TRU1);
1582
1583 mx_sigs_all_rele();
1584
1585 NYD_OU;
1586 return rv;
1587 }
1588
1589 FL void
n_go_input_clearerr(void)1590 n_go_input_clearerr(void){
1591 FILE *fp;
1592 NYD2_IN;
1593
1594 fp = NULL;
1595
1596 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1597 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1598 fp = a_go_ctx->gc_file;
1599
1600 if(fp != NULL){
1601 a_go_ctx->gc_flags &= ~a_GO_IS_EOF;
1602 clearerr(fp);
1603 }
1604 NYD2_OU;
1605 }
1606
1607 FL void
n_go_input_force_eof(void)1608 n_go_input_force_eof(void){
1609 NYD2_IN;
1610 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1611 NYD2_OU;
1612 }
1613
1614 FL boole
n_go_input_is_eof(void)1615 n_go_input_is_eof(void){
1616 boole rv;
1617 NYD2_IN;
1618
1619 rv = ((a_go_ctx->gc_flags & a_GO_IS_EOF) != 0);
1620 NYD2_OU;
1621 return rv;
1622 }
1623
1624 FL boole
n_go_input_have_injections(void)1625 n_go_input_have_injections(void){
1626 boole rv;
1627 NYD2_IN;
1628
1629 rv = (a_go_ctx->gc_inject != NULL);
1630 NYD2_OU;
1631 return rv;
1632 }
1633
1634 FL void
n_go_input_inject(enum n_go_input_inject_flags giif,char const * buf,uz len)1635 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1636 uz len){
1637 NYD_IN;
1638
1639 if(len == UZ_MAX)
1640 len = su_cs_len(buf);
1641
1642 if(UZ_MAX - VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1643 len > 0){
1644 struct a_go_input_inject *giip, **giipp;
1645
1646 mx_sigs_all_holdx();
1647
1648 giip = n_alloc(VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1649 ) + 1 + len +1);
1650 giipp = &a_go_ctx->gc_inject;
1651 giip->gii_next = *giipp;
1652 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1653 giip->gii_no_history = ((giif & n_GO_INPUT_INJECT_HISTORY) == 0);
1654 su_mem_copy(&giip->gii_dat[0], buf, len);
1655 giip->gii_dat[giip->gii_len = len] = '\0';
1656 *giipp = giip;
1657
1658 mx_sigs_all_rele();
1659 }
1660 NYD_OU;
1661 }
1662
1663 FL int
1664 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1665 uz *linesize, char const *string, boole *histok_or_nil
1666 su_DBG_LOC_ARGS_DECL){
1667 /* TODO readline: linebuf pool!; n_go_input should return s64.
1668 * TODO This thing should be replaced by a(n) (stack of) event generator(s)
1669 * TODO and consumed by OnLineCompletedEvent listeners */
1670 struct n_string xprompt;
1671 FILE *ifile;
1672 char const *iftype;
1673 struct a_go_input_inject *giip;
1674 int nold, n;
1675 enum{
1676 a_NONE,
1677 a_HISTOK = 1u<<0,
1678 a_USE_PROMPT = 1u<<1,
1679 a_USE_MLE = 1u<<2,
1680 a_DIG_MSG_OVERLAY = 1u<<16
1681 } f;
1682 NYD2_IN;
1683
1684 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1685 mx_sigs_all_holdx();
1686
1687 f = a_NONE;
1688
1689 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
1690 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1691 n = -1;
1692 goto jleave;
1693 }
1694
1695 if(gif & n_GO_INPUT_FORCE_STDIN)
1696 goto jforce_stdin;
1697
1698 /* Special case macro mode: never need to prompt, lines have always been
1699 * unfolded already; TODO we need on_line_completed event and producers! */
1700 if(a_go_ctx->gc_flags & a_GO_MACRO){
1701 if(*linebuf != NULL)
1702 n_free(*linebuf);
1703
1704 /* Injection in progress? Don't care about the autocommit state here */
1705 if(!(gif & n_GO_INPUT_DELAY_INJECTIONS) &&
1706 (giip = a_go_ctx->gc_inject) != NULL){
1707 a_go_ctx->gc_inject = giip->gii_next;
1708
1709 /* Simply "reuse" allocation, copy string to front of it */
1710 jinject:
1711 *linesize = giip->gii_len;
1712 *linebuf = (char*)giip;
1713 su_mem_move(*linebuf, giip->gii_dat, giip->gii_len +1);
1714 iftype = "INJECTION";
1715 }else{
1716 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
1717 *linesize = 0;
1718 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1719 n = -1;
1720 goto jleave;
1721 }
1722
1723 ++a_go_ctx->gc_loff;
1724 *linesize = su_cs_len(*linebuf);
1725 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
1726 *linebuf = su_cs_dup_cbuf(*linebuf, *linesize, 0);
1727
1728 iftype = ((a_go_ctx->gc_flags &
1729 (a_GO_MACRO_X_OPTION | a_GO_MACRO_BLTIN_RC))
1730 ? "COMMAND-LINE"
1731 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO");
1732 }
1733 n = (int)*linesize;
1734 n_pstate |= n_PS_READLINE_NL;
1735 goto jhave_dat;
1736 }
1737
1738 if(!(gif & n_GO_INPUT_DELAY_INJECTIONS)){
1739 /* Injection in progress? */
1740 struct a_go_input_inject **giipp;
1741
1742 giipp = &a_go_ctx->gc_inject;
1743
1744 if((giip = *giipp) != NULL){
1745 *giipp = giip->gii_next;
1746
1747 if(giip->gii_commit){
1748 if(*linebuf != NULL)
1749 n_free(*linebuf);
1750 if(!giip->gii_no_history)
1751 f |= a_HISTOK;
1752 goto jinject; /* (above) */
1753 }else{
1754 string = savestrbuf(giip->gii_dat, giip->gii_len);
1755 n_free(giip);
1756 }
1757 }
1758 }
1759
1760 jforce_stdin:
1761 n_pstate &= ~n_PS_READLINE_NL;
1762 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
1763 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
1764 if(!(n_pstate & n_PS_ROBOT) &&
1765 (n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
1766 (n_PSO_INTERACTIVE | n_PSO_STARTED))
1767 f |= a_HISTOK;
1768 if(!(f & a_HISTOK) || (gif & n_GO_INPUT_FORCE_STDIN))
1769 gif |= n_GO_INPUT_PROMPT_NONE;
1770 else{
1771 f |= a_USE_PROMPT;
1772 if(!ok_blook(line_editor_disable))
1773 f |= a_USE_MLE;
1774 else
1775 (void)n_string_creat_auto(&xprompt);
1776 if(prompt == NULL)
1777 gif |= n_GO_INPUT_PROMPT_EVAL;
1778 }
1779
1780 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
1781 if((gif & n_GO_INPUT_PROMPT_NONE) && !(f & a_USE_MLE))
1782 fflush(n_stdout);
1783
1784 if(gif & n_GO_INPUT_FORCE_STDIN){
1785 struct a_go_readctl_ctx *grcp;
1786 struct mx_dig_msg_ctx *dmcp;
1787
1788 if((dmcp = mx_dig_msg_read_overlay) != NIL){
1789 ifile = dmcp->dmc_fp;
1790 f |= a_DIG_MSG_OVERLAY;
1791 }else if((grcp = n_readctl_read_overlay) == NULL ||
1792 (ifile = grcp->grc_fp) == NULL)
1793 ifile = n_stdin;
1794 }else
1795 ifile = a_go_ctx->gc_file;
1796 if(ifile == NULL){
1797 ASSERT((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
1798 (a_go_ctx->gc_flags & a_GO_MACRO));
1799 ifile = n_stdin;
1800 }
1801
1802 for(nold = n = 0;;){
1803 if(f & a_USE_MLE){
1804 ASSERT(ifile == n_stdin);
1805 if(string != NULL && (n = (int)su_cs_len(string)) > 0){
1806 if(*linesize > 0)
1807 *linesize += n +1;
1808 else
1809 *linesize = (uz)n + LINESIZE +1;
1810 *linebuf = su_MEM_REALLOC_LOCOR(*linebuf, *linesize,
1811 su_DBG_LOC_ARGS_ORUSE);
1812 su_mem_copy(*linebuf, string, (uz)n +1);
1813 }
1814 string = NULL;
1815
1816 mx_sigs_all_rele();
1817
1818 n = (mx_tty_readline)(gif, prompt, linebuf, linesize, n, histok_or_nil
1819 su_DBG_LOC_ARGS_USE);
1820
1821 mx_sigs_all_holdx();
1822
1823 if(n < 0 && !ferror(ifile)) /* EOF never i guess */
1824 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1825 }else{
1826 mx_sigs_all_rele();
1827
1828 if(!(gif & n_GO_INPUT_PROMPT_NONE)){
1829 mx_tty_create_prompt(&xprompt, prompt, gif);
1830
1831 if(xprompt.s_len > 0){
1832 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
1833 fflush(n_stdout);
1834 }
1835 }
1836
1837 n = (readline_restart)(ifile, linebuf, linesize, n
1838 su_DBG_LOC_ARGS_USE);
1839
1840 mx_sigs_all_holdx();
1841
1842 if(n < 0 && !ferror(ifile))
1843 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1844
1845 if(n > 0 && nold > 0){
1846 char const *cp;
1847 int i;
1848
1849 i = 0;
1850 cp = &(*linebuf)[nold];
1851 while(su_cs_is_space(*cp) && n - i >= nold)
1852 ++cp, ++i;
1853 if(i > 0){
1854 su_mem_move(&(*linebuf)[nold], cp, n - nold - i);
1855 n -= i;
1856 (*linebuf)[n] = '\0';
1857 }
1858 }
1859 }
1860 if(n <= 0)
1861 break;
1862
1863 /* POSIX says:
1864 * TODO This does not take care for current shell quote mode!
1865 * TODO Thus "echo '\<NEWLINE HERE> bla' will never work
1866 * An unquoted <backslash> at the end of a command line shall
1867 * be discarded and the next line shall continue the command */
1868 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\')
1869 break;
1870
1871 /* Definitely outside of quotes, thus quoting rules are so that an uneven
1872 * number of successive reverse solidus at EOL is a continuation */
1873 if(n > 1){
1874 uz i, j;
1875
1876 for(j = 1, i = (uz)n - 1; i-- > 0; ++j)
1877 if((*linebuf)[i] != '\\')
1878 break;
1879 if(!(j & 1))
1880 break;
1881 }
1882 (*linebuf)[nold = --n] = '\0';
1883 gif |= n_GO_INPUT_NL_FOLLOW;
1884 }
1885 if(n < 0)
1886 goto jleave;
1887
1888 (*linebuf)[*linesize = n] = '\0';
1889
1890 if(f & a_USE_MLE)
1891 n_pstate |= n_PS_READLINE_NL;
1892 else if(n == 0 || su_cs_is_space(**linebuf))
1893 f &= ~a_HISTOK;
1894
1895 jhave_dat:
1896 if(n_poption & n_PO_D_VVV)
1897 n_err(_("%s%s %d bytes <%s>\n"),
1898 iftype, (n_cnd_if_exists() == TRUM1 ? "?whiteout" : su_empty),
1899 n, *linebuf);
1900 jleave:
1901 if (n_pstate & n_PS_PSTATE_PENDMASK)
1902 a_go_update_pstate();
1903
1904 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
1905 * TODO but only established from the outside and we need to drop this
1906 * TODO overlay context somehow; ditto DIG_MSG_OVERLAY */
1907 if(n < 0){
1908 if(f & a_DIG_MSG_OVERLAY)
1909 mx_dig_msg_read_overlay = NIL;
1910 if(a_go_ctx->gc_flags & a_GO_SPLICE)
1911 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
1912 }
1913
1914 if(histok_or_nil != NIL && !(f & a_HISTOK))
1915 *histok_or_nil = FAL0;
1916
1917 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1918 mx_sigs_all_rele();
1919 NYD2_OU;
1920 return n;
1921 }
1922
1923 FL char *
n_go_input_cp(enum n_go_input_flags gif,char const * prompt,char const * string)1924 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
1925 char const *string){
1926 struct n_sigman sm;
1927 boole histadd;
1928 uz linesize;
1929 char *linebuf, * volatile rv;
1930 int n;
1931 NYD2_IN;
1932
1933 mx_fs_linepool_aquire(&linebuf, &linesize);
1934 rv = NIL;
1935
1936 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1937 case 0:
1938 break;
1939 default:
1940 goto jleave;
1941 }
1942
1943 histadd = TRU1;
1944 n = n_go_input(gif, prompt, &linebuf, &linesize, string, &histadd);
1945 if(n > 0 && *(rv = savestrbuf(linebuf, (uz)n)) != '\0' &&
1946 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE) &&
1947 histadd){
1948 ASSERT(!(gif & n_GO_INPUT_HIST_ERROR) || (gif & n_GO_INPUT_HIST_GABBY));
1949 mx_tty_addhist(rv, gif);
1950 }
1951
1952 n_sigman_cleanup_ping(&sm);
1953
1954 jleave:
1955 mx_fs_linepool_release(linebuf, linesize);
1956 NYD2_OU;
1957 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1958 return rv;
1959 }
1960
1961 FL boole
n_go_load_rc(char const * name)1962 n_go_load_rc(char const *name){
1963 struct a_go_ctx *gcp;
1964 uz i;
1965 FILE *fip;
1966 boole rv;
1967 NYD_IN;
1968 ASSERT_NYD_EXEC(name != NIL, rv = FAL0);
1969
1970 rv = TRU1;
1971
1972 if((fip = mx_fs_open(name, "r")) == NIL){
1973 if(n_poption & n_PO_D_V)
1974 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
1975 goto jleave;
1976 }
1977
1978 i = su_cs_len(name) +1;
1979 gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
1980 su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1981
1982 gcp->gc_file = fip;
1983 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
1984 su_mem_copy(gcp->gc_name, name, i);
1985
1986 if(n_poption & n_PO_D_VV)
1987 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
1988 rv = a_go_load(gcp);
1989 jleave:
1990 NYD_OU;
1991 return rv;
1992 }
1993
1994 FL boole
n_go_load_lines(boole injectit,char const ** lines,uz cnt)1995 n_go_load_lines(boole injectit, char const **lines, uz cnt){
1996 static char const a_name_x[] = "-X", a_name_bltin[] = "builtin RC file";
1997
1998 union{
1999 boole rv;
2000 u64 align;
2001 char uf[VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2002 MAX(sizeof(a_name_x), sizeof(a_name_bltin))];
2003 } b;
2004 char const *srcp, *xsrcp;
2005 char *cp;
2006 uz imax, i, len;
2007 boole nofail;
2008 struct a_go_ctx *gcp;
2009 NYD_IN;
2010
2011 gcp = (void*)b.uf;
2012 su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2013
2014 if(lines == NIL){
2015 su_mem_copy(gcp->gc_name, a_name_bltin, sizeof a_name_bltin);
2016 lines = C(char const**,a_go_bltin_rc_lines);
2017 cnt = a_GO_BLTIN_RC_LINES_CNT;
2018 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_BLTIN_RC |
2019 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
2020 nofail = TRUM1;
2021 }else if(!injectit){
2022 su_mem_copy(gcp->gc_name, a_name_x, sizeof a_name_x);
2023 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
2024 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
2025 nofail = FAL0;
2026 }else
2027 nofail = TRU1;
2028
2029 /* The problem being that we want to support reverse solidus newline
2030 * escaping also within multiline -X, i.e., POSIX says:
2031 * An unquoted <backslash> at the end of a command line shall
2032 * be discarded and the next line shall continue the command
2033 * Therefore instead of "gcp->gc_lines = UNCONST(lines)", duplicate
2034 * the entire lines array and set _MACRO_FREE_DATA.
2035 * Likewise, for injections, we need to reverse the order. */
2036 imax = cnt + 1;
2037 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
2038
2039 /* For each of the input lines.. */
2040 for(i = len = 0, cp = NULL; cnt > 0;){
2041 boole keep;
2042 uz j;
2043
2044 if((j = su_cs_len(srcp = *lines)) == 0){
2045 ++lines, --cnt;
2046 continue;
2047 }
2048
2049 /* Separate one line from a possible multiline input string */
2050 if(nofail != TRUM1 && (xsrcp = su_mem_find(srcp, '\n', j)) != NIL){
2051 *lines = &xsrcp[1];
2052 j = P2UZ(xsrcp - srcp);
2053 }else
2054 ++lines, --cnt;
2055
2056 /* The (separated) string may itself indicate soft newline escaping */
2057 if((keep = (srcp[j - 1] == '\\'))){
2058 uz xj, xk;
2059
2060 /* Need an uneven number of reverse solidus */
2061 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
2062 if(srcp[xj] != '\\')
2063 break;
2064 if(xk & 1)
2065 --j;
2066 else
2067 keep = FAL0;
2068 }
2069
2070 /* Strip any leading WS from follow lines, then */
2071 if(cp != NULL)
2072 while(j > 0 && su_cs_is_space(*srcp))
2073 ++srcp, --j;
2074
2075 if(j > 0){
2076 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
2077 imax += 4;
2078 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
2079 imax);
2080 }
2081 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
2082 su_mem_copy(&cp[len], srcp, j);
2083 cp[len += j] = '\0';
2084
2085 if(!keep)
2086 ++i;
2087 }
2088 if(!keep)
2089 cp = NULL, len = 0;
2090 }
2091 if(cp != NULL){
2092 ASSERT(i + 1 < imax);
2093 gcp->gc_lines[i++] = cp;
2094 }
2095 gcp->gc_lines[i] = NULL;
2096
2097 if(!injectit)
2098 b.rv = a_go_load(gcp);
2099 else{
2100 while(i > 0){
2101 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, cp = gcp->gc_lines[--i],
2102 UZ_MAX);
2103 n_free(cp);
2104 }
2105 n_free(gcp->gc_lines);
2106 ASSERT(nofail);
2107 }
2108
2109 if(nofail)
2110 /* Program exit handling is a total mess! */
2111 b.rv = ((n_psonce & n_PSO_EXIT_MASK) == 0);
2112 NYD_OU;
2113 return b.rv;
2114 }
2115
2116 FL int
c_source(void * v)2117 c_source(void *v){
2118 int rv;
2119 NYD_IN;
2120
2121 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
2122 NYD_OU;
2123 return rv;
2124 }
2125
2126 FL int
c_source_if(void * v)2127 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
2128 int rv;
2129 NYD_IN;
2130
2131 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
2132 NYD_OU;
2133 return rv;
2134 }
2135
2136 FL boole
n_go_macro(enum n_go_input_flags gif,char const * name,char ** lines,void (* on_finalize)(void *),void * finalize_arg)2137 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
2138 void (*on_finalize)(void*), void *finalize_arg){
2139 struct a_go_ctx *gcp;
2140 uz i;
2141 int rv;
2142 sigset_t osigmask;
2143 NYD_IN;
2144
2145 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2146
2147 gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2148 (i = su_cs_len(name) +1));
2149 su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2150 gcp->gc_data.gdc_membag =
2151 su_mem_bag_create(&gcp->gc_data.gdc__membag_buf[0], 0);
2152
2153 mx_sigs_all_holdx();
2154
2155 gcp->gc_outer = a_go_ctx;
2156 gcp->gc_osigmask = osigmask;
2157 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
2158 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2159 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0) |
2160 ((gif & n_GO_INPUT_NO_XCALL) ? a_GO_XCALL_IS_CALL : 0);
2161 gcp->gc_lines = lines;
2162 gcp->gc_on_finalize = on_finalize;
2163 gcp->gc_finalize_arg = finalize_arg;
2164 su_mem_copy(gcp->gc_name, name, i);
2165
2166 a_go_ctx = gcp;
2167 n_go_data = &gcp->gc_data;
2168 n_pstate |= n_PS_ROBOT;
2169 rv = a_go_event_loop(gcp, gif);
2170
2171 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
2172 if(a_go_xcall != NULL){
2173 void *vp;
2174 struct mx_cmd_arg_ctx *cacp;
2175
2176 if(a_go_xcall == (void*)-1)
2177 a_go_xcall = NULL;
2178 else if(((void const*)(cacp = a_go_xcall)->cac_indat) == gcp){
2179 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
2180 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
2181 while(a_go_xcall != NIL){
2182 mx_sigs_all_holdx();
2183
2184 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
2185
2186 vp = a_go_xcall;
2187 a_go_xcall = NIL;
2188 cacp = mx_cmd_arg_restore_from_heap(vp);
2189 n_free(vp);
2190
2191 mx_sigs_all_rele();
2192
2193 (void)c_call(cacp);
2194 }
2195 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) == 0);
2196 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
2197 }
2198 }
2199 NYD_OU;
2200 return rv;
2201 }
2202
2203 FL boole
n_go_command(enum n_go_input_flags gif,char const * cmd)2204 n_go_command(enum n_go_input_flags gif, char const *cmd){
2205 struct a_go_ctx *gcp;
2206 boole rv;
2207 uz i, ial;
2208 sigset_t osigmask;
2209 NYD_IN;
2210
2211 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2212
2213 i = su_cs_len(cmd) +1;
2214 ial = Z_ALIGN(i);
2215 gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2216 ial + 2*sizeof(char*));
2217 su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2218 gcp->gc_data.gdc_membag =
2219 su_mem_bag_create(&gcp->gc_data.gdc__membag_buf[0], 0);
2220
2221 mx_sigs_all_holdx();
2222
2223 gcp->gc_outer = a_go_ctx;
2224 gcp->gc_osigmask = osigmask;
2225 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
2226 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2227 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2228 gcp->gc_lines = (void*)&gcp->gc_name[ial];
2229 su_mem_copy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
2230 gcp->gc_lines[1] = NULL;
2231
2232 a_go_ctx = gcp;
2233 n_go_data = &gcp->gc_data;
2234 n_pstate |= n_PS_ROBOT;
2235 rv = a_go_event_loop(gcp, gif);
2236 NYD_OU;
2237 return rv;
2238 }
2239
2240 FL void
n_go_splice_hack(char const * cmd,FILE * new_stdin,FILE * new_stdout,u32 new_psonce,void (* on_finalize)(void *),void * finalize_arg)2241 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
2242 u32 new_psonce, void (*on_finalize)(void*), void *finalize_arg){
2243 struct a_go_ctx *gcp;
2244 uz i;
2245 sigset_t osigmask;
2246 NYD_IN;
2247
2248 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2249
2250 gcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2251 (i = su_cs_len(cmd) +1));
2252 su_mem_set(gcp, 0, VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2253
2254 mx_sigs_all_holdx();
2255
2256 gcp->gc_outer = a_go_ctx;
2257 gcp->gc_osigmask = osigmask;
2258 gcp->gc_file = new_stdin;
2259 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE | a_GO_DATACTX_INHERITED;
2260 gcp->gc_on_finalize = on_finalize;
2261 gcp->gc_finalize_arg = finalize_arg;
2262 gcp->gc_splice_stdin = n_stdin;
2263 gcp->gc_splice_stdout = n_stdout;
2264 gcp->gc_splice_psonce = n_psonce;
2265 su_mem_copy(gcp->gc_name, cmd, i);
2266
2267 n_stdin = new_stdin;
2268 n_stdout = new_stdout;
2269 n_psonce = new_psonce;
2270 a_go_ctx = gcp;
2271 /* Do NOT touch n_go_data! */
2272 n_pstate |= n_PS_ROBOT;
2273
2274 mx_sigs_all_rele();
2275 NYD_OU;
2276 }
2277
2278 FL void
n_go_splice_hack_remove_after_jump(void)2279 n_go_splice_hack_remove_after_jump(void){
2280 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2281 }
2282
2283 FL boole
n_go_may_yield_control(void)2284 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2285 struct a_go_ctx *gcp;
2286 boole rv;
2287 NYD2_IN;
2288
2289 rv = FAL0;
2290
2291 /* Only when startup completed */
2292 if(!(n_psonce & n_PSO_STARTED))
2293 goto jleave;
2294 /* Only interactive or batch mode (assuming that is ok) */
2295 if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
2296 goto jleave;
2297
2298 /* Not when running any hook */
2299 if(n_pstate & n_PS_HOOK_MASK)
2300 goto jleave;
2301
2302 /* Traverse up the stack:
2303 * . not when controlled by a child process
2304 * TODO . not when there are pipes involved, we neither handle job control,
2305 * TODO nor process groups, that is, controlling terminal acceptably
2306 * . not when sourcing a file */
2307 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2308 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2309 goto jleave;
2310 }
2311
2312 rv = TRU1;
2313 jleave:
2314 NYD2_OU;
2315 return rv;
2316 }
2317
2318 FL int
c_eval(void * vp)2319 c_eval(void *vp){
2320 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
2321 * TODO ARGV with shell rules, but if that is not possible then simply
2322 * TODO adjust argv/argc of "the CmdCtx" that we will have exec real cmd */
2323 struct a_go_eval_ctx gec;
2324 struct n_string s_b, *s;
2325 uz i, j;
2326 char const **argv, *cp;
2327 NYD_IN;
2328
2329 argv = vp;
2330
2331 for(j = i = 0; (cp = argv[i]) != NULL; ++i)
2332 j += su_cs_len(cp);
2333
2334 s = n_string_creat_auto(&s_b);
2335 s = n_string_reserve(s, j);
2336
2337 for(i = 0; (cp = argv[i]) != NULL; ++i){
2338 if(i > 0)
2339 s = n_string_push_c(s, ' ');
2340 s = n_string_push_cp(s, cp);
2341 }
2342
2343 su_mem_set(&gec, 0, sizeof gec);
2344 gec.gec_line.s = n_string_cp(s);
2345 gec.gec_line.l = s->s_len;
2346 if(n_poption & n_PO_D_VV)
2347 n_err(_("EVAL %" PRIuZ " bytes <%s>\n"), gec.gec_line.l, gec.gec_line.s);
2348 (void)/* XXX */a_go_evaluate(&gec);
2349
2350 NYD_OU;
2351 return (a_go_xcall != NULL ? 0 : n_pstate_ex_no);
2352 }
2353
2354 FL int
c_xcall(void * vp)2355 c_xcall(void *vp){
2356 int rv;
2357 struct a_go_ctx *gcp;
2358 NYD2_IN;
2359
2360 /* The context can only be a macro context, except that possibly a single
2361 * level of `eval' (TODO: yet) was used to double-expand our arguments */
2362 if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
2363 gcp = gcp->gc_outer;
2364 if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_X_OPTION |
2365 a_GO_MACRO_BLTIN_RC | a_GO_MACRO_CMD)) != a_GO_MACRO){
2366 if(n_poption & n_PO_D_V)
2367 n_err(_("xcall: can only be used inside a macro, using `call'\n"));
2368 rv = c_call(vp);
2369 goto jleave;
2370 }
2371
2372 /* Try to roll up the stack as much as possible.
2373 * See a_GO_XCALL_LOOP flag description for more */
2374 if(!(gcp->gc_flags & a_GO_XCALL_IS_CALL) && gcp->gc_outer != NULL){
2375 if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
2376 gcp = gcp->gc_outer;
2377 }else{
2378 /* Otherwise this macro is "invoked from the top level", in which case we
2379 * silently act as if we were `call'... */
2380 rv = c_call(vp);
2381 /* ...which means we must ensure the rest of the macro that was us
2382 * doesn't become evaluated! */
2383 a_go_xcall = (void*)-1;
2384 goto jleave;
2385 }
2386
2387 /* C99 */{
2388 struct mx_cmd_arg_ctx *cacp;
2389
2390 cacp = mx_cmd_arg_save_to_heap(vp);
2391 cacp->cac_indat = (char*)gcp;
2392 a_go_xcall = cacp;
2393 }
2394 rv = 0;
2395 jleave:
2396 NYD2_OU;
2397 return rv;
2398 }
2399
2400 FL int
c_exit(void * vp)2401 c_exit(void *vp){
2402 char const **argv;
2403 NYD_IN;
2404
2405 if(*(argv = vp) != NULL && (su_idec_s32_cp(&n_exit_status, *argv, 0, NULL) &
2406 (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
2407 ) != su_IDEC_STATE_CONSUMED)
2408 n_exit_status |= n_EXIT_ERR;
2409
2410 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2411 fflush(NULL);
2412 _exit(n_exit_status);
2413 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2414 n_err(_("exit: delayed until compose mode is left\n")); /* XXX ..log? */
2415 n_psonce |= n_PSO_XIT;
2416 NYD_OU;
2417 return 0;
2418 }
2419
2420 FL int
c_quit(void * vp)2421 c_quit(void *vp){
2422 char const **argv;
2423 NYD_IN;
2424
2425 if(*(argv = vp) != NULL && (su_idec_s32_cp(&n_exit_status, *argv, 0, NULL) &
2426 (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
2427 ) != su_IDEC_STATE_CONSUMED)
2428 n_exit_status |= n_EXIT_ERR;
2429
2430 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2431 fflush(NULL);
2432 _exit(n_exit_status);
2433 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2434 n_err(_("quit: delayed until compose mode is left\n")); /* XXX ..log? */
2435 n_psonce |= n_PSO_QUIT;
2436 NYD_OU;
2437 return 0;
2438 }
2439
2440 FL int
c_readctl(void * vp)2441 c_readctl(void *vp){
2442 /* TODO We would need OnForkEvent and then simply remove some internal
2443 * TODO management; we don't have this, therefore we need global
2444 * TODO n_readctl_read_overlay to be accessible via =NULL, and to make that
2445 * TODO work in turn we need an instance for default STDIN! Sigh. */
2446 static union{
2447 u64 alignme;
2448 u8 buf[VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name)+1 +1];
2449 } a;
2450 static struct a_go_readctl_ctx *a_stdin;
2451
2452 struct a_go_readctl_ctx *grcp;
2453 char const *emsg;
2454 enum{
2455 a_NONE = 0,
2456 a_ERR = 1u<<0,
2457 a_SET = 1u<<1,
2458 a_CREATE = 1u<<2,
2459 a_REMOVE = 1u<<3
2460 } f;
2461 struct mx_cmd_arg *cap;
2462 struct mx_cmd_arg_ctx *cacp;
2463 NYD_IN;
2464
2465 if(a_stdin == NULL){
2466 a_stdin = (struct a_go_readctl_ctx*)(void*)a.buf;
2467 a_stdin->grc_name[0] = '-';
2468 n_readctl_read_overlay = a_stdin;
2469 }
2470
2471 n_pstate_err_no = su_ERR_NONE;
2472 cacp = vp;
2473 cap = cacp->cac_arg;
2474
2475 if(cacp->cac_no == 0 ||
2476 su_cs_starts_with_case("show", cap->ca_arg.ca_str.s))
2477 goto jshow;
2478 else if(su_cs_starts_with_case("set", cap->ca_arg.ca_str.s))
2479 f = a_SET;
2480 else if(su_cs_starts_with_case("create", cap->ca_arg.ca_str.s))
2481 f = a_CREATE;
2482 else if(su_cs_starts_with_case("remove", cap->ca_arg.ca_str.s))
2483 f = a_REMOVE;
2484 else{
2485 emsg = N_("readctl: invalid subcommand: %s\n");
2486 goto jeinval_quote;
2487 }
2488
2489 if(cacp->cac_no == 1){ /* TODO better option parser <> subcommand */
2490 n_err(_("readctl: %s: requires argument\n"), cap->ca_arg.ca_str.s);
2491 goto jeinval;
2492 }
2493 cap = cap->ca_next;
2494
2495 /* - is special TODO unfortunately also regarding storage */
2496 if(cap->ca_arg.ca_str.l == 1 && *cap->ca_arg.ca_str.s == '-'){
2497 if(f & (a_CREATE | a_REMOVE)){
2498 n_err(_("readctl: cannot create nor remove -\n"));
2499 goto jeinval;
2500 }
2501 n_readctl_read_overlay = a_stdin;
2502 goto jleave;
2503 }
2504
2505 /* Try to find a yet existing instance */
2506 if((grcp = n_readctl_read_overlay) != NULL){
2507 for(; grcp != NULL; grcp = grcp->grc_next)
2508 if(!su_cs_cmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2509 goto jfound;
2510 for(grcp = n_readctl_read_overlay; (grcp = grcp->grc_last) != NULL;)
2511 if(!su_cs_cmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2512 goto jfound;
2513 }
2514
2515 if(f & (a_SET | a_REMOVE)){
2516 emsg = N_("readctl: no such channel: %s\n");
2517 goto jeinval_quote;
2518 }
2519
2520 jfound:
2521 if(f & a_SET)
2522 n_readctl_read_overlay = grcp;
2523 else if(f & a_REMOVE){
2524 if(n_readctl_read_overlay == grcp)
2525 n_readctl_read_overlay = a_stdin;
2526
2527 if(grcp->grc_last != NULL)
2528 grcp->grc_last->grc_next = grcp->grc_next;
2529 if(grcp->grc_next != NULL)
2530 grcp->grc_next->grc_last = grcp->grc_last;
2531 fclose(grcp->grc_fp);
2532 n_free(grcp);
2533 }else{
2534 FILE *fp;
2535 uz elen;
2536 s32 fd;
2537
2538 if(grcp != NULL){
2539 n_err(_("readctl: channel already exists: %s\n"), /* TODO reopen */
2540 n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2541 n_pstate_err_no = su_ERR_EXIST;
2542 f = a_ERR;
2543 goto jleave;
2544 }
2545
2546 if((su_idec_s32_cp(&fd, cap->ca_arg.ca_str.s, 0, NULL
2547 ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
2548 ) != su_IDEC_STATE_CONSUMED){
2549 if((emsg = fexpand(cap->ca_arg.ca_str.s, (FEXP_LOCAL_FILE |
2550 FEXP_NVAR))) == NIL){
2551 emsg = N_("readctl: cannot expand filename %s\n");
2552 goto jeinval_quote;
2553 }
2554 fd = -1;
2555 elen = su_cs_len(emsg);
2556 fp = mx_fs_open(emsg, "&r");
2557 }else if(fd == STDIN_FILENO || fd == STDOUT_FILENO ||
2558 fd == STDERR_FILENO){
2559 emsg = N_("readctl: create: standard descriptors not allowed: %s\n");
2560 goto jeinval_quote;
2561 }else{
2562 /* xxx Avoid */
2563 if(!mx_FS_FD_CLOEXEC_SET(fd)){
2564 emsg = N_("readctl: create: "
2565 "cannot set close-on-exec flag for: %s\n");
2566 goto jeinval_quote;
2567 }
2568 emsg = NIL;
2569 elen = 0;
2570 fp = fdopen(fd, "r");
2571 }
2572
2573 if(fp != NULL){
2574 uz i;
2575
2576 if((i = UZ_MAX - elen) <= cap->ca_arg.ca_str.l ||
2577 (i -= cap->ca_arg.ca_str.l) <=
2578 VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +2){
2579 fclose(fp);
2580 n_err(_("readctl: failed to create storage for %s\n"),
2581 cap->ca_arg.ca_str.s);
2582 n_pstate_err_no = su_ERR_OVERFLOW;
2583 f = a_ERR;
2584 goto jleave;
2585 }
2586
2587 grcp = n_alloc(VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +
2588 cap->ca_arg.ca_str.l +1 + elen +1);
2589 grcp->grc_last = NULL;
2590 if((grcp->grc_next = n_readctl_read_overlay) != NULL)
2591 grcp->grc_next->grc_last = grcp;
2592 n_readctl_read_overlay = grcp;
2593 grcp->grc_fp = fp;
2594 grcp->grc_fd = fd;
2595 su_mem_copy(grcp->grc_name, cap->ca_arg.ca_str.s,
2596 cap->ca_arg.ca_str.l +1);
2597 if(elen == 0)
2598 grcp->grc_expand = NULL;
2599 else{
2600 char *cp;
2601
2602 grcp->grc_expand = cp = &grcp->grc_name[cap->ca_arg.ca_str.l +1];
2603 su_mem_copy(cp, emsg, ++elen);
2604 }
2605 }else{
2606 emsg = N_("readctl: failed to create file for %s\n");
2607 goto jeinval_quote;
2608 }
2609 }
2610
2611 jleave:
2612 NYD_OU;
2613 return (f & a_ERR) ? 1 : 0;
2614 jeinval_quote:
2615 n_err(V_(emsg), n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2616 jeinval:
2617 n_pstate_err_no = su_ERR_INVAL;
2618 f = a_ERR;
2619 goto jleave;
2620
2621 jshow:
2622 if((grcp = n_readctl_read_overlay) == NULL)
2623 fprintf(n_stdout, _("readctl: no channels registered\n"));
2624 else{
2625 while(grcp->grc_last != NULL)
2626 grcp = grcp->grc_last;
2627
2628 fprintf(n_stdout, _("readctl: registered channels:\n"));
2629 for(; grcp != NULL; grcp = grcp->grc_next)
2630 fprintf(n_stdout, _("%c%s %s%s%s%s\n"),
2631 (grcp == n_readctl_read_overlay ? '*' : ' '),
2632 (grcp->grc_fd != -1 ? _("descriptor") : _("name")),
2633 n_shexp_quote_cp(grcp->grc_name, FAL0),
2634 (grcp->grc_expand != NULL ? " (" : n_empty),
2635 (grcp->grc_expand != NULL ? grcp->grc_expand : n_empty),
2636 (grcp->grc_expand != NULL ? ")" : n_empty));
2637 }
2638 f = a_NONE;
2639 goto jleave;
2640 }
2641
2642 #include "su/code-ou.h"
2643 /* s-it-mode */
2644