sopen(): use n_PO_D_V for verbosity, not n_PO_VERB
[s-mailx.git] / go.c
blob4f9f08885c89a17c3988af2c0c047af1919496e7
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 a_GO_XCALL_IS_CALL = 1u<<24, /* n_GO_INPUT_NO_XCALL */
89 /* `xcall' optimization barrier: n_go_macro() has been finished with
90 * a `xcall' request, and `xcall' set this in the parent a_go_input of the
91 * said n_go_macro() to indicate a barrier: we teardown the a_go_input of
92 * the n_go_macro() away after leaving its _event_loop(), but then,
93 * back in n_go_macro(), that enters a for(;;) loop that directly calls
94 * c_call() -- our `xcall' stack avoidance optimization --, yet this call
95 * will itself end up in a new n_go_macro(), and if that again ends up with
96 * `xcall' this should teardown and leave its own n_go_macro(), unrolling the
97 * stack "up to the barrier level", but which effectively still is the
98 * n_go_macro() that lost its a_go_input and is looping the `xcall'
99 * optimization loop. If no `xcall' is desired that loop is simply left and
100 * the _event_loop() of the outer a_go_ctx will perform a loop tick and
101 * clear this bit again OR become teardown itself */
102 a_GO_XCALL_LOOP = 1u<<25, /* `xcall' optimization barrier level */
103 a_GO_XCALL_LOOP_ERROR = 1u<<26, /* .. state machine error transporter */
104 a_GO_XCALL_LOOP_MASK = a_GO_XCALL_LOOP | a_GO_XCALL_LOOP_ERROR
107 enum a_go_cleanup_mode{
108 a_GO_CLEANUP_UNWIND = 1u<<0, /* Teardown all contexts except outermost */
109 a_GO_CLEANUP_TEARDOWN = 1u<<1, /* Teardown current context */
110 a_GO_CLEANUP_LOOPTICK = 1u<<2, /* Normal looptick cleanup */
111 a_GO_CLEANUP_MODE_MASK = n_BITENUM_MASK(0, 2),
113 a_GO_CLEANUP_ERROR = 1u<<8, /* Error occurred on level */
114 a_GO_CLEANUP_SIGINT = 1u<<9, /* Interrupt signal received */
115 a_GO_CLEANUP_HOLDALLSIGS = 1u<<10 /* hold_all_sigs() active TODO */
118 enum a_go_hist_flags{
119 a_GO_HIST_NONE = 0,
120 a_GO_HIST_ADD = 1u<<0,
121 a_GO_HIST_GABBY = 1u<<1,
122 a_GO_HIST_INIT = 1u<<2
125 struct a_go_eval_ctx{
126 struct str gec_line; /* The terminated data to _evaluate() */
127 ui32_t gec_line_size; /* May be used to store line memory size */
128 bool_t gec_ever_seen; /* Has ever been used (main_loop() only) */
129 ui8_t gec__dummy[2];
130 ui8_t gec_hist_flags; /* enum a_go_hist_flags */
131 char const *gec_hist_cmd; /* If a_GO_HIST_ADD only, cmd and args */
132 char const *gec_hist_args;
135 struct a_go_input_inject{
136 struct a_go_input_inject *gii_next;
137 size_t gii_len;
138 bool_t gii_commit;
139 bool_t gii_no_history;
140 char gii_dat[n_VFIELD_SIZE(6)];
143 struct a_go_ctx{
144 struct a_go_ctx *gc_outer;
145 sigset_t gc_osigmask;
146 ui32_t gc_flags; /* enum a_go_flags */
147 ui32_t gc_loff; /* Pseudo (macro): index in .gc_lines */
148 char **gc_lines; /* Pseudo content, lines unfolded */
149 FILE *gc_file; /* File we were in, if applicable */
150 struct a_go_input_inject *gc_inject; /* To be consumed first */
151 void (*gc_on_finalize)(void *);
152 void *gc_finalize_arg;
153 sigjmp_buf gc_eloop_jmp; /* TODO one day... for _event_loop() */
154 /* SPLICE hacks: saved stdin/stdout, saved pstate */
155 FILE *gc_splice_stdin;
156 FILE *gc_splice_stdout;
157 ui32_t gc_splice_psonce;
158 ui8_t gc_splice__dummy[4];
159 struct n_go_data_ctx gc_data;
160 char gc_name[n_VFIELD_SIZE(0)]; /* Name of file or macro */
163 struct a_go_readctl_ctx{ /* TODO localize n_readctl_overlay, use OnForkEvent! */
164 struct a_go_readctl_ctx *grc_last;
165 struct a_go_readctl_ctx *grc_next;
166 char const *grc_expand; /* If filename based, expanded string */
167 FILE *grc_fp;
168 si32_t grc_fd; /* Based upon file-descriptor */
169 char grc_name[n_VFIELD_SIZE(4)]; /* User input for identification purposes */
172 static sighandler_type a_go_oldpipe;
173 /* a_go_cmd_tab[] after fun protos */
175 /* Our current execution context, and the buffer backing the outermost level */
176 static struct a_go_ctx *a_go_ctx;
178 #define a_GO_MAINCTX_NAME "Main event loop"
179 static union{
180 ui64_t align;
181 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
182 sizeof(a_GO_MAINCTX_NAME)];
183 } a_go__mainctx_b;
185 /* `xcall' stack-avoidance bypass optimization. This actually is
186 * a n_cmd_arg_save_to_heap() buffer with n_cmd_arg_ctx.cac_indat misused to
187 * point to the a_go_ctx to unroll up to */
188 static void *a_go_xcall;
190 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
192 /* n_PS_STATE_PENDMASK requires some actions */
193 static void a_go_update_pstate(void);
195 /* Evaluate a single command */
196 static bool_t a_go_evaluate(struct a_go_eval_ctx *gecp);
198 /* Branch here on hangup signal and simulate "exit" */
199 static void a_go_hangup(int s);
201 /* The following gets called on receipt of an interrupt */
202 static void a_go_onintr(int s);
204 /* Cleanup current execution context, update the program state.
205 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
206 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we hold_all_sigs() */
207 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
209 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
210 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
211 * upon evaluation error, and TRU1 on success */
212 static bool_t a_go_file(char const *file, bool_t silent_open_error);
214 /* System resource file load()ing or -X command line option array traversal */
215 static bool_t a_go_load(struct a_go_ctx *gcp);
217 /* A simplified command loop for recursed state machines */
218 static bool_t a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
220 static void
221 a_go_update_pstate(void){
222 bool_t act;
223 NYD_ENTER;
225 act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
226 n_pstate &= ~n_PS_PSTATE_PENDMASK;
228 if(act){
229 char buf[32];
231 snprintf(buf, sizeof buf, "%d", n_scrnwidth);
232 ok_vset(COLUMNS, buf);
233 snprintf(buf, sizeof buf, "%d", n_scrnheight);
234 ok_vset(LINES, buf);
236 NYD_LEAVE;
239 static bool_t
240 a_go_evaluate(struct a_go_eval_ctx *gecp){
241 /* xxx old style(9), but also old code */
242 /* TODO a_go_evaluate() should be splitted in multiple subfunctions,
243 * TODO `eval' should be a prefix, etc., a Ctx should be passed along */
244 struct str line;
245 struct n_string s, *sp;
246 struct str const *alias_exp;
247 char _wordbuf[2], **arglist_base/*[n_MAXARGC]*/, **arglist, *cp, *word;
248 char const *alias_name;
249 struct n_cmd_desc const *cdp;
250 si32_t nerrn, nexn; /* TODO n_pstate_ex_no -> si64_t! */
251 int rv, c;
252 enum{
253 a_NONE = 0,
254 a_ALIAS_MASK = n_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
255 a_NOPREFIX = 1u<<4, /* Modifier prefix not allowed right now */
256 a_NOALIAS = 1u<<5, /* "No alias!" expansion modifier */
257 /* New modifier prefixes must be reflected in a_go_c_alias()! */
258 a_IGNERR = 1u<<6, /* ignerr modifier prefix */
259 a_WYSH = 1u<<7, /* XXX v15+ drop wysh modifier prefix */
260 a_VPUT = 1u<<8, /* vput modifier prefix */
261 a_MODE_MASK = n_BITENUM_MASK(5, 8),
262 a_NO_ERRNO = 1u<<16 /* Don't set n_pstate_err_no */
263 } flags;
264 NYD_ENTER;
266 n_exit_status = n_EXIT_OK;
268 flags = a_NONE;
269 rv = 1;
270 nerrn = n_ERR_NONE;
271 nexn = n_EXIT_OK;
272 cdp = NULL;
273 alias_name = NULL;
274 n_UNINIT(alias_exp, NULL);
275 arglist =
276 arglist_base = n_autorec_alloc(sizeof(*arglist_base) * n_MAXARGC);
277 line = gecp->gec_line; /* TODO const-ify original (buffer)! */
278 assert(line.s[line.l] == '\0');
280 if(line.l > 0 && spacechar(line.s[0]))
281 gecp->gec_hist_flags = a_GO_HIST_NONE;
282 else if(gecp->gec_hist_flags & a_GO_HIST_ADD)
283 gecp->gec_hist_cmd = gecp->gec_hist_args = NULL;
284 sp = NULL;
286 /* Aliases that refer to shell commands or macro expansion restart */
287 jrestart:
288 if(n_str_trim_ifs(&line, TRU1)->l == 0){
289 line.s[0] = '\0';
290 gecp->gec_hist_flags = a_GO_HIST_NONE;
291 goto jempty;
293 (cp = line.s)[line.l] = '\0';
295 /* No-expansion modifier? */
296 if(!(flags & a_NOPREFIX) && *cp == '\\'){
297 line.s = ++cp;
298 --line.l;
299 flags |= a_NOALIAS;
302 /* Note: adding more special treatments must be reflected in the `help' etc.
303 * output in cmd-tab.c! */
305 /* Ignore null commands (comments) */
306 if(*cp == '#'){
307 gecp->gec_hist_flags = a_GO_HIST_NONE;
308 goto jret0;
311 /* Handle ! differently to get the correct lexical conventions */
312 if(*cp == '!')
313 ++cp;
314 /* Isolate the actual command; since it may not necessarily be
315 * separated from the arguments (as in `p1') we need to duplicate it to
316 * be able to create a NUL terminated version.
317 * We must be aware of several special one letter commands here */
318 else if((cp = n_UNCONST(n_cmd_isolate(cp))) == line.s &&
319 (*cp == '|' || *cp == '?'))
320 ++cp;
321 c = (int)PTR2SIZE(cp - line.s);
322 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
323 memcpy(word, arglist[0] = line.s, c);
324 word[c] = '\0';
325 line.l -= c;
326 line.s = cp;
328 /* It may be a modifier.
329 * Note: adding modifiers must be reflected in commandalias handling code */
330 if(c == sizeof("ignerr") -1 && !asccasecmp(word, "ignerr")){
331 flags |= a_NOPREFIX | a_IGNERR;
332 goto jrestart;
333 }else if(c == sizeof("wysh") -1 && !asccasecmp(word, "wysh")){
334 flags |= a_NOPREFIX | a_WYSH;
335 goto jrestart;
336 }else if(c == sizeof("vput") -1 && !asccasecmp(word, "vput")){
337 flags |= a_NOPREFIX | a_VPUT;
338 goto jrestart;
341 /* We need to trim for a possible history entry, but do it anyway and insert
342 * a space for argument separation in case of alias expansion. Also, do
343 * terminate again because nothing prevents aliases from introducing WS */
344 n_str_trim_ifs(&line, TRU1);
345 line.s[line.l] = '\0';
347 /* Lengthy history entry setup, possibly even redundant. But having
348 * normalized history entries is a good thing, and this is maybe still
349 * cheaper than parsing a StrList of words per se */
350 if((gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
351 ) == a_GO_HIST_ADD){
352 if(line.l > 0){
353 sp = n_string_creat_auto(&s);
354 sp = n_string_assign_buf(sp, line.s, line.l);
355 gecp->gec_hist_args = n_string_cp(sp);
358 sp = n_string_creat_auto(&s);
359 sp = n_string_reserve(sp, 32);
361 if(flags & a_NOALIAS)
362 sp = n_string_push_c(sp, '\\');
363 if(flags & a_IGNERR)
364 sp = n_string_push_buf(sp, "ignerr ", sizeof("ignerr ") -1);
365 if(flags & a_WYSH)
366 sp = n_string_push_buf(sp, "wysh ", sizeof("wysh ") -1);
367 if(flags & a_VPUT)
368 sp = n_string_push_buf(sp, "vput ", sizeof("vput ") -1);
369 gecp->gec_hist_flags = a_GO_HIST_ADD | a_GO_HIST_INIT;
372 /* Look up the command; if not found, bitch.
373 * Normally, a blank command would map to the first command in the
374 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
375 * confusion; act just the same for aliases */
376 if(*word == '\0'){
377 jempty:
378 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
379 alias_name != NULL){
380 gecp->gec_hist_flags = a_GO_HIST_NONE;
381 goto jret0;
383 cdp = n_cmd_default();
384 goto jexec;
387 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
388 ui8_t expcnt;
390 expcnt = (flags & a_ALIAS_MASK);
391 ++expcnt;
392 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
394 /* Avoid self-recursion; yes, the user could use \ no-expansion, but.. */
395 if(alias_name != NULL && !strcmp(word, alias_name)){
396 if(n_poption & n_PO_D_V)
397 n_err(_("Actively avoiding self-recursion of `commandalias': %s\n"),
398 word);
399 }else if((alias_name = n_commandalias_exists(word, &alias_exp)) != NULL){
400 size_t i;
402 if(sp != NULL){
403 sp = n_string_push_cp(sp, word);
404 gecp->gec_hist_cmd = n_string_cp(sp);
405 sp = NULL;
408 /* And join arguments onto alias expansion */
409 alias_name = word;
410 i = alias_exp->l;
411 cp = line.s;
412 line.s = n_autorec_alloc(i + 1 + line.l +1);
413 memcpy(line.s, alias_exp->s, i);
414 if(line.l > 0){
415 line.s[i++] = ' ';
416 memcpy(&line.s[i], cp, line.l);
418 line.s[i += line.l] = '\0';
419 line.l = i;
420 goto jrestart;
424 if((cdp = n_cmd_firstfit(word)) == NULL){
425 bool_t doskip;
427 if(!(doskip = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
428 n_err(_("Unknown command%s: `%s'\n"),
429 (doskip ? _(" (ignored due to `if' condition)") : n_empty),
430 prstr(word));
431 gecp->gec_hist_flags = a_GO_HIST_NONE;
432 if(doskip)
433 goto jret0;
434 nerrn = n_ERR_NOSYS;
435 goto jleave;
438 /* See if we should execute the command -- if a conditional we always
439 * execute it, otherwise, check the state of cond */
440 jexec:
441 if(!(cdp->cd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip()){
442 gecp->gec_hist_flags = a_GO_HIST_NONE;
443 goto jret0;
446 if(sp != NULL){
447 sp = n_string_push_cp(sp, cdp->cd_name);
448 gecp->gec_hist_cmd = n_string_cp(sp);
449 sp = NULL;
452 nerrn = n_ERR_INVAL;
454 /* Process the arguments to the command, depending on the type it expects */
455 if((cdp->cd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
456 !(n_poption & n_PO_BATCH_FLAG)){
457 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
458 cdp->cd_name);
459 goto jleave;
461 if(!(cdp->cd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
462 n_err(_("May not execute `%s' while sending\n"), cdp->cd_name);
463 goto jleave;
465 if(cdp->cd_caflags & n_CMD_ARG_R){
466 if(n_pstate & n_PS_COMPOSE_MODE){
467 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
468 n_err(_("Cannot invoke `%s' when in compose mode\n"), cdp->cd_name);
469 goto jleave;
471 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
472 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
473 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING)){
474 n_err(_("Cannot invoke `%s' from a macro or during file inclusion\n"),
475 cdp->cd_name);
476 goto jleave;
479 if((cdp->cd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED)){
480 n_err(_("May not execute `%s' during startup\n"), cdp->cd_name);
481 goto jleave;
483 if(!(cdp->cd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
484 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
485 cdp->cd_name);
486 goto jleave;
489 if((cdp->cd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
490 n_err(_("Cannot execute `%s' without active mailbox\n"), cdp->cd_name);
491 goto jleave;
493 if((cdp->cd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
494 n_err(_("May not execute `%s' -- message file is read only\n"),
495 cdp->cd_name);
496 goto jleave;
499 if(cdp->cd_caflags & n_CMD_ARG_O)
500 n_OBSOLETE2(_("this command will be removed"), cdp->cd_name);
502 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
503 * TODO doesn't use any of those list commands which strip this mask,
504 * TODO and for now we misuse bits for checking relation to history;
505 * TODO argument state should be property of a per-command carrier instead */
506 n_pstate &= ~n_PS_ARGLIST_MASK;
508 if((flags & a_WYSH) &&
509 (cdp->cd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
510 n_err(_("`wysh' prefix does not affect `%s'\n"), cdp->cd_name);
511 flags &= ~a_WYSH;
514 if(flags & a_VPUT){
515 if(cdp->cd_caflags & n_CMD_ARG_V){
516 char const *emsg;
518 emsg = line.s; /* xxx Cannot pass &char* as char const**, so no cp */
519 arglist[0] = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIM_SPACE |
520 n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_LOG |
521 n_SHEXP_PARSE_META_KEEP), &emsg);
522 line.l -= PTR2SIZE(emsg - line.s);
523 line.s = cp = n_UNCONST(emsg);
524 if(cp == NULL)
525 emsg = N_("could not parse input token");
526 else if(!n_shexp_is_valid_varname(arglist[0]))
527 emsg = N_("not a valid variable name");
528 else if(!n_var_is_user_writable(arglist[0]))
529 emsg = N_("either not a user writable, or a boolean variable");
530 else
531 emsg = NULL;
532 if(emsg != NULL){
533 n_err("`%s': vput: %s: %s\n",
534 cdp->cd_name, V_(emsg), n_shexp_quote_cp(arglist[0], FAL0));
535 nerrn = n_ERR_NOTSUP;
536 rv = -1;
537 goto jleave;
539 ++arglist;
540 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
541 * TODO on in getrawlist() etc., i.e., the argument vector producers,
542 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
543 }else{
544 n_err(_("`vput' prefix does not affect `%s'\n"), cdp->cd_name);
545 flags &= ~a_VPUT;
549 switch(cdp->cd_caflags & n_CMD_ARG_TYPE_MASK){
550 case n_CMD_ARG_TYPE_MSGLIST:
551 /* Message list defaulting to nearest forward legal message */
552 if(n_msgvec == NULL)
553 goto jemsglist;
554 if((c = getmsglist(line.s, n_msgvec, cdp->cd_msgflag)) < 0){
555 nerrn = n_ERR_NOMSG;
556 flags |= a_NO_ERRNO;
557 break;
559 if(c == 0){
560 if((n_msgvec[0] = first(cdp->cd_msgflag, cdp->cd_msgmask)) != 0)
561 n_msgvec[1] = 0;
563 if(n_msgvec[0] == 0){
564 jemsglist:
565 if(!(n_pstate & n_PS_HOOK_MASK))
566 fprintf(n_stdout, _("No applicable messages\n"));
567 nerrn = n_ERR_NOMSG;
568 flags |= a_NO_ERRNO;
569 break;
571 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
572 n_err_no = 0;
573 rv = (*cdp->cd_func)(n_msgvec);
574 break;
576 case n_CMD_ARG_TYPE_NDMLIST:
577 /* Message list with no defaults, but no error if none exist */
578 if(n_msgvec == NULL)
579 goto jemsglist;
580 if((c = getmsglist(line.s, n_msgvec, cdp->cd_msgflag)) < 0){
581 nerrn = n_ERR_NOMSG;
582 flags |= a_NO_ERRNO;
583 break;
585 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
586 n_err_no = 0;
587 rv = (*cdp->cd_func)(n_msgvec);
588 break;
590 case n_CMD_ARG_TYPE_STRING:
591 /* Just the straight string, old style, with leading blanks removed */
592 for(cp = line.s; spacechar(*cp);)
593 ++cp;
594 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
595 n_err_no = 0;
596 rv = (*cdp->cd_func)(cp);
597 break;
598 case n_CMD_ARG_TYPE_RAWDAT:
599 /* Just the straight string, placed in argv[] */
600 *arglist++ = line.s;
601 *arglist = NULL;
602 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
603 n_err_no = 0;
604 rv = (*cdp->cd_func)(arglist_base);
605 break;
607 case n_CMD_ARG_TYPE_WYSH:
608 c = 1;
609 if(0){
610 /* FALLTHRU */
611 case n_CMD_ARG_TYPE_WYRA:
612 c = (flags & a_WYSH) ? 1 : 0;
613 if(0){
614 case n_CMD_ARG_TYPE_RAWLIST:
615 c = 0;
618 if((c = getrawlist((c != 0), arglist,
619 n_MAXARGC - PTR2SIZE(arglist - arglist_base), line.s, line.l)) < 0){
620 n_err(_("Invalid argument list\n"));
621 flags |= a_NO_ERRNO;
622 break;
625 if(c < cdp->cd_minargs){
626 n_err(_("`%s' requires at least %u arg(s)\n"),
627 cdp->cd_name, (ui32_t)cdp->cd_minargs);
628 flags |= a_NO_ERRNO;
629 break;
631 #undef cd_minargs
632 if(c > cdp->cd_maxargs){
633 n_err(_("`%s' takes no more than %u arg(s)\n"),
634 cdp->cd_name, (ui32_t)cdp->cd_maxargs);
635 flags |= a_NO_ERRNO;
636 break;
638 #undef cd_maxargs
640 if(flags & a_VPUT)
641 n_pstate |= n_PS_ARGMOD_VPUT;
643 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
644 n_err_no = 0;
645 rv = (*cdp->cd_func)(arglist_base);
646 if(a_go_xcall != NULL)
647 goto jret0;
648 break;
650 case n_CMD_ARG_TYPE_ARG:{
651 /* TODO The _ARG_TYPE_ARG is preliminary, in the end we should have a
652 * TODO per command-ctx carrier that also has slots for it arguments,
653 * TODO and that should be passed along all the way. No more arglists
654 * TODO here, etc. */
655 struct n_cmd_arg_ctx cac;
657 cac.cac_desc = cdp->cd_cadp;
658 cac.cac_indat = line.s;
659 cac.cac_inlen = line.l;
660 if(!n_cmd_arg_parse(&cac)){
661 flags |= a_NO_ERRNO;
662 break;
665 if(flags & a_VPUT){
666 cac.cac_vput = *arglist_base;
667 n_pstate |= n_PS_ARGMOD_VPUT;
668 }else
669 cac.cac_vput = NULL;
671 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
672 n_err_no = 0;
673 rv = (*cdp->cd_func)(&cac);
674 if(a_go_xcall != NULL)
675 goto jret0;
676 } break;
678 default:
679 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
680 cdp->cd_caflags & n_CMD_ARG_TYPE_MASK); )
681 nerrn = n_ERR_NOTOBACCO;
682 nexn = 1;
683 goto jret0;
686 if(gecp->gec_hist_flags & a_GO_HIST_ADD){
687 if(cdp->cd_caflags & n_CMD_ARG_H)
688 gecp->gec_hist_flags = a_GO_HIST_NONE;
689 else if((cdp->cd_caflags & n_CMD_ARG_G) ||
690 (n_pstate & n_PS_MSGLIST_GABBY))
691 gecp->gec_hist_flags |= a_GO_HIST_GABBY;
694 if(rv != 0){
695 if(!(flags & a_NO_ERRNO)){
696 if(cdp->cd_caflags & n_CMD_ARG_EM)
697 flags |= a_NO_ERRNO;
698 else if((nerrn = n_err_no) == 0)
699 nerrn = n_ERR_INVAL;
700 }else
701 flags ^= a_NO_ERRNO;
702 }else if(cdp->cd_caflags & n_CMD_ARG_EM)
703 flags |= a_NO_ERRNO;
704 else
705 nerrn = n_ERR_NONE;
706 jleave:
707 nexn = rv;
709 if(flags & a_IGNERR){
710 n_pstate &= ~n_PS_ERR_EXIT_MASK;
711 n_exit_status = n_EXIT_OK;
712 }else if(rv != 0){
713 bool_t bo;
715 if((bo = ok_blook(batch_exit_on_error))){
716 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
717 if(!(n_poption & n_PO_BATCH_FLAG))
718 bo = FAL0;
720 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
721 n_pstate |= n_PS_ERR_QUIT;
722 else if(ok_blook(posix)){
723 if(n_psonce & n_PSO_STARTED)
724 rv = 0;
725 else if(!(n_psonce & n_PSO_INTERACTIVE))
726 n_pstate |= n_PS_ERR_XIT;
727 }else
728 rv = 0;
730 if(rv != 0){
731 if(n_exit_status == n_EXIT_OK)
732 n_exit_status = n_EXIT_ERR;
733 if((n_poption & n_PO_D_V) &&
734 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
735 n_alert(_("Non-interactive, bailing out due to errors "
736 "in startup load phase"));
737 goto jret;
741 if(cdp == NULL)
742 goto jret0;
743 if((cdp->cd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
744 if(visible(dot))
745 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
746 sizeof("\\type") -1);
748 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
749 !(cdp->cd_caflags & n_CMD_ARG_T))
750 n_pstate |= n_PS_SAW_COMMAND;
751 jret0:
752 rv = 0;
753 jret:
754 if(!(flags & a_NO_ERRNO))
755 n_pstate_err_no = nerrn;
756 n_pstate_ex_no = nexn;
757 NYD_LEAVE;
758 return (rv == 0);
761 static void
762 a_go_hangup(int s){
763 NYD_X; /* Signal handler */
764 n_UNUSED(s);
765 /* nothing to do? */
766 exit(n_EXIT_ERR);
769 #ifdef HAVE_IMAP
770 FL void n_go_onintr_for_imap(void){a_go_onintr(0);}
771 #endif
772 static void
773 a_go_onintr(int s){ /* TODO block signals while acting */
774 NYD_X; /* Signal handler */
775 n_UNUSED(s);
777 safe_signal(SIGINT, a_go_onintr);
779 termios_state_reset();
781 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
783 if(interrupts != 1)
784 n_err_sighdl(_("Interrupt\n"));
785 safe_signal(SIGPIPE, a_go_oldpipe);
786 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
789 static void
790 a_go_cleanup(enum a_go_cleanup_mode gcm){
791 /* Signals blocked */
792 struct a_go_ctx *gcp;
793 NYD_ENTER;
795 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
796 hold_all_sigs();
797 jrestart:
798 gcp = a_go_ctx;
800 /* Free input injections of this level first */
801 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
802 struct a_go_input_inject **giipp, *giip;
804 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
805 *giipp = giip->gii_next;
806 n_free(giip);
810 /* Cleanup non-crucial external stuff */
811 n_COLOUR(
812 if(gcp->gc_data.gdc_colour != NULL)
813 n_colour_stack_del(&gcp->gc_data);
816 /* Work the actual context (according to cleanup mode) */
817 if(gcp->gc_outer == NULL){
818 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
819 if(a_go_xcall != NULL){
820 n_free(a_go_xcall);
821 a_go_xcall = NULL;
823 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
824 n_pstate &= ~n_PS_ERR_EXIT_MASK;
825 close_all_files();
826 }else{
827 if(!(n_pstate & n_PS_SOURCING))
828 close_all_files();
831 n_memory_reset();
833 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
834 assert(a_go_xcall == NULL);
835 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
836 assert(gcp->gc_on_finalize == NULL);
837 assert(gcp->gc_data.gdc_colour == NULL);
838 goto jxleave;
839 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
840 n_memory_reset();
841 goto jxleave;
842 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
843 n_stdin = gcp->gc_splice_stdin;
844 n_stdout = gcp->gc_splice_stdout;
845 n_psonce = gcp->gc_splice_psonce;
846 goto jstackpop;
849 /* Cleanup crucial external stuff */
850 if(gcp->gc_data.gdc_ifcond != NULL){
851 n_cnd_if_stack_del(&gcp->gc_data);
852 if(!(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
853 !(gcp->gc_flags & a_GO_FORCE_EOF) && a_go_xcall == NULL &&
854 !(n_psonce & n_PSO_EXIT_MASK)){
855 n_err(_("Unmatched `if' at end of %s %s\n"),
856 ((gcp->gc_flags & a_GO_MACRO
857 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
858 : _("`source'd file"))),
859 gcp->gc_name);
860 gcm |= a_GO_CLEANUP_ERROR;
864 /* Teardown context */
865 if(gcp->gc_flags & a_GO_MACRO){
866 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
867 char **lp;
869 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
870 n_free(*lp);
871 ++gcp->gc_loff;
873 /* Part of gcp's memory chunk, then */
874 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
875 n_free(gcp->gc_lines);
877 }else if(gcp->gc_flags & a_GO_PIPE)
878 /* XXX command manager should -TERM then -KILL instead of hoping
879 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
880 Pclose(gcp->gc_file, TRU1);
881 else if(gcp->gc_flags & a_GO_FILE)
882 Fclose(gcp->gc_file);
884 jstackpop:
885 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
886 if(gcp->gc_data.gdc_mempool != NULL)
887 n_memory_pool_pop(NULL);
888 }else
889 n_memory_reset();
891 n_go_data = &(a_go_ctx = gcp->gc_outer)->gc_data;
892 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
893 (a_GO_MACRO | a_GO_SUPER_MACRO)){
894 n_pstate &= ~n_PS_SOURCING;
895 assert(n_pstate & n_PS_ROBOT);
896 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
897 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
898 else
899 assert(n_pstate & n_PS_ROBOT);
901 if(gcp->gc_on_finalize != NULL)
902 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
904 if(gcm & a_GO_CLEANUP_ERROR){
905 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
906 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
907 goto jerr;
909 jleave:
910 if(gcp->gc_flags & a_GO_FREE)
911 n_free(gcp);
913 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
914 goto jrestart;
916 jxleave:
917 NYD_LEAVE;
918 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
919 rele_all_sigs();
920 return;
922 jerr:
923 /* With *posix* we follow what POSIX says:
924 * Any errors in the start-up file shall either cause mailx to
925 * terminate with a diagnostic message and a non-zero status or to
926 * continue after writing a diagnostic message, ignoring the
927 * remainder of the lines in the start-up file
928 * Print the diagnostic only for the outermost resource unless the user
929 * is debugging or in verbose mode */
930 if((n_poption & n_PO_D_V) ||
931 (!(n_psonce & n_PSO_STARTED) &&
932 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
933 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
934 /* I18N: file inclusion, macro etc. evaluation has been stopped */
935 n_alert(_("Stopped %s %s due to errors%s"),
936 (n_psonce & n_PSO_STARTED
937 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
938 : (gcp->gc_flags & a_GO_MACRO
939 ? (gcp->gc_flags & a_GO_MACRO_CMD
940 ? _("evaluating command") : _("evaluating macro"))
941 : (gcp->gc_flags & a_GO_PIPE
942 ? _("executing `source'd pipe")
943 : (gcp->gc_flags & a_GO_FILE
944 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
946 : (gcp->gc_flags & a_GO_MACRO
947 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
948 ? _("evaluating command line") : _("evaluating macro"))
949 : _("loading initialization resource"))),
950 gcp->gc_name,
951 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
952 goto jleave;
955 static bool_t
956 a_go_file(char const *file, bool_t silent_open_error){
957 struct a_go_ctx *gcp;
958 sigset_t osigmask;
959 size_t nlen;
960 char *nbuf;
961 bool_t ispipe;
962 FILE *fip;
963 NYD_ENTER;
965 fip = NULL;
967 /* Being a command argument file is space-trimmed *//* TODO v15 with
968 * TODO WYRALIST this is no longer necessary true, and for that we
969 * TODO don't set _PARSE_TRIM_SPACE because we cannot! -> cmd-tab.h!! */
970 #if 0
971 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
972 file[--nlen] == '|')))
973 #else
974 ispipe = FAL0;
975 if(!silent_open_error){
976 for(nlen = strlen(file); nlen > 0;){
977 char c;
979 c = file[--nlen];
980 if(!spacechar(c)){
981 if(c == '|'){
982 nbuf = savestrbuf(file, nlen);
983 ispipe = TRU1;
985 break;
989 #endif
991 if(ispipe){
992 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
993 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
994 goto jeopencheck;
995 }else if((nbuf = fexpand(file, FEXP_LOCAL)) == NULL)
996 goto jeopencheck;
997 else if((fip = Fopen(nbuf, "r")) == NULL){
998 jeopencheck:
999 if(!silent_open_error || (n_poption & n_PO_D_V))
1000 n_perr(nbuf, 0);
1001 if(silent_open_error)
1002 fip = (FILE*)-1;
1003 goto jleave;
1006 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1008 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1009 (nlen = strlen(nbuf) +1));
1010 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1012 hold_all_sigs();
1014 gcp->gc_outer = a_go_ctx;
1015 gcp->gc_osigmask = osigmask;
1016 gcp->gc_file = fip;
1017 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1018 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1019 memcpy(gcp->gc_name, nbuf, nlen);
1021 a_go_ctx = gcp;
1022 n_go_data = &gcp->gc_data;
1023 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1024 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1025 fip = NULL;
1026 jleave:
1027 NYD_LEAVE;
1028 return (fip != NULL);
1031 static bool_t
1032 a_go_load(struct a_go_ctx *gcp){
1033 NYD2_ENTER;
1035 assert(!(n_psonce & n_PSO_STARTED));
1036 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1038 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1039 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1041 hold_all_sigs();
1043 /* POSIX:
1044 * Any errors in the start-up file shall either cause mailx to terminate
1045 * with a diagnostic message and a non-zero status or to continue after
1046 * writing a diagnostic message, ignoring the remainder of the lines in
1047 * the start-up file. */
1048 gcp->gc_outer = a_go_ctx;
1049 a_go_ctx = gcp;
1050 n_go_data = &gcp->gc_data;
1051 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1052 n_pstate |= n_PS_ROBOT |
1053 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1055 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1057 rele_all_sigs();
1059 n_go_main_loop();
1060 NYD2_LEAVE;
1061 return (((n_psonce & n_PSO_EXIT_MASK) |
1062 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1065 static void
1066 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1067 NYD_X; /* Signal handler */
1068 n_UNUSED(sig);
1069 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1072 static bool_t
1073 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1074 sighandler_type soldhdl;
1075 struct a_go_eval_ctx gec;
1076 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1077 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1078 sigset_t osigmask;
1079 NYD2_ENTER;
1081 memset(&gec, 0, sizeof gec);
1082 osigmask = gcp->gc_osigmask;
1083 hadint = FAL0;
1084 f = a_RETOK;
1086 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1087 safe_signal(SIGINT, &a_go__eloopint);
1088 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1089 hold_all_sigs();
1090 hadint = TRU1;
1091 f &= ~a_RETOK;
1092 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1093 goto jjump;
1097 for(;; f |= a_TICKED){
1098 int n;
1100 if(f & a_TICKED)
1101 n_memory_reset();
1103 /* Read a line of commands and handle end of file specially */
1104 gec.gec_line.l = gec.gec_line_size;
1105 rele_all_sigs();
1106 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL, NULL);
1107 hold_all_sigs();
1108 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1109 gec.gec_line.l = (ui32_t)n;
1111 if(n < 0)
1112 break;
1114 rele_all_sigs();
1115 assert(gec.gec_hist_flags == a_GO_HIST_NONE);
1116 if(!a_go_evaluate(&gec))
1117 f &= ~a_RETOK;
1118 hold_all_sigs();
1120 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1121 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1122 break;
1125 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1126 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1127 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1128 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1130 if(gec.gec_line.s != NULL)
1131 n_free(gec.gec_line.s);
1133 if(soldhdl != SIG_IGN)
1134 safe_signal(SIGINT, soldhdl);
1135 NYD2_LEAVE;
1136 rele_all_sigs();
1137 if(hadint){
1138 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1139 n_raise(SIGINT);
1141 return (f & a_RETOK);
1144 FL void
1145 n_go_init(void){
1146 struct a_go_ctx *gcp;
1147 NYD2_ENTER;
1149 assert(n_stdin != NULL);
1151 gcp = (void*)a_go__mainctx_b.uf;
1152 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1153 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1154 gcp->gc_file = n_stdin;
1155 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1156 a_go_ctx = gcp;
1157 n_go_data = &gcp->gc_data;
1159 n_child_manager_start();
1160 NYD2_LEAVE;
1163 FL bool_t
1164 n_go_main_loop(void){ /* FIXME */
1165 struct a_go_eval_ctx gec;
1166 int n, eofcnt;
1167 bool_t volatile rv;
1168 NYD_ENTER;
1170 rv = TRU1;
1172 if (!(n_pstate & n_PS_SOURCING)) {
1173 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1174 safe_signal(SIGINT, &a_go_onintr);
1175 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1176 safe_signal(SIGHUP, &a_go_hangup);
1178 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1179 safe_signal(SIGPIPE, a_go_oldpipe);
1181 memset(&gec, 0, sizeof gec);
1183 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1184 hold_all_sigs();
1186 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1187 interrupts = 0;
1189 if(gec.gec_ever_seen)
1190 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1192 if (!(n_pstate & n_PS_SOURCING)) {
1193 char *cp;
1195 /* TODO Note: this buffer may contain a password. We should redefine
1196 * TODO the code flow which has to do that */
1197 if ((cp = termios_state.ts_linebuf) != NULL) {
1198 termios_state.ts_linebuf = NULL;
1199 termios_state.ts_linesize = 0;
1200 n_free(cp); /* TODO pool give-back */
1202 if (gec.gec_line.l > LINESIZE * 3) {
1203 n_free(gec.gec_line.s);
1204 gec.gec_line.s = NULL;
1205 gec.gec_line.l = gec.gec_line_size = 0;
1209 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1210 char *cp;
1212 if ((cp = ok_vlook(newmail)) != NULL) { /* TODO bla */
1213 struct stat st;
1215 if(mb.mb_type == MB_FILE){
1216 if(!stat(mailname, &st) && st.st_size > mailsize) Jnewmail:{
1217 ui32_t odid;
1218 size_t odot;
1220 odot = PTR2SIZE(dot - message);
1221 odid = (n_pstate & n_PS_DID_PRINT_DOT);
1223 rele_all_sigs();
1224 n = setfile(mailname,
1225 (FEDIT_NEWMAIL |
1226 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)));
1227 hold_all_sigs();
1229 if(n < 0) {
1230 n_exit_status |= n_EXIT_ERR;
1231 rv = FAL0;
1232 break;
1234 #ifdef HAVE_IMAP
1235 if(mb.mb_type != MB_IMAP){
1236 #endif
1237 dot = &message[odot];
1238 n_pstate |= odid;
1239 #ifdef HAVE_IMAP
1241 #endif
1243 }else{
1244 n = (cp != NULL && strcmp(cp, "nopoll"));
1246 if(mb.mb_type == MB_MAILDIR){
1247 if(n != 0)
1248 goto Jnewmail;
1250 #ifdef HAVE_IMAP
1251 else if(mb.mb_type == MB_IMAP){
1252 if(!n)
1253 n = (cp != NULL && strcmp(cp, "noimap"));
1255 if(imap_newmail(n) > (cp == NULL))
1256 goto Jnewmail;
1258 #endif
1263 /* Read a line of commands and handle end of file specially */
1264 gec.gec_line.l = gec.gec_line_size;
1265 /* C99 */{
1266 bool_t histadd;
1268 histadd = (!(n_pstate & n_PS_SOURCING) &&
1269 (n_psonce & n_PSO_INTERACTIVE));
1270 rele_all_sigs();
1271 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1272 &gec.gec_line.s, &gec.gec_line.l, NULL, &histadd);
1273 hold_all_sigs();
1275 gec.gec_hist_flags = histadd ? a_GO_HIST_ADD : a_GO_HIST_NONE;
1277 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1278 gec.gec_line.l = (ui32_t)n;
1280 if (n < 0) {
1281 if (!(n_pstate & n_PS_ROBOT) &&
1282 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1283 ++eofcnt < 4) {
1284 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1285 n_go_input_clearerr();
1286 continue;
1288 break;
1291 n_pstate &= ~n_PS_HOOK_MASK;
1292 rele_all_sigs();
1293 rv = a_go_evaluate(&gec);
1294 hold_all_sigs();
1296 if(gec.gec_hist_flags & a_GO_HIST_ADD){
1297 char const *cc, *ca;
1299 cc = gec.gec_hist_cmd;
1300 ca = gec.gec_hist_args;
1301 if(cc != NULL && ca != NULL)
1302 cc = savecatsep(cc, ' ', ca);
1303 else if(ca != NULL)
1304 cc = ca;
1305 n_tty_addhist(cc, ((gec.gec_hist_flags & a_GO_HIST_GABBY) != 0));
1308 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1309 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1310 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1311 default: break;
1313 if(n_psonce & n_PSO_EXIT_MASK)
1314 break;
1316 if(!rv)
1317 break;
1320 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1321 (rv ? 0 : a_GO_CLEANUP_ERROR));
1323 if (gec.gec_line.s != NULL)
1324 n_free(gec.gec_line.s);
1326 rele_all_sigs();
1327 NYD_LEAVE;
1328 return rv;
1331 FL void
1332 n_go_input_clearerr(void){
1333 FILE *fp;
1334 NYD2_ENTER;
1336 fp = NULL;
1338 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1339 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1340 fp = a_go_ctx->gc_file;
1342 if(fp != NULL){
1343 a_go_ctx->gc_flags &= ~a_GO_IS_EOF;
1344 clearerr(fp);
1346 NYD2_LEAVE;
1349 FL void
1350 n_go_input_force_eof(void){
1351 NYD2_ENTER;
1352 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1353 NYD2_LEAVE;
1356 FL bool_t
1357 n_go_input_is_eof(void){
1358 bool_t rv;
1359 NYD2_ENTER;
1361 rv = ((a_go_ctx->gc_flags & a_GO_IS_EOF) != 0);
1362 NYD2_LEAVE;
1363 return rv;
1366 FL void
1367 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1368 size_t len){
1369 NYD_ENTER;
1370 if(len == UIZ_MAX)
1371 len = strlen(buf);
1373 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1374 len > 0){
1375 struct a_go_input_inject *giip, **giipp;
1377 hold_all_sigs();
1379 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1380 ) + 1 + len +1);
1381 giipp = &a_go_ctx->gc_inject;
1382 giip->gii_next = *giipp;
1383 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1384 giip->gii_no_history = ((giif & n_GO_INPUT_INJECT_HISTORY) == 0);
1385 memcpy(&giip->gii_dat[0], buf, len);
1386 giip->gii_dat[giip->gii_len = len] = '\0';
1387 *giipp = giip;
1389 rele_all_sigs();
1391 NYD_LEAVE;
1394 FL int
1395 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1396 size_t *linesize, char const *string, bool_t *histok_or_null
1397 n_MEMORY_DEBUG_ARGS){
1398 /* TODO readline: linebuf pool!; n_go_input should return si64_t */
1399 struct n_string xprompt;
1400 FILE *ifile;
1401 bool_t doprompt, dotty;
1402 char const *iftype;
1403 struct a_go_input_inject *giip;
1404 int nold, n;
1405 bool_t histok;
1406 NYD2_ENTER;
1408 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1409 hold_all_sigs();
1411 histok = FAL0;
1413 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
1414 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1415 n = -1;
1416 goto jleave;
1419 if(gif & n_GO_INPUT_FORCE_STDIN)
1420 goto jforce_stdin;
1422 /* Special case macro mode: never need to prompt, lines have always been
1423 * unfolded already */
1424 if(a_go_ctx->gc_flags & a_GO_MACRO){
1425 if(*linebuf != NULL)
1426 n_free(*linebuf);
1428 /* Injection in progress? Don't care about the autocommit state here */
1429 if((giip = a_go_ctx->gc_inject) != NULL){
1430 a_go_ctx->gc_inject = giip->gii_next;
1432 /* Simply "reuse" allocation, copy string to front of it */
1433 jinject:
1434 *linesize = giip->gii_len;
1435 *linebuf = (char*)giip;
1436 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
1437 iftype = "INJECTION";
1438 }else{
1439 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
1440 *linesize = 0;
1441 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1442 n = -1;
1443 goto jleave;
1446 ++a_go_ctx->gc_loff;
1447 *linesize = strlen(*linebuf);
1448 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
1449 *linebuf = sbufdup(*linebuf, *linesize);
1451 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
1452 ? "-X OPTION"
1453 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
1455 n = (int)*linesize;
1456 n_pstate |= n_PS_READLINE_NL;
1457 goto jhave_dat;
1458 }else{
1459 /* Injection in progress? */
1460 struct a_go_input_inject **giipp;
1462 giipp = &a_go_ctx->gc_inject;
1464 if((giip = *giipp) != NULL){
1465 *giipp = giip->gii_next;
1467 if(giip->gii_commit){
1468 if(*linebuf != NULL)
1469 n_free(*linebuf);
1470 histok = !giip->gii_no_history;
1471 goto jinject; /* (above) */
1472 }else{
1473 string = savestrbuf(giip->gii_dat, giip->gii_len);
1474 n_free(giip);
1479 jforce_stdin:
1480 n_pstate &= ~n_PS_READLINE_NL;
1481 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
1482 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
1483 histok = (n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
1484 (n_PSO_INTERACTIVE | n_PSO_STARTED) && !(n_pstate & n_PS_ROBOT);
1485 doprompt = !(gif & n_GO_INPUT_FORCE_STDIN) && histok;
1486 dotty = (doprompt && !ok_blook(line_editor_disable));
1487 if(!doprompt)
1488 gif |= n_GO_INPUT_PROMPT_NONE;
1489 else{
1490 if(!dotty)
1491 n_string_creat_auto(&xprompt);
1492 if(prompt == NULL)
1493 gif |= n_GO_INPUT_PROMPT_EVAL;
1496 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
1497 if(!dotty && (gif & n_GO_INPUT_PROMPT_NONE))
1498 fflush(n_stdout);
1500 if(gif & n_GO_INPUT_FORCE_STDIN){
1501 struct a_go_readctl_ctx *grcp;
1503 grcp = n_readctl_overlay;
1504 ifile = (grcp == NULL || grcp->grc_fp == NULL) ? n_stdin : grcp->grc_fp;
1505 }else
1506 ifile = a_go_ctx->gc_file;
1507 if(ifile == NULL){
1508 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
1509 (a_go_ctx->gc_flags & a_GO_MACRO));
1510 ifile = n_stdin;
1513 for(nold = n = 0;;){
1514 if(dotty){
1515 assert(ifile == n_stdin);
1516 if(string != NULL && (n = (int)strlen(string)) > 0){
1517 if(*linesize > 0)
1518 *linesize += n +1;
1519 else
1520 *linesize = (size_t)n + LINESIZE +1;
1521 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
1522 memcpy(*linebuf, string, (size_t)n +1);
1524 string = NULL;
1526 rele_all_sigs();
1528 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n, histok_or_null
1529 n_MEMORY_DEBUG_ARGSCALL);
1531 hold_all_sigs();
1532 }else{
1533 if(!(gif & n_GO_INPUT_PROMPT_NONE))
1534 n_tty_create_prompt(&xprompt, prompt, gif);
1536 rele_all_sigs();
1538 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
1539 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
1540 fflush(n_stdout);
1543 n = (readline_restart)(ifile, linebuf, linesize, n
1544 n_MEMORY_DEBUG_ARGSCALL);
1546 hold_all_sigs();
1548 if(n < 0 && feof(ifile))
1549 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1551 if(n > 0 && nold > 0){
1552 char const *cp;
1553 int i;
1555 i = 0;
1556 cp = &(*linebuf)[nold];
1557 while(spacechar(*cp) && n - i >= nold)
1558 ++cp, ++i;
1559 if(i > 0){
1560 memmove(&(*linebuf)[nold], cp, n - nold - i);
1561 n -= i;
1562 (*linebuf)[n] = '\0';
1567 if(n <= 0)
1568 break;
1570 /* POSIX says:
1571 * An unquoted <backslash> at the end of a command line shall
1572 * be discarded and the next line shall continue the command */
1573 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\'){
1574 if(dotty)
1575 n_pstate |= n_PS_READLINE_NL;
1576 break;
1578 /* Definitely outside of quotes, thus the quoting rules are so that an
1579 * uneven number of successive reverse solidus at EOL is a continuation */
1580 if(n > 1){
1581 size_t i, j;
1583 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
1584 if((*linebuf)[i] != '\\')
1585 break;
1586 if(!(j & 1))
1587 break;
1589 (*linebuf)[nold = --n] = '\0';
1590 gif |= n_GO_INPUT_NL_FOLLOW;
1593 if(n < 0)
1594 goto jleave;
1595 (*linebuf)[*linesize = n] = '\0';
1597 jhave_dat:
1598 if(n_poption & n_PO_D_VV)
1599 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
1600 jleave:
1601 if (n_pstate & n_PS_PSTATE_PENDMASK)
1602 a_go_update_pstate();
1604 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
1605 * TODO but only established from the outside and we need to drop this
1606 * TODO overlay context somehow */
1607 if(n < 0 && (a_go_ctx->gc_flags & a_GO_SPLICE))
1608 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
1610 if(histok_or_null != NULL && !histok)
1611 *histok_or_null = FAL0;
1613 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1614 rele_all_sigs();
1615 NYD2_LEAVE;
1616 return n;
1619 FL char *
1620 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
1621 char const *string){
1622 struct n_sigman sm;
1623 bool_t histadd;
1624 size_t linesize;
1625 char *linebuf, * volatile rv;
1626 int n;
1627 NYD2_ENTER;
1629 linesize = 0;
1630 linebuf = NULL;
1631 rv = NULL;
1633 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1634 case 0:
1635 break;
1636 default:
1637 goto jleave;
1640 histadd = TRU1;
1641 n = n_go_input(gif, prompt, &linebuf, &linesize, string, &histadd);
1642 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
1643 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE) &&
1644 histadd)
1645 n_tty_addhist(rv, ((gif & n_GO_INPUT_HIST_GABBY) != 0));
1647 n_sigman_cleanup_ping(&sm);
1648 jleave:
1649 if(linebuf != NULL)
1650 n_free(linebuf);
1651 NYD2_LEAVE;
1652 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1653 return rv;
1656 FL bool_t
1657 n_go_load(char const *name){
1658 struct a_go_ctx *gcp;
1659 size_t i;
1660 FILE *fip;
1661 bool_t rv;
1662 NYD_ENTER;
1664 rv = TRU1;
1666 if(name == NULL || *name == '\0')
1667 goto jleave;
1668 else if((fip = Fopen(name, "r")) == NULL){
1669 if(n_poption & n_PO_D_V)
1670 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
1671 goto jleave;
1674 i = strlen(name) +1;
1675 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
1676 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1678 gcp->gc_file = fip;
1679 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
1680 memcpy(gcp->gc_name, name, i);
1682 if(n_poption & n_PO_D_V)
1683 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
1684 rv = a_go_load(gcp);
1685 jleave:
1686 NYD_LEAVE;
1687 return rv;
1690 FL bool_t
1691 n_go_Xargs(char const **lines, size_t cnt){
1692 static char const name[] = "-X";
1694 union{
1695 bool_t rv;
1696 ui64_t align;
1697 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
1698 } b;
1699 char const *srcp, *xsrcp;
1700 char *cp;
1701 size_t imax, i, len;
1702 struct a_go_ctx *gcp;
1703 NYD_ENTER;
1705 gcp = (void*)b.uf;
1706 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1708 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
1709 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
1710 memcpy(gcp->gc_name, name, sizeof name);
1712 /* The problem being that we want to support reverse solidus newline
1713 * escaping also within multiline -X, i.e., POSIX says:
1714 * An unquoted <backslash> at the end of a command line shall
1715 * be discarded and the next line shall continue the command
1716 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
1717 * entire lines array and set _MACRO_FREE_DATA */
1718 imax = cnt + 1;
1719 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
1721 /* For each of the input lines.. */
1722 for(i = len = 0, cp = NULL; cnt > 0;){
1723 bool_t keep;
1724 size_t j;
1726 if((j = strlen(srcp = *lines)) == 0){
1727 ++lines, --cnt;
1728 continue;
1731 /* Separate one line from a possible multiline input string */
1732 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
1733 *lines = &xsrcp[1];
1734 j = PTR2SIZE(xsrcp - srcp);
1735 }else
1736 ++lines, --cnt;
1738 /* The (separated) string may itself indicate soft newline escaping */
1739 if((keep = (srcp[j - 1] == '\\'))){
1740 size_t xj, xk;
1742 /* Need an uneven number of reverse solidus */
1743 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
1744 if(srcp[xj] != '\\')
1745 break;
1746 if(xk & 1)
1747 --j;
1748 else
1749 keep = FAL0;
1752 /* Strip any leading WS from follow lines, then */
1753 if(cp != NULL)
1754 while(j > 0 && spacechar(*srcp))
1755 ++srcp, --j;
1757 if(j > 0){
1758 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
1759 imax += 4;
1760 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
1761 imax);
1763 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
1764 memcpy(&cp[len], srcp, j);
1765 cp[len += j] = '\0';
1767 if(!keep)
1768 ++i;
1770 if(!keep)
1771 cp = NULL, len = 0;
1773 if(cp != NULL){
1774 assert(i + 1 < imax);
1775 gcp->gc_lines[i++] = cp;
1777 gcp->gc_lines[i] = NULL;
1779 b.rv = a_go_load(gcp);
1780 NYD_LEAVE;
1781 return b.rv;
1784 FL int
1785 c_source(void *v){
1786 int rv;
1787 NYD_ENTER;
1789 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
1790 NYD_LEAVE;
1791 return rv;
1794 FL int
1795 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
1796 int rv;
1797 NYD_ENTER;
1799 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
1800 NYD_LEAVE;
1801 return rv;
1804 FL bool_t
1805 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
1806 void (*on_finalize)(void*), void *finalize_arg){
1807 struct a_go_ctx *gcp;
1808 size_t i;
1809 int rv;
1810 sigset_t osigmask;
1811 NYD_ENTER;
1813 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1815 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1816 (i = strlen(name) +1));
1817 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1819 hold_all_sigs();
1821 gcp->gc_outer = a_go_ctx;
1822 gcp->gc_osigmask = osigmask;
1823 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
1824 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1825 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0) |
1826 ((gif & n_GO_INPUT_NO_XCALL) ? a_GO_XCALL_IS_CALL : 0);
1827 gcp->gc_lines = lines;
1828 gcp->gc_on_finalize = on_finalize;
1829 gcp->gc_finalize_arg = finalize_arg;
1830 memcpy(gcp->gc_name, name, i);
1832 a_go_ctx = gcp;
1833 n_go_data = &gcp->gc_data;
1834 n_pstate |= n_PS_ROBOT;
1835 rv = a_go_event_loop(gcp, gif);
1837 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
1838 if(a_go_xcall != NULL){
1839 void *vp;
1840 struct n_cmd_arg_ctx *cacp;
1842 if(a_go_xcall == (void*)-1)
1843 a_go_xcall = NULL;
1844 else if(((void const*)(cacp = a_go_xcall)->cac_indat) == gcp){
1845 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
1846 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
1847 while(a_go_xcall != NULL){
1848 hold_all_sigs();
1850 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
1852 vp = a_go_xcall;
1853 a_go_xcall = NULL;
1854 cacp = n_cmd_arg_restore_from_heap(vp);
1855 n_free(vp);
1857 rele_all_sigs();
1859 (void)c_call(cacp);
1861 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) == 0);
1862 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1865 NYD_LEAVE;
1866 return rv;
1869 FL bool_t
1870 n_go_command(enum n_go_input_flags gif, char const *cmd){
1871 struct a_go_ctx *gcp;
1872 bool_t rv;
1873 size_t i, ial;
1874 sigset_t osigmask;
1875 NYD_ENTER;
1877 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1879 i = strlen(cmd) +1;
1880 ial = n_ALIGN(i);
1881 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1882 ial + 2*sizeof(char*));
1883 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1885 hold_all_sigs();
1887 gcp->gc_outer = a_go_ctx;
1888 gcp->gc_osigmask = osigmask;
1889 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
1890 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1891 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
1892 gcp->gc_lines = (void*)&gcp->gc_name[ial];
1893 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
1894 gcp->gc_lines[1] = NULL;
1896 a_go_ctx = gcp;
1897 n_go_data = &gcp->gc_data;
1898 n_pstate |= n_PS_ROBOT;
1899 rv = a_go_event_loop(gcp, gif);
1900 NYD_LEAVE;
1901 return rv;
1904 FL void
1905 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
1906 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
1907 struct a_go_ctx *gcp;
1908 size_t i;
1909 sigset_t osigmask;
1910 NYD_ENTER;
1912 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1914 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1915 (i = strlen(cmd) +1));
1916 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1918 hold_all_sigs();
1920 gcp->gc_outer = a_go_ctx;
1921 gcp->gc_osigmask = osigmask;
1922 gcp->gc_file = new_stdin;
1923 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE;
1924 gcp->gc_on_finalize = on_finalize;
1925 gcp->gc_finalize_arg = finalize_arg;
1926 gcp->gc_splice_stdin = n_stdin;
1927 gcp->gc_splice_stdout = n_stdout;
1928 gcp->gc_splice_psonce = n_psonce;
1929 memcpy(gcp->gc_name, cmd, i);
1931 n_stdin = new_stdin;
1932 n_stdout = new_stdout;
1933 n_psonce = new_psonce;
1934 a_go_ctx = gcp;
1935 n_pstate |= n_PS_ROBOT;
1937 rele_all_sigs();
1938 NYD_LEAVE;
1941 FL void
1942 n_go_splice_hack_remove_after_jump(void){
1943 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
1946 FL bool_t
1947 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
1948 struct a_go_ctx *gcp;
1949 bool_t rv;
1950 NYD2_ENTER;
1952 rv = FAL0;
1954 /* Only when startup completed */
1955 if(!(n_psonce & n_PSO_STARTED))
1956 goto jleave;
1957 /* Only interactive or batch mode (assuming that is ok) */
1958 if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
1959 goto jleave;
1961 /* Not when running any hook */
1962 if(n_pstate & n_PS_HOOK_MASK)
1963 goto jleave;
1965 /* Traverse up the stack:
1966 * . not when controlled by a child process
1967 * TODO . not when there are pipes involved, we neither handle job control,
1968 * TODO nor process groups, that is, controlling terminal acceptably
1969 * . not when sourcing a file */
1970 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
1971 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
1972 goto jleave;
1975 rv = TRU1;
1976 jleave:
1977 NYD2_LEAVE;
1978 return rv;
1981 FL int
1982 c_eval(void *vp){
1983 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
1984 * TODO ARGV with shell rules, but if that is not possible then simply
1985 * TODO adjust argv/argc of "the CmdCtx" that we will have "exec" real cmd */
1986 struct a_go_eval_ctx gec;
1987 struct n_string s_b, *sp;
1988 size_t i, j;
1989 char const **argv, *cp;
1990 NYD_ENTER;
1992 argv = vp;
1994 for(j = i = 0; (cp = argv[i]) != NULL; ++i)
1995 j += strlen(cp);
1997 sp = n_string_creat_auto(&s_b);
1998 sp = n_string_reserve(sp, j);
2000 for(i = 0; (cp = argv[i]) != NULL; ++i){
2001 if(i > 0)
2002 sp = n_string_push_c(sp, ' ');
2003 sp = n_string_push_cp(sp, cp);
2006 memset(&gec, 0, sizeof gec);
2007 gec.gec_line.s = n_string_cp(sp);
2008 gec.gec_line.l = sp->s_len;
2009 if(n_poption & n_PO_D_VV)
2010 n_err(_("EVAL %" PRIuZ " bytes <%s>\n"), gec.gec_line.l, gec.gec_line.s);
2011 (void)/* XXX */a_go_evaluate(&gec);
2012 NYD_LEAVE;
2013 return (a_go_xcall != NULL ? 0 : n_pstate_ex_no);
2016 FL int
2017 c_xcall(void *vp){
2018 int rv;
2019 struct a_go_ctx *gcp;
2020 NYD2_ENTER;
2022 /* The context can only be a macro context, except that possibly a single
2023 * level of `eval' (TODO: yet) was used to double-expand our arguments */
2024 if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
2025 gcp = gcp->gc_outer;
2026 if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD)
2027 ) != a_GO_MACRO){
2028 if(n_poption & n_PO_D_V_VV)
2029 n_err(_("`xcall': can only be used inside a macro, using `call'\n"));
2030 rv = c_call(vp);
2031 goto jleave;
2034 /* Try to roll up the stack as much as possible.
2035 * See a_GO_XCALL_LOOP flag description for more */
2036 if(!(gcp->gc_flags & a_GO_XCALL_IS_CALL) && gcp->gc_outer != NULL){
2037 if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
2038 gcp = gcp->gc_outer;
2039 }else{
2040 /* Otherwise this macro is "invoked from the top level", in which case we
2041 * silently act as if we were `call'... */
2042 rv = c_call(vp);
2043 /* ...which means we must ensure the rest of the macro that was us
2044 * doesn't become evaluated! */
2045 a_go_xcall = (void*)-1;
2046 goto jleave;
2049 /* C99 */{
2050 struct n_cmd_arg_ctx *cacp;
2052 cacp = n_cmd_arg_save_to_heap(vp);
2053 cacp->cac_indat = (char*)gcp;
2054 a_go_xcall = cacp;
2056 rv = 0;
2057 jleave:
2058 NYD2_LEAVE;
2059 return rv;
2062 FL int
2063 c_exit(void *vp){
2064 char const **argv;
2065 NYD_ENTER;
2067 if(*(argv = vp) != NULL && (n_idec_si32_cp(&n_exit_status, *argv, 0, NULL) &
2068 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2069 ) != n_IDEC_STATE_CONSUMED)
2070 n_exit_status |= n_EXIT_ERR;
2072 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2073 fflush(NULL);
2074 _exit(n_exit_status);
2075 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2076 n_err(_("`exit' delayed until compose mode is left\n")); /* XXX ..log? */
2077 n_psonce |= n_PSO_XIT;
2078 NYD_LEAVE;
2079 return 0;
2082 FL int
2083 c_quit(void *vp){
2084 char const **argv;
2085 NYD_ENTER;
2087 if(*(argv = vp) != NULL && (n_idec_si32_cp(&n_exit_status, *argv, 0, NULL) &
2088 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2089 ) != n_IDEC_STATE_CONSUMED)
2090 n_exit_status |= n_EXIT_ERR;
2092 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2093 fflush(NULL);
2094 _exit(n_exit_status);
2095 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2096 n_err(_("`exit' delayed until compose mode is left\n")); /* XXX ..log? */
2097 n_psonce |= n_PSO_QUIT;
2098 NYD_LEAVE;
2099 return 0;
2102 FL int
2103 c_readctl(void *vp){
2104 /* TODO We would need OnForkEvent and then simply remove some internal
2105 * TODO management; we don't have this, therefore we need global
2106 * TODO n_readctl_overlay to be accessible via =NULL, and to make that
2107 * TODO work in turn we need an instance for default STDIN! Sigh. */
2108 static ui8_t a_buf[n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name)+1 +1];
2109 static struct a_go_readctl_ctx *a_stdin;
2111 struct a_go_readctl_ctx *grcp;
2112 char const *emsg;
2113 enum{
2114 a_NONE = 0,
2115 a_ERR = 1u<<0,
2116 a_SET = 1u<<1,
2117 a_CREATE = 1u<<2,
2118 a_REMOVE = 1u<<3
2119 } f;
2120 struct n_cmd_arg *cap;
2121 struct n_cmd_arg_ctx *cacp;
2122 NYD_ENTER;
2124 if(a_stdin == NULL){
2125 a_stdin = (struct a_go_readctl_ctx*)a_buf;
2126 a_stdin->grc_name[0] = '-';
2127 n_readctl_overlay = a_stdin;
2130 n_pstate_err_no = n_ERR_NONE;
2131 cacp = vp;
2132 cap = cacp->cac_arg;
2134 if(cacp->cac_no == 0 || is_asccaseprefix(cap->ca_arg.ca_str.s, "show"))
2135 goto jshow;
2136 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "set"))
2137 f = a_SET;
2138 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "create"))
2139 f = a_CREATE;
2140 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "remove"))
2141 f = a_REMOVE;
2142 else{
2143 emsg = N_("`readctl': invalid subcommand: %s\n");
2144 goto jeinval_quote;
2147 if(cacp->cac_no == 1){ /* TODO better option parser <> subcommand */
2148 n_err(_("`readctl': %s: requires argument\n"), cap->ca_arg.ca_str.s);
2149 goto jeinval;
2151 cap = cap->ca_next;
2153 /* - is special TODO unfortunately also regarding storage */
2154 if(cap->ca_arg.ca_str.l == 1 && *cap->ca_arg.ca_str.s == '-'){
2155 if(f & (a_CREATE | a_REMOVE)){
2156 n_err(_("`readctl': cannot create nor remove -\n"));
2157 goto jeinval;
2159 n_readctl_overlay = a_stdin;
2160 goto jleave;
2163 /* Try to find a yet existing instance */
2164 if((grcp = n_readctl_overlay) != NULL){
2165 for(; grcp != NULL; grcp = grcp->grc_next)
2166 if(!strcmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2167 goto jfound;
2168 for(grcp = n_readctl_overlay; (grcp = grcp->grc_last) != NULL;)
2169 if(!strcmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2170 goto jfound;
2173 if(f & (a_SET | a_REMOVE)){
2174 emsg = N_("`readctl': no such channel: %s\n");
2175 goto jeinval_quote;
2178 jfound:
2179 if(f & a_SET)
2180 n_readctl_overlay = grcp;
2181 else if(f & a_REMOVE){
2182 if(n_readctl_overlay == grcp)
2183 n_readctl_overlay = a_stdin;
2185 if(grcp->grc_last != NULL)
2186 grcp->grc_last->grc_next = grcp->grc_next;
2187 if(grcp->grc_next != NULL)
2188 grcp->grc_next->grc_last = grcp->grc_last;
2189 fclose(grcp->grc_fp);
2190 n_free(grcp);
2191 }else{
2192 FILE *fp;
2193 size_t elen;
2194 si32_t fd;
2196 if(grcp != NULL){
2197 n_err(_("`readctl': channel already exists: %s\n"), /* TODO reopen */
2198 n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2199 n_pstate_err_no = n_ERR_EXIST;
2200 f = a_ERR;
2201 goto jleave;
2204 if((n_idec_si32_cp(&fd, cap->ca_arg.ca_str.s, 0, NULL
2205 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2206 ) != n_IDEC_STATE_CONSUMED){
2207 if((emsg = fexpand(cap->ca_arg.ca_str.s, FEXP_LOCAL | FEXP_NVAR)
2208 ) == NULL){
2209 emsg = N_("`readctl': cannot expand filename %s\n");
2210 goto jeinval_quote;
2212 fd = -1;
2213 elen = strlen(emsg);
2214 fp = safe_fopen(emsg, "r", NULL);
2215 }else if(fd == STDIN_FILENO || fd == STDOUT_FILENO ||
2216 fd == STDERR_FILENO){
2217 n_err(_("`readctl': create: standard descriptors are not allowed"));
2218 goto jeinval;
2219 }else{
2220 /* xxx Avoid */
2221 _CLOEXEC_SET(fd);
2222 emsg = NULL;
2223 elen = 0;
2224 fp = fdopen(fd, "r");
2227 if(fp != NULL){
2228 size_t i;
2230 if((i = UIZ_MAX - elen) <= cap->ca_arg.ca_str.l ||
2231 (i -= cap->ca_arg.ca_str.l) <=
2232 n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +2){
2233 n_err(_("`readctl': failed to create storage for %s\n"),
2234 cap->ca_arg.ca_str.s);
2235 n_pstate_err_no = n_ERR_OVERFLOW;
2236 f = a_ERR;
2237 goto jleave;
2240 grcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +
2241 cap->ca_arg.ca_str.l +1 + elen +1);
2242 grcp->grc_last = NULL;
2243 if((grcp->grc_next = n_readctl_overlay) != NULL)
2244 grcp->grc_next->grc_last = grcp;
2245 n_readctl_overlay = grcp;
2246 grcp->grc_fp = fp;
2247 grcp->grc_fd = fd;
2248 memcpy(grcp->grc_name, cap->ca_arg.ca_str.s, cap->ca_arg.ca_str.l +1);
2249 if(elen == 0)
2250 grcp->grc_expand = NULL;
2251 else{
2252 char *cp;
2254 grcp->grc_expand = cp = &grcp->grc_name[cap->ca_arg.ca_str.l +1];
2255 memcpy(cp, emsg, ++elen);
2257 }else{
2258 emsg = N_("`readctl': failed to create file for %s\n");
2259 goto jeinval_quote;
2263 jleave:
2264 NYD_LEAVE;
2265 return (f & a_ERR) ? 1 : 0;
2266 jeinval_quote:
2267 n_err(V_(emsg), n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2268 jeinval:
2269 n_pstate_err_no = n_ERR_INVAL;
2270 f = a_ERR;
2271 goto jleave;
2273 jshow:
2274 if((grcp = n_readctl_overlay) == NULL)
2275 fprintf(n_stdout, _("`readctl': no channels registered\n"));
2276 else{
2277 while(grcp->grc_last != NULL)
2278 grcp = grcp->grc_last;
2280 fprintf(n_stdout, _("`readctl': registered channels:\n"));
2281 for(; grcp != NULL; grcp = grcp->grc_next)
2282 fprintf(n_stdout, _("%c%s %s%s%s%s\n"),
2283 (grcp == n_readctl_overlay ? '*' : ' '),
2284 (grcp->grc_fd != -1 ? _("descriptor") : _("name")),
2285 n_shexp_quote_cp(grcp->grc_name, FAL0),
2286 (grcp->grc_expand != NULL ? " (" : n_empty),
2287 (grcp->grc_expand != NULL ? grcp->grc_expand : n_empty),
2288 (grcp->grc_expand != NULL ? ")" : n_empty));
2290 f = a_NONE;
2291 goto jleave;
2294 /* s-it-mode */