url_parse(): tweak!?! an error message (Olav Mørkrid)
[s-mailx.git] / go.c
blobc37beb1e3c33acde57332e6c9709a55837ff5a3a
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 - hold_all_sigs() most often on, especially robot mode: TERRIBLE!
5 *@ TODO - n_PS_ROBOT requires yet n_PS_SOURCING, which REALLY sucks.
6 *@ TODO - go_input(): with IO::Device we could have CStringListDevice, for
7 *@ TODO example to handle injections, and also `readctl' channels!
8 *@ TODO (Including sh(1)ell HERE strings and such.)
10 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
11 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
12 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
15 * Copyright (c) 1980, 1993
16 * The Regents of the University of California. All rights reserved.
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
20 * are met:
21 * 1. Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 * 2. Redistributions in binary form must reproduce the above copyright
24 * notice, this list of conditions and the following disclaimer in the
25 * documentation and/or other materials provided with the distribution.
26 * 3. Neither the name of the University nor the names of its contributors
27 * may be used to endorse or promote products derived from this software
28 * without specific prior written permission.
30 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
42 #undef n_FILE
43 #define n_FILE go
45 #ifndef HAVE_AMALGAMATION
46 # include "nail.h"
47 #endif
49 enum a_go_flags{
50 a_GO_NONE,
51 a_GO_FREE = 1u<<0, /* Structure was allocated, n_free() it */
52 a_GO_PIPE = 1u<<1, /* Open on a pipe */
53 a_GO_FILE = 1u<<2, /* Loading or sourcing a file */
54 a_GO_MACRO = 1u<<3, /* Running a macro */
55 a_GO_MACRO_FREE_DATA = 1u<<4, /* Lines are allocated, n_free() once done */
56 /* TODO For simplicity this is yet _MACRO plus specialization overlay
57 * TODO (_X_OPTION, _CMD) -- these should be types on their own! */
58 a_GO_MACRO_X_OPTION = 1u<<5, /* Macro indeed command line -X option */
59 a_GO_MACRO_CMD = 1u<<6, /* Macro indeed single-line: ~:COMMAND */
60 /* TODO a_GO_SPLICE: the right way to support *on-compose-splice(-shell)?*
61 * TODO would be a command_loop object that emits an on_read_line event, and
62 * TODO have a special handler for the compose mode; with that, then,
63 * TODO _event_loop() would not call _evaluate() but CTX->on_read_line,
64 * TODO and _evaluate() would be the standard impl.,
65 * TODO whereas the COMMAND ESCAPE switch in collect.c would be another one.
66 * TODO With this generic accmacvar.c:temporary_compose_mode_hook_call()
67 * TODO could be dropped, and n_go_macro() could become extended,
68 * TODO and/or we would add a n_go_anything(), which would allow special
69 * TODO input handlers, special I/O input and output, special `localopts'
70 * TODO etc., to be glued to the new execution context. And all I/O all
71 * TODO over this software should not use stdin/stdout, but CTX->in/out.
72 * TODO The pstate must be a property of the current execution context, too.
73 * TODO This not today. :( For now we invent a special SPLICE execution
74 * TODO context overlay that at least allows to temporarily modify the
75 * TODO global pstate, and the global stdin and stdout pointers. HACK!
76 * TODO This splice thing is very special and has to go again. HACK!!
77 * TODO a_go_input() will drop it once it sees EOF (HACK!), but care for
78 * TODO jumps must be taken by splice creators. HACK!!! But works. ;} */
79 a_GO_SPLICE = 1u<<7,
80 /* If it is none of those, it must be the outermost, the global one */
81 a_GO_TYPE_MASK = a_GO_PIPE | a_GO_FILE | a_GO_MACRO |
82 /* a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD | */ a_GO_SPLICE,
84 a_GO_FORCE_EOF = 1u<<8, /* go_input() shall return EOF next */
85 a_GO_IS_EOF = 1u<<9,
87 a_GO_SUPER_MACRO = 1u<<16, /* *Not* inheriting n_PS_SOURCING state */
88 /* This context has inherited the memory pool from its parent.
89 * In practice only used for resource file loading and -X args, which enter
90 * a top level n_go_main_loop() and should (re)use the in practice already
91 * allocated memory pool of the global context */
92 a_GO_MEMPOOL_INHERITED = 1u<<17,
94 /* This context has inherited the entire data context from its parent */
95 a_GO_DATACTX_INHERITED = 1u<<18,
97 a_GO_XCALL_IS_CALL = 1u<<24, /* n_GO_INPUT_NO_XCALL */
98 /* `xcall' optimization barrier: n_go_macro() has been finished with
99 * a `xcall' request, and `xcall' set this in the parent a_go_input of the
100 * said n_go_macro() to indicate a barrier: we teardown the a_go_input of
101 * the n_go_macro() away after leaving its _event_loop(), but then,
102 * back in n_go_macro(), that enters a for(;;) loop that directly calls
103 * c_call() -- our `xcall' stack avoidance optimization --, yet this call
104 * will itself end up in a new n_go_macro(), and if that again ends up with
105 * `xcall' this should teardown and leave its own n_go_macro(), unrolling the
106 * stack "up to the barrier level", but which effectively still is the
107 * n_go_macro() that lost its a_go_input and is looping the `xcall'
108 * optimization loop. If no `xcall' is desired that loop is simply left and
109 * the _event_loop() of the outer a_go_ctx will perform a loop tick and
110 * clear this bit again OR become teardown itself */
111 a_GO_XCALL_LOOP = 1u<<25, /* `xcall' optimization barrier level */
112 a_GO_XCALL_LOOP_ERROR = 1u<<26, /* .. state machine error transporter */
113 a_GO_XCALL_LOOP_MASK = a_GO_XCALL_LOOP | a_GO_XCALL_LOOP_ERROR
116 enum a_go_cleanup_mode{
117 a_GO_CLEANUP_UNWIND = 1u<<0, /* Teardown all contexts except outermost */
118 a_GO_CLEANUP_TEARDOWN = 1u<<1, /* Teardown current context */
119 a_GO_CLEANUP_LOOPTICK = 1u<<2, /* Normal looptick cleanup */
120 a_GO_CLEANUP_MODE_MASK = n_BITENUM_MASK(0, 2),
122 a_GO_CLEANUP_ERROR = 1u<<8, /* Error occurred on level */
123 a_GO_CLEANUP_SIGINT = 1u<<9, /* Interrupt signal received */
124 a_GO_CLEANUP_HOLDALLSIGS = 1u<<10 /* hold_all_sigs() active TODO */
127 enum a_go_hist_flags{
128 a_GO_HIST_NONE = 0,
129 a_GO_HIST_ADD = 1u<<0,
130 a_GO_HIST_GABBY = 1u<<1,
131 a_GO_HIST_INIT = 1u<<2
134 struct a_go_eval_ctx{
135 struct str gec_line; /* The terminated data to _evaluate() */
136 ui32_t gec_line_size; /* May be used to store line memory size */
137 bool_t gec_ever_seen; /* Has ever been used (main_loop() only) */
138 ui8_t gec__dummy[2];
139 ui8_t gec_hist_flags; /* enum a_go_hist_flags */
140 char const *gec_hist_cmd; /* If a_GO_HIST_ADD only, cmd and args */
141 char const *gec_hist_args;
144 struct a_go_input_inject{
145 struct a_go_input_inject *gii_next;
146 size_t gii_len;
147 bool_t gii_commit;
148 bool_t gii_no_history;
149 char gii_dat[n_VFIELD_SIZE(6)];
152 struct a_go_ctx{
153 struct a_go_ctx *gc_outer;
154 sigset_t gc_osigmask;
155 ui32_t gc_flags; /* enum a_go_flags */
156 ui32_t gc_loff; /* Pseudo (macro): index in .gc_lines */
157 char **gc_lines; /* Pseudo content, lines unfolded */
158 FILE *gc_file; /* File we were in, if applicable */
159 struct a_go_input_inject *gc_inject; /* To be consumed first */
160 void (*gc_on_finalize)(void *);
161 void *gc_finalize_arg;
162 sigjmp_buf gc_eloop_jmp; /* TODO one day... for _event_loop() */
163 /* SPLICE hacks: saved stdin/stdout, saved pstate */
164 FILE *gc_splice_stdin;
165 FILE *gc_splice_stdout;
166 ui32_t gc_splice_psonce;
167 ui8_t gc_splice__dummy[4];
168 struct n_go_data_ctx gc_data;
169 char gc_name[n_VFIELD_SIZE(0)]; /* Name of file or macro */
172 struct a_go_readctl_ctx{ /* TODO localize readctl_read_overlay: OnForkEvent! */
173 struct a_go_readctl_ctx *grc_last;
174 struct a_go_readctl_ctx *grc_next;
175 char const *grc_expand; /* If filename based, expanded string */
176 FILE *grc_fp;
177 si32_t grc_fd; /* Based upon file-descriptor */
178 char grc_name[n_VFIELD_SIZE(4)]; /* User input for identification purposes */
181 static sighandler_type a_go_oldpipe;
182 /* a_go_cmd_tab[] after fun protos */
184 /* Our current execution context, and the buffer backing the outermost level */
185 static struct a_go_ctx *a_go_ctx;
187 #define a_GO_MAINCTX_NAME "Main event loop"
188 static union{
189 ui64_t align;
190 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
191 sizeof(a_GO_MAINCTX_NAME)];
192 } a_go__mainctx_b;
194 /* `xcall' stack-avoidance bypass optimization. This actually is
195 * a n_cmd_arg_save_to_heap() buffer with n_cmd_arg_ctx.cac_indat misused to
196 * point to the a_go_ctx to unroll up to */
197 static void *a_go_xcall;
199 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
201 /* n_PS_STATE_PENDMASK requires some actions */
202 static void a_go_update_pstate(void);
204 /* Evaluate a single command */
205 static bool_t a_go_evaluate(struct a_go_eval_ctx *gecp);
207 /* Branch here on hangup signal and simulate "exit" */
208 static void a_go_hangup(int s);
210 /* The following gets called on receipt of an interrupt */
211 static void a_go_onintr(int s);
213 /* Cleanup current execution context, update the program state.
214 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
215 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we hold_all_sigs() */
216 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
218 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
219 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
220 * upon evaluation error, and TRU1 on success */
221 static bool_t a_go_file(char const *file, bool_t silent_open_error);
223 /* System resource file load()ing or -X command line option array traversal */
224 static bool_t a_go_load(struct a_go_ctx *gcp);
226 /* A simplified command loop for recursed state machines */
227 static bool_t a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
229 static void
230 a_go_update_pstate(void){
231 bool_t act;
232 NYD_ENTER;
234 act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
235 n_pstate &= ~n_PS_PSTATE_PENDMASK;
237 if(act){
238 char buf[32];
240 snprintf(buf, sizeof buf, "%d", n_scrnwidth);
241 ok_vset(COLUMNS, buf);
242 snprintf(buf, sizeof buf, "%d", n_scrnheight);
243 ok_vset(LINES, buf);
245 NYD_LEAVE;
248 static bool_t
249 a_go_evaluate(struct a_go_eval_ctx *gecp){
250 /* xxx old style(9), but also old code */
251 /* TODO a_go_evaluate() should be splitted in multiple subfunctions,
252 * TODO `eval' should be a prefix, etc., a Ctx should be passed along */
253 struct str line;
254 struct n_string s, *sp;
255 struct str const *alias_exp;
256 char _wordbuf[2], *argv_stack[3], **argv_base, **argvp, *vput, *cp, *word;
257 char const *alias_name;
258 struct n_cmd_desc const *cdp;
259 si32_t nerrn, nexn; /* TODO n_pstate_ex_no -> si64_t! */
260 int rv, c;
261 enum{
262 a_NONE = 0,
263 a_ALIAS_MASK = n_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
264 a_NOPREFIX = 1u<<4, /* Modifier prefix not allowed right now */
265 a_NOALIAS = 1u<<5, /* "No alias!" expansion modifier */
266 a_IGNERR = 1u<<6, /* ignerr modifier prefix */
267 a_LOCAL = 1u<<7, /* local modifier prefix */
268 a_SCOPE = 1u<<8, /* TODO scope modifier prefix */
269 a_U = 1u<<9, /* TODO UTF-8 modifier prefix */
270 a_VPUT = 1u<<10, /* vput modifier prefix */
271 a_WYSH = 1u<<11, /* XXX v15+ drop wysh modifier prefix */
272 a_MODE_MASK = n_BITENUM_MASK(5, 11),
273 a_NO_ERRNO = 1u<<16 /* Don't set n_pstate_err_no */
274 } flags;
275 NYD_ENTER;
277 if(!(n_psonce & n_PSO_EXIT_MASK) && !(n_pstate & n_PS_ERR_EXIT_MASK))
278 n_exit_status = n_EXIT_OK;
280 flags = a_NONE;
281 rv = 1;
282 nerrn = n_ERR_NONE;
283 nexn = n_EXIT_OK;
284 cdp = NULL;
285 vput = NULL;
286 alias_name = NULL;
287 n_UNINIT(alias_exp, NULL);
288 line = gecp->gec_line; /* TODO const-ify original (buffer)! */
289 assert(line.s[line.l] == '\0');
291 if(line.l > 0 && spacechar(line.s[0]))
292 gecp->gec_hist_flags = a_GO_HIST_NONE;
293 else if(gecp->gec_hist_flags & a_GO_HIST_ADD)
294 gecp->gec_hist_cmd = gecp->gec_hist_args = NULL;
295 sp = NULL;
297 /* Aliases that refer to shell commands or macro expansion restart */
298 jrestart:
299 if(n_str_trim_ifs(&line, TRU1)->l == 0){
300 line.s[0] = '\0';
301 gecp->gec_hist_flags = a_GO_HIST_NONE;
302 goto jempty;
304 (cp = line.s)[line.l] = '\0';
306 /* No-expansion modifier? */
307 if(!(flags & a_NOPREFIX) && *cp == '\\'){
308 line.s = ++cp;
309 --line.l;
310 flags |= a_NOALIAS;
313 /* Note: adding more special treatments must be reflected in the `help' etc.
314 * output in cmd-tab.c! */
316 /* Ignore null commands (comments) */
317 if(*cp == '#'){
318 gecp->gec_hist_flags = a_GO_HIST_NONE;
319 goto jret0;
322 /* Handle ! differently to get the correct lexical conventions */
323 if(*cp == '!')
324 ++cp;
325 /* Isolate the actual command; since it may not necessarily be
326 * separated from the arguments (as in `p1') we need to duplicate it to
327 * be able to create a NUL terminated version.
328 * We must be aware of several special one letter commands here */
329 else if((cp = n_UNCONST(n_cmd_isolate(cp))) == line.s &&
330 (*cp == '|' || *cp == '?'))
331 ++cp;
332 c = (int)PTR2SIZE(cp - line.s);
333 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
334 memcpy(word, line.s, c);
335 word[c] = '\0';
336 line.l -= c;
337 line.s = cp;
339 /* It may be a modifier.
340 * NOTE: changing modifiers must be reflected in `commandalias' handling
341 * code as well as in the manual (of course)! */
342 switch(c){
343 default:
344 break;
345 case sizeof("ignerr") -1:
346 if(!asccasecmp(word, "ignerr")){
347 flags |= a_NOPREFIX | a_IGNERR;
348 goto jrestart;
350 break;
351 /*case sizeof("scope") -1:*/
352 case sizeof("local") -1:
353 if(!asccasecmp(word, "local")){
354 flags |= a_NOPREFIX | a_LOCAL;
355 goto jrestart;
356 }else if(!asccasecmp(word, "scope")){
357 /* This will be an extended per-command `localopts' */
358 n_err(_("Ignoring yet unused `scope' command modifier!"));
359 flags |= a_NOPREFIX | a_SCOPE;
360 goto jrestart;
362 break;
363 case sizeof("u") -1:
364 if(!asccasecmp(word, "u")){
365 n_err(_("Ignoring yet unused `u' command modifier!"));
366 flags |= a_NOPREFIX | a_U;
367 goto jrestart;
369 break;
370 /*case sizeof("vput") -1:*/
371 case sizeof("wysh") -1:
372 if(!asccasecmp(word, "wysh")){
373 flags |= a_NOPREFIX | a_WYSH;
374 goto jrestart;
375 }else if(!asccasecmp(word, "vput")){
376 flags |= a_NOPREFIX | a_VPUT;
377 goto jrestart;
379 break;
382 /* We need to trim for a possible history entry, but do it anyway and insert
383 * a space for argument separation in case of alias expansion. Also, do
384 * terminate again because nothing prevents aliases from introducing WS */
385 n_str_trim_ifs(&line, TRU1);
386 line.s[line.l] = '\0';
388 /* Lengthy history entry setup, possibly even redundant. But having
389 * normalized history entries is a good thing, and this is maybe still
390 * cheaper than parsing a StrList of words per se */
391 if((gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
392 ) == a_GO_HIST_ADD){
393 if(line.l > 0){
394 sp = n_string_creat_auto(&s);
395 sp = n_string_assign_buf(sp, line.s, line.l);
396 gecp->gec_hist_args = n_string_cp(sp);
399 sp = n_string_creat_auto(&s);
400 sp = n_string_reserve(sp, 32);
402 if(flags & a_NOALIAS)
403 sp = n_string_push_c(sp, '\\');
404 if(flags & a_IGNERR)
405 sp = n_string_push_buf(sp, "ignerr ", sizeof("ignerr ") -1);
406 if(flags & a_WYSH)
407 sp = n_string_push_buf(sp, "wysh ", sizeof("wysh ") -1);
408 if(flags & a_VPUT)
409 sp = n_string_push_buf(sp, "vput ", sizeof("vput ") -1);
410 gecp->gec_hist_flags = a_GO_HIST_ADD | a_GO_HIST_INIT;
413 /* Look up the command; if not found, bitch.
414 * Normally, a blank command would map to the first command in the
415 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
416 * confusion; act just the same for aliases */
417 if(*word == '\0'){
418 jempty:
419 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
420 alias_name != NULL){
421 gecp->gec_hist_flags = a_GO_HIST_NONE;
422 goto jret0;
424 cdp = n_cmd_default();
425 goto jexec;
428 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
429 ui8_t expcnt;
431 expcnt = (flags & a_ALIAS_MASK);
432 ++expcnt;
433 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
435 /* Avoid self-recursion; since a commandalias can shadow a command of
436 * equal name allow one level of expansion to return an equal result:
437 * "commandalias q q;commandalias x q;x" should be "x->q->q->quit".
438 * P.S.: should also work for "help x" ... */
439 if(alias_name != NULL && !strcmp(word, alias_name))
440 flags |= a_NOALIAS;
442 if((alias_name = n_commandalias_exists(word, &alias_exp)) != NULL){
443 size_t i;
445 if(sp != NULL){
446 sp = n_string_push_cp(sp, word);
447 gecp->gec_hist_cmd = n_string_cp(sp);
448 sp = NULL;
451 /* And join arguments onto alias expansion */
452 alias_name = word;
453 i = alias_exp->l;
454 cp = line.s;
455 line.s = n_autorec_alloc(i + 1 + line.l +1);
456 memcpy(line.s, alias_exp->s, i);
457 if(line.l > 0){
458 line.s[i++] = ' ';
459 memcpy(&line.s[i], cp, line.l);
461 line.s[i += line.l] = '\0';
462 line.l = i;
463 goto jrestart;
467 if((cdp = n_cmd_firstfit(word)) == NULL){
468 bool_t doskip;
470 if(!(doskip = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
471 n_err(_("Unknown command%s: `%s'\n"),
472 (doskip ? _(" (ignored due to `if' condition)") : n_empty),
473 prstr(word));
474 gecp->gec_hist_flags = a_GO_HIST_NONE;
475 if(doskip)
476 goto jret0;
477 nerrn = n_ERR_NOSYS;
478 goto jleave;
481 /* See if we should execute the command -- if a conditional we always
482 * execute it, otherwise, check the state of cond */
483 jexec:
484 if(!(cdp->cd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip()){
485 gecp->gec_hist_flags = a_GO_HIST_NONE;
486 goto jret0;
489 if(sp != NULL){
490 sp = n_string_push_cp(sp, cdp->cd_name);
491 gecp->gec_hist_cmd = n_string_cp(sp);
492 sp = NULL;
495 nerrn = n_ERR_INVAL;
497 /* Process the arguments to the command, depending on the type it expects */
498 if((cdp->cd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
499 !(n_poption & n_PO_BATCH_FLAG)){
500 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
501 cdp->cd_name);
502 goto jleave;
504 if(!(cdp->cd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
505 n_err(_("May not execute `%s' while sending\n"), cdp->cd_name);
506 goto jleave;
508 if(cdp->cd_caflags & n_CMD_ARG_R){
509 if(n_pstate & n_PS_COMPOSE_MODE){
510 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
511 n_err(_("Cannot invoke `%s' when in compose mode\n"), cdp->cd_name);
512 goto jleave;
514 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
515 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
516 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING) && !n_go_may_yield_control()){
517 n_err(_("Cannot invoke `%s' in this program state\n"),
518 cdp->cd_name);
519 goto jleave;
522 if((cdp->cd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED_CONFIG)){
523 n_err(_("May not execute `%s' during startup\n"), cdp->cd_name);
524 goto jleave;
526 if(!(cdp->cd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
527 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
528 cdp->cd_name);
529 goto jleave;
532 if((cdp->cd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
533 n_err(_("Cannot execute `%s' without active mailbox\n"), cdp->cd_name);
534 goto jleave;
536 if((cdp->cd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
537 n_err(_("May not execute `%s' -- message file is read only\n"),
538 cdp->cd_name);
539 goto jleave;
542 if(cdp->cd_caflags & n_CMD_ARG_O)
543 n_OBSOLETE2(_("this command will be removed"), cdp->cd_name);
545 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
546 * TODO doesn't use any of those list commands which strip this mask,
547 * TODO and for now we misuse bits for checking relation to history;
548 * TODO argument state should be property of a per-command carrier instead */
549 n_pstate &= ~n_PS_ARGLIST_MASK;
551 if((flags & a_WYSH) &&
552 (cdp->cd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
553 n_err(_("`wysh' command modifier does not affect `%s'\n"), cdp->cd_name);
554 goto jleave;
557 if(flags & a_LOCAL){
558 if(!(cdp->cd_caflags & n_CMD_ARG_L)){
559 n_err(_("`local' command modifier does not affect `%s'\n"),
560 cdp->cd_name);
561 goto jleave;
563 flags |= a_WYSH;
564 n_pstate |= n_PS_ARGMOD_LOCAL; /* TODO YET useless since stripped later
565 * TODO on in getrawlist() etc., i.e., the argument vector producers,
566 * TODO therefore yet needs to be set again based on flags&a_LOCAL! */
569 if(flags & a_VPUT){
570 if(cdp->cd_caflags & n_CMD_ARG_V){
571 char const *emsg;
573 emsg = line.s; /* xxx Cannot pass &char* as char const**, so no cp */
574 vput = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIM_SPACE |
575 n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_LOG |
576 n_SHEXP_PARSE_META_SEMICOLON | n_SHEXP_PARSE_META_KEEP), &emsg);
577 line.l -= PTR2SIZE(emsg - line.s);
578 line.s = cp = n_UNCONST(emsg);
579 if(cp == NULL)
580 emsg = N_("could not parse input token");
581 else if(!n_shexp_is_valid_varname(vput))
582 emsg = N_("not a valid variable name");
583 else if(!n_var_is_user_writable(vput))
584 emsg = N_("either not a user writable, or a boolean variable");
585 else
586 emsg = NULL;
587 if(emsg != NULL){
588 n_err("`%s': vput: %s: %s\n",
589 cdp->cd_name, V_(emsg), n_shexp_quote_cp(vput, FAL0));
590 nerrn = n_ERR_NOTSUP;
591 rv = -1;
592 goto jleave;
594 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
595 * TODO on in getrawlist() etc., i.e., the argument vector producers,
596 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
597 }else{
598 n_err(_("`vput' prefix does not affect `%s'\n"), cdp->cd_name);
599 flags &= ~a_VPUT;
603 switch(cdp->cd_caflags & n_CMD_ARG_TYPE_MASK){
604 case n_CMD_ARG_TYPE_MSGLIST:
605 /* Message list defaulting to nearest forward legal message */
606 if(n_msgvec == NULL)
607 goto jmsglist_err;
608 if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_msgflag, NULL)) < 0){
609 nerrn = n_ERR_NOMSG;
610 flags |= a_NO_ERRNO;
611 break;
613 if(c == 0){
614 if((n_msgvec[0] = first(cdp->cd_msgflag, cdp->cd_msgmask)) != 0)
615 c = 1;
616 else{
617 jmsglist_err:
618 if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
619 (n_poption & n_PO_D_V))
620 fprintf(n_stdout, _("No applicable messages\n"));
621 nerrn = n_ERR_NOMSG;
622 flags |= a_NO_ERRNO;
623 break;
626 jmsglist_go:
627 /* C99 */{
628 int *mvp;
630 mvp = n_autorec_calloc(c +1, sizeof *mvp);
631 while(c-- > 0)
632 mvp[c] = n_msgvec[c];
633 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /*XXX*/
634 n_err_no = 0;
635 rv = (*cdp->cd_func)(mvp);
637 break;
639 case n_CMD_ARG_TYPE_NDMLIST:
640 /* Message list with no defaults, but no error if none exist */
641 if(n_msgvec == NULL)
642 goto jmsglist_err;
643 if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_msgflag, NULL)) < 0){
644 nerrn = n_ERR_NOMSG;
645 flags |= a_NO_ERRNO;
646 break;
648 goto jmsglist_go;
650 case n_CMD_ARG_TYPE_STRING:
651 /* Just the straight string, old style, with leading blanks removed */
652 for(cp = line.s; spacechar(*cp);)
653 ++cp;
654 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
655 n_err_no = 0;
656 rv = (*cdp->cd_func)(cp);
657 break;
659 case n_CMD_ARG_TYPE_RAWDAT:
660 /* Just the straight string, placed in argv[] */
661 argvp = argv_stack;
662 if(flags & a_VPUT)
663 *argvp++ = vput;
664 *argvp++ = line.s;
665 *argvp = NULL;
666 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
667 n_err_no = 0;
668 rv = (*cdp->cd_func)(argv_stack);
669 break;
671 case n_CMD_ARG_TYPE_WYSH:
672 c = 1;
673 if(0){
674 /* FALLTHRU */
675 case n_CMD_ARG_TYPE_WYRA:
676 c = (flags & a_WYSH) ? 1 : 0;
677 if(0){
678 case n_CMD_ARG_TYPE_RAWLIST:
679 c = 0;
682 argvp = argv_base = n_autorec_alloc(sizeof(*argv_base) * n_MAXARGC);
683 if(flags & a_VPUT)
684 *argvp++ = vput;
685 if((c = getrawlist((c != 0), argvp,
686 (n_MAXARGC - ((flags & a_VPUT) != 0)), line.s, line.l)) < 0){
687 n_err(_("Invalid argument list\n"));
688 flags |= a_NO_ERRNO;
689 break;
692 if(UICMP(32, c, <, cdp->cd_minargs)){
693 n_err(_("`%s' requires at least %u arg(s)\n"),
694 cdp->cd_name, (ui32_t)cdp->cd_minargs);
695 flags |= a_NO_ERRNO;
696 break;
698 #undef cd_minargs
699 if(UICMP(32, c, >, cdp->cd_maxargs)){
700 n_err(_("`%s' takes no more than %u arg(s)\n"),
701 cdp->cd_name, (ui32_t)cdp->cd_maxargs);
702 flags |= a_NO_ERRNO;
703 break;
705 #undef cd_maxargs
707 if(flags & a_LOCAL)
708 n_pstate |= n_PS_ARGMOD_LOCAL;
709 if(flags & a_VPUT)
710 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO due to getrawlist(), as above */
712 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
713 n_err_no = 0;
714 rv = (*cdp->cd_func)(argv_base);
715 if(a_go_xcall != NULL)
716 goto jret0;
717 break;
719 case n_CMD_ARG_TYPE_ARG:{
720 /* TODO The _ARG_TYPE_ARG is preliminary, in the end we should have a
721 * TODO per command-ctx carrier that also has slots for it arguments,
722 * TODO and that should be passed along all the way. No more arglists
723 * TODO here, etc. */
724 struct n_cmd_arg_ctx cac;
726 cac.cac_desc = cdp->cd_cadp;
727 cac.cac_indat = line.s;
728 cac.cac_inlen = line.l;
729 cac.cac_msgflag = cdp->cd_msgflag;
730 cac.cac_msgmask = cdp->cd_msgmask;
731 if(!n_cmd_arg_parse(&cac)){
732 flags |= a_NO_ERRNO;
733 break;
736 if(flags & a_VPUT){
737 cac.cac_vput = vput;
738 /* Global "hack" not used: n_pstate |= n_PS_ARGMOD_VPUT; */
739 }else
740 cac.cac_vput = NULL;
742 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
743 n_err_no = 0;
744 rv = (*cdp->cd_func)(&cac);
745 if(a_go_xcall != NULL)
746 goto jret0;
747 }break;
749 default:
750 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
751 cdp->cd_caflags & n_CMD_ARG_TYPE_MASK); )
752 nerrn = n_ERR_NOTOBACCO;
753 nexn = 1;
754 goto jret0;
757 if(gecp->gec_hist_flags & a_GO_HIST_ADD){
758 if(cdp->cd_caflags & n_CMD_ARG_H)
759 gecp->gec_hist_flags = a_GO_HIST_NONE;
760 else if((cdp->cd_caflags & n_CMD_ARG_G) ||
761 (n_pstate & n_PS_MSGLIST_GABBY))
762 gecp->gec_hist_flags |= a_GO_HIST_GABBY;
765 if(rv != 0){
766 if(!(flags & a_NO_ERRNO)){
767 if(cdp->cd_caflags & n_CMD_ARG_EM)
768 flags |= a_NO_ERRNO;
769 else if((nerrn = n_err_no) == 0)
770 nerrn = n_ERR_INVAL;
771 }/*else
772 flags ^= a_NO_ERRNO;*/
773 }else if(cdp->cd_caflags & n_CMD_ARG_EM)
774 flags |= a_NO_ERRNO;
775 else
776 nerrn = n_ERR_NONE;
777 jleave:
778 nexn = rv;
780 if(flags & a_IGNERR){
781 if(!(n_psonce & n_PSO_EXIT_MASK) && !(n_pstate & n_PS_ERR_EXIT_MASK))
782 n_exit_status = n_EXIT_OK;
783 n_pstate &= ~n_PS_ERR_EXIT_MASK;
784 }else if(rv != 0){
785 bool_t bo;
787 if((bo = ok_blook(batch_exit_on_error))){
788 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
789 if(!(n_poption & n_PO_BATCH_FLAG))
790 bo = FAL0;
792 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
793 n_pstate |= n_PS_ERR_QUIT;
794 else if(ok_blook(posix)){
795 if(n_psonce & n_PSO_STARTED)
796 rv = 0;
797 else if(!(n_psonce & n_PSO_INTERACTIVE))
798 n_pstate |= n_PS_ERR_XIT;
799 }else
800 rv = 0;
802 if(rv != 0){
803 if(n_exit_status == n_EXIT_OK)
804 n_exit_status = n_EXIT_ERR;
805 if((n_poption & n_PO_D_V) &&
806 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
807 n_alert(_("Non-interactive, bailing out due to errors "
808 "in startup load phase"));
809 goto jret;
813 if(cdp == NULL)
814 goto jret0;
815 if((cdp->cd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
816 if(visible(dot))
817 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
818 sizeof("\\type") -1);
820 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
821 !(cdp->cd_caflags & n_CMD_ARG_T))
822 n_pstate |= n_PS_SAW_COMMAND;
823 jret0:
824 rv = 0;
825 jret:
826 if(!(flags & a_NO_ERRNO))
827 n_pstate_err_no = nerrn;
828 n_pstate_ex_no = nexn;
829 NYD_LEAVE;
830 return (rv == 0);
833 static void
834 a_go_hangup(int s){
835 NYD_X; /* Signal handler */
836 n_UNUSED(s);
837 /* nothing to do? */
838 exit(n_EXIT_ERR);
841 #ifdef HAVE_IMAP
842 FL void n_go_onintr_for_imap(void){a_go_onintr(0);}
843 #endif
844 static void
845 a_go_onintr(int s){ /* TODO block signals while acting */
846 NYD_X; /* Signal handler */
847 n_UNUSED(s);
849 safe_signal(SIGINT, a_go_onintr);
851 termios_state_reset();
853 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
855 if(interrupts != 1)
856 n_err_sighdl(_("Interrupt\n"));
857 safe_signal(SIGPIPE, a_go_oldpipe);
858 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
861 static void
862 a_go_cleanup(enum a_go_cleanup_mode gcm){
863 /* Signals blocked */
864 struct a_go_ctx *gcp;
865 NYD_ENTER;
867 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
868 hold_all_sigs();
869 jrestart:
870 gcp = a_go_ctx;
872 /* Free input injections of this level first */
873 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
874 struct a_go_input_inject **giipp, *giip;
876 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
877 *giipp = giip->gii_next;
878 n_free(giip);
882 /* Cleanup non-crucial external stuff */
883 n_COLOUR(
884 if(gcp->gc_data.gdc_colour != NULL)
885 n_colour_stack_del(&gcp->gc_data);
888 /* Work the actual context (according to cleanup mode) */
889 if(gcp->gc_outer == NULL){
890 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
891 if(a_go_xcall != NULL){
892 n_free(a_go_xcall);
893 a_go_xcall = NULL;
895 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
896 n_pstate &= ~n_PS_ERR_EXIT_MASK;
897 close_all_files();
898 }else{
899 if(!(n_pstate & n_PS_SOURCING))
900 close_all_files();
903 n_memory_reset();
905 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
906 assert(a_go_xcall == NULL);
907 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
908 assert(gcp->gc_on_finalize == NULL);
909 n_COLOUR( assert(gcp->gc_data.gdc_colour == NULL); )
910 goto jxleave;
911 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
912 n_memory_reset();
913 goto jxleave;
914 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
915 n_stdin = gcp->gc_splice_stdin;
916 n_stdout = gcp->gc_splice_stdout;
917 n_psonce = gcp->gc_splice_psonce;
918 goto jstackpop;
921 /* Cleanup crucial external stuff */
922 if(gcp->gc_data.gdc_ifcond != NULL){
923 n_cnd_if_stack_del(&gcp->gc_data);
924 if(!(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
925 !(gcp->gc_flags & a_GO_FORCE_EOF) && a_go_xcall == NULL &&
926 !(n_psonce & n_PSO_EXIT_MASK)){
927 n_err(_("Unmatched `if' at end of %s %s\n"),
928 ((gcp->gc_flags & a_GO_MACRO
929 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
930 : _("`source'd file"))),
931 gcp->gc_name);
932 gcm |= a_GO_CLEANUP_ERROR;
936 /* Teardown context */
937 if(gcp->gc_flags & a_GO_MACRO){
938 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
939 char **lp;
941 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
942 n_free(*lp);
943 ++gcp->gc_loff;
945 /* Part of gcp's memory chunk, then */
946 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
947 n_free(gcp->gc_lines);
949 }else if(gcp->gc_flags & a_GO_PIPE)
950 /* XXX command manager should -TERM then -KILL instead of hoping
951 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
952 Pclose(gcp->gc_file, TRU1);
953 else if(gcp->gc_flags & a_GO_FILE)
954 Fclose(gcp->gc_file);
956 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
957 if(gcp->gc_data.gdc_mempool != NULL)
958 n_memory_pool_pop(NULL, TRU1);
959 }else
960 n_memory_reset();
962 jstackpop:
963 /* Update a_go_ctx and n_go_data, n_pstate ... */
964 a_go_ctx = gcp->gc_outer;
965 assert(a_go_ctx != NULL);
966 /* C99 */{
967 struct a_go_ctx *x;
969 for(x = a_go_ctx; x->gc_flags & a_GO_DATACTX_INHERITED;){
970 x = x->gc_outer;
971 assert(x != NULL);
973 n_go_data = &x->gc_data;
976 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
977 (a_GO_MACRO | a_GO_SUPER_MACRO)){
978 n_pstate &= ~n_PS_SOURCING;
979 assert(n_pstate & n_PS_ROBOT);
980 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
981 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
982 else
983 assert(n_pstate & n_PS_ROBOT);
985 if(gcp->gc_on_finalize != NULL)
986 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
988 if(gcm & a_GO_CLEANUP_ERROR){
989 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
990 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
991 goto jerr;
993 jleave:
994 if(gcp->gc_flags & a_GO_FREE)
995 n_free(gcp);
997 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
998 goto jrestart;
1000 jxleave:
1001 NYD_LEAVE;
1002 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1003 rele_all_sigs();
1004 return;
1006 jerr:
1007 /* With *posix* we follow what POSIX says:
1008 * Any errors in the start-up file shall either cause mailx to
1009 * terminate with a diagnostic message and a non-zero status or to
1010 * continue after writing a diagnostic message, ignoring the
1011 * remainder of the lines in the start-up file
1012 * Print the diagnostic only for the outermost resource unless the user
1013 * is debugging or in verbose mode */
1014 if((n_poption & n_PO_D_V) ||
1015 (!(n_psonce & n_PSO_STARTED) &&
1016 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1017 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
1018 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1019 n_alert(_("Stopped %s %s due to errors%s"),
1020 (n_psonce & n_PSO_STARTED
1021 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1022 : (gcp->gc_flags & a_GO_MACRO
1023 ? (gcp->gc_flags & a_GO_MACRO_CMD
1024 ? _("evaluating command") : _("evaluating macro"))
1025 : (gcp->gc_flags & a_GO_PIPE
1026 ? _("executing `source'd pipe")
1027 : (gcp->gc_flags & a_GO_FILE
1028 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1030 : (gcp->gc_flags & a_GO_MACRO
1031 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
1032 ? _("evaluating command line") : _("evaluating macro"))
1033 : _("loading initialization resource"))),
1034 n_shexp_quote_cp(gcp->gc_name, FAL0),
1035 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
1036 goto jleave;
1039 static bool_t
1040 a_go_file(char const *file, bool_t silent_open_error){
1041 struct a_go_ctx *gcp;
1042 sigset_t osigmask;
1043 size_t nlen;
1044 char *nbuf;
1045 bool_t ispipe;
1046 FILE *fip;
1047 NYD_ENTER;
1049 fip = NULL;
1051 /* Being a command argument file is space-trimmed *//* TODO v15 with
1052 * TODO WYRALIST this is no longer necessary true, and for that we
1053 * TODO don't set _PARSE_TRIM_SPACE because we cannot! -> cmd-tab.h!! */
1054 #if 0
1055 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
1056 file[--nlen] == '|')))
1057 #else
1058 ispipe = FAL0;
1059 if(!silent_open_error){
1060 for(nlen = strlen(file); nlen > 0;){
1061 char c;
1063 c = file[--nlen];
1064 if(!spacechar(c)){
1065 if(c == '|'){
1066 nbuf = savestrbuf(file, nlen);
1067 ispipe = TRU1;
1069 break;
1073 #endif
1075 if(ispipe){
1076 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
1077 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
1078 goto jeopencheck;
1079 }else if((nbuf = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL)
1080 goto jeopencheck;
1081 else if((fip = Fopen(nbuf, "r")) == NULL){
1082 jeopencheck:
1083 if(!silent_open_error || (n_poption & n_PO_D_V))
1084 n_perr(nbuf, 0);
1085 if(silent_open_error)
1086 fip = (FILE*)-1;
1087 goto jleave;
1090 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1092 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1093 (nlen = strlen(nbuf) +1));
1094 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1096 hold_all_sigs();
1098 gcp->gc_outer = a_go_ctx;
1099 gcp->gc_osigmask = osigmask;
1100 gcp->gc_file = fip;
1101 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1102 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1103 memcpy(gcp->gc_name, nbuf, nlen);
1105 a_go_ctx = gcp;
1106 n_go_data = &gcp->gc_data;
1107 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1108 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1109 fip = NULL;
1110 jleave:
1111 NYD_LEAVE;
1112 return (fip != NULL);
1115 static bool_t
1116 a_go_load(struct a_go_ctx *gcp){
1117 NYD2_ENTER;
1119 assert(!(n_psonce & n_PSO_STARTED));
1120 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1122 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1123 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1125 hold_all_sigs();
1127 /* POSIX:
1128 * Any errors in the start-up file shall either cause mailx to terminate
1129 * with a diagnostic message and a non-zero status or to continue after
1130 * writing a diagnostic message, ignoring the remainder of the lines in
1131 * the start-up file. */
1132 gcp->gc_outer = a_go_ctx;
1133 a_go_ctx = gcp;
1134 n_go_data = &gcp->gc_data;
1135 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1136 n_pstate |= n_PS_ROBOT |
1137 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1139 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1141 rele_all_sigs();
1143 n_go_main_loop();
1144 NYD2_LEAVE;
1145 return (((n_psonce & n_PSO_EXIT_MASK) |
1146 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1149 static void
1150 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1151 NYD_X; /* Signal handler */
1152 n_UNUSED(sig);
1153 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1156 static bool_t
1157 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1158 sighandler_type soldhdl;
1159 struct a_go_eval_ctx gec;
1160 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1161 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1162 sigset_t osigmask;
1163 NYD2_ENTER;
1165 memset(&gec, 0, sizeof gec);
1166 osigmask = gcp->gc_osigmask;
1167 hadint = FAL0;
1168 f = a_RETOK;
1170 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1171 safe_signal(SIGINT, &a_go__eloopint);
1172 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1173 hold_all_sigs();
1174 hadint = TRU1;
1175 f &= ~a_RETOK;
1176 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1177 goto jjump;
1181 for(;; f |= a_TICKED){
1182 int n;
1184 if(f & a_TICKED)
1185 n_memory_reset();
1187 /* Read a line of commands and handle end of file specially */
1188 gec.gec_line.l = gec.gec_line_size;
1189 rele_all_sigs();
1190 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL, NULL);
1191 hold_all_sigs();
1192 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1193 gec.gec_line.l = (ui32_t)n;
1195 if(n < 0)
1196 break;
1198 rele_all_sigs();
1199 assert(gec.gec_hist_flags == a_GO_HIST_NONE);
1200 if(!a_go_evaluate(&gec))
1201 f &= ~a_RETOK;
1202 hold_all_sigs();
1204 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1205 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1206 break;
1209 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1210 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1211 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1212 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1214 if(gec.gec_line.s != NULL)
1215 n_free(gec.gec_line.s);
1217 if(soldhdl != SIG_IGN)
1218 safe_signal(SIGINT, soldhdl);
1219 NYD2_LEAVE;
1220 rele_all_sigs();
1221 if(hadint){
1222 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1223 n_raise(SIGINT);
1225 return (f & a_RETOK);
1228 FL void
1229 n_go_init(void){
1230 struct a_go_ctx *gcp;
1231 NYD2_ENTER;
1233 assert(n_stdin != NULL);
1235 gcp = (void*)a_go__mainctx_b.uf;
1236 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1237 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1238 gcp->gc_file = n_stdin;
1239 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1240 a_go_ctx = gcp;
1241 n_go_data = &gcp->gc_data;
1243 n_child_manager_start();
1244 NYD2_LEAVE;
1247 FL bool_t
1248 n_go_main_loop(void){ /* FIXME */
1249 struct a_go_eval_ctx gec;
1250 int n, eofcnt;
1251 bool_t volatile rv;
1252 NYD_ENTER;
1254 rv = TRU1;
1256 if (!(n_pstate & n_PS_SOURCING)) {
1257 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1258 safe_signal(SIGINT, &a_go_onintr);
1259 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1260 safe_signal(SIGHUP, &a_go_hangup);
1262 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1263 safe_signal(SIGPIPE, a_go_oldpipe);
1265 memset(&gec, 0, sizeof gec);
1267 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1268 hold_all_sigs();
1270 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1271 interrupts = 0;
1273 if(gec.gec_ever_seen)
1274 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1276 if (!(n_pstate & n_PS_SOURCING)) {
1277 char *cp;
1279 /* TODO Note: this buffer may contain a password. We should redefine
1280 * TODO the code flow which has to do that */
1281 if ((cp = termios_state.ts_linebuf) != NULL) {
1282 termios_state.ts_linebuf = NULL;
1283 termios_state.ts_linesize = 0;
1284 n_free(cp); /* TODO pool give-back */
1286 if (gec.gec_line.l > LINESIZE * 3) {
1287 n_free(gec.gec_line.s);
1288 gec.gec_line.s = NULL;
1289 gec.gec_line.l = gec.gec_line_size = 0;
1293 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1294 char *cp;
1296 if ((cp = ok_vlook(newmail)) != NULL) { /* TODO bla */
1297 struct stat st;
1299 if(mb.mb_type == MB_FILE){
1300 if(!stat(mailname, &st) && st.st_size > mailsize)
1301 #if defined HAVE_MAILDIR || defined HAVE_IMAP
1302 Jnewmail:
1303 #endif
1305 ui32_t odid;
1306 size_t odot;
1308 odot = PTR2SIZE(dot - message);
1309 odid = (n_pstate & n_PS_DID_PRINT_DOT);
1311 rele_all_sigs();
1312 n = setfile(mailname,
1313 (FEDIT_NEWMAIL |
1314 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)));
1315 hold_all_sigs();
1317 if(n < 0) {
1318 n_exit_status |= n_EXIT_ERR;
1319 rv = FAL0;
1320 break;
1322 #ifdef HAVE_IMAP
1323 if(mb.mb_type != MB_IMAP){
1324 #endif
1325 dot = &message[odot];
1326 n_pstate |= odid;
1327 #ifdef HAVE_IMAP
1329 #endif
1331 }else{
1332 #if defined HAVE_MAILDIR || defined HAVE_IMAP
1333 n = (cp != NULL && strcmp(cp, "nopoll"));
1334 #endif
1336 #ifdef HAVE_MAILDIR
1337 if(mb.mb_type == MB_MAILDIR){
1338 if(n != 0)
1339 goto Jnewmail;
1341 #endif
1342 #ifdef HAVE_IMAP
1343 if(mb.mb_type == MB_IMAP){
1344 if(!n)
1345 n = (cp != NULL && strcmp(cp, "noimap"));
1347 if(imap_newmail(n) > (cp == NULL))
1348 goto Jnewmail;
1350 #endif
1355 /* Read a line of commands and handle end of file specially */
1356 gec.gec_line.l = gec.gec_line_size;
1357 /* C99 */{
1358 bool_t histadd;
1360 histadd = (!(n_pstate & n_PS_SOURCING) &&
1361 (n_psonce & n_PSO_INTERACTIVE));
1362 rele_all_sigs();
1363 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1364 &gec.gec_line.s, &gec.gec_line.l, NULL, &histadd);
1365 hold_all_sigs();
1367 gec.gec_hist_flags = histadd ? a_GO_HIST_ADD : a_GO_HIST_NONE;
1369 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1370 gec.gec_line.l = (ui32_t)n;
1372 if (n < 0) {
1373 if (!(n_pstate & n_PS_ROBOT) &&
1374 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1375 ++eofcnt < 4) {
1376 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1377 n_go_input_clearerr();
1378 continue;
1380 break;
1383 n_pstate &= ~n_PS_HOOK_MASK;
1384 rele_all_sigs();
1385 rv = a_go_evaluate(&gec);
1386 hold_all_sigs();
1388 if(gec.gec_hist_flags & a_GO_HIST_ADD){
1389 char const *cc, *ca;
1391 cc = gec.gec_hist_cmd;
1392 ca = gec.gec_hist_args;
1393 if(cc != NULL && ca != NULL)
1394 cc = savecatsep(cc, ' ', ca);
1395 else if(ca != NULL)
1396 cc = ca;
1397 assert(cc != NULL);
1398 n_tty_addhist(cc, (n_GO_INPUT_CTX_DEFAULT |
1399 (gec.gec_hist_flags & a_GO_HIST_GABBY ? n_GO_INPUT_HIST_GABBY
1400 : n_GO_INPUT_NONE)));
1403 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1404 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1405 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1406 default: break;
1408 if(n_psonce & n_PSO_EXIT_MASK)
1409 break;
1411 if(!rv)
1412 break;
1415 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1416 (rv ? 0 : a_GO_CLEANUP_ERROR));
1418 if (gec.gec_line.s != NULL)
1419 n_free(gec.gec_line.s);
1421 rele_all_sigs();
1422 NYD_LEAVE;
1423 return rv;
1426 FL void
1427 n_go_input_clearerr(void){
1428 FILE *fp;
1429 NYD2_ENTER;
1431 fp = NULL;
1433 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1434 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1435 fp = a_go_ctx->gc_file;
1437 if(fp != NULL){
1438 a_go_ctx->gc_flags &= ~a_GO_IS_EOF;
1439 clearerr(fp);
1441 NYD2_LEAVE;
1444 FL void
1445 n_go_input_force_eof(void){
1446 NYD2_ENTER;
1447 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1448 NYD2_LEAVE;
1451 FL bool_t
1452 n_go_input_is_eof(void){
1453 bool_t rv;
1454 NYD2_ENTER;
1456 rv = ((a_go_ctx->gc_flags & a_GO_IS_EOF) != 0);
1457 NYD2_LEAVE;
1458 return rv;
1461 FL void
1462 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1463 size_t len){
1464 NYD_ENTER;
1465 if(len == UIZ_MAX)
1466 len = strlen(buf);
1468 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1469 len > 0){
1470 struct a_go_input_inject *giip, **giipp;
1472 hold_all_sigs();
1474 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1475 ) + 1 + len +1);
1476 giipp = &a_go_ctx->gc_inject;
1477 giip->gii_next = *giipp;
1478 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1479 giip->gii_no_history = ((giif & n_GO_INPUT_INJECT_HISTORY) == 0);
1480 memcpy(&giip->gii_dat[0], buf, len);
1481 giip->gii_dat[giip->gii_len = len] = '\0';
1482 *giipp = giip;
1484 rele_all_sigs();
1486 NYD_LEAVE;
1489 FL int
1490 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1491 size_t *linesize, char const *string, bool_t *histok_or_null
1492 n_MEMORY_DEBUG_ARGS){
1493 /* TODO readline: linebuf pool!; n_go_input should return si64_t.
1494 * TODO This thing should be replaced by a(n) (stack of) event generator(s)
1495 * TODO and consumed by OnLineCompletedEvent listeners */
1496 struct n_string xprompt;
1497 FILE *ifile;
1498 char const *iftype;
1499 struct a_go_input_inject *giip;
1500 int nold, n;
1501 enum{
1502 a_NONE,
1503 a_HISTOK = 1u<<0,
1504 a_USE_PROMPT = 1u<<1,
1505 a_USE_MLE = 1u<<2,
1506 a_DIGMSG_OVERLAY = 1u<<16
1507 } f;
1508 NYD2_ENTER;
1510 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1511 hold_all_sigs();
1513 f = a_NONE;
1515 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
1516 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1517 n = -1;
1518 goto jleave;
1521 if(gif & n_GO_INPUT_FORCE_STDIN)
1522 goto jforce_stdin;
1524 /* Special case macro mode: never need to prompt, lines have always been
1525 * unfolded already */
1526 if(a_go_ctx->gc_flags & a_GO_MACRO){
1527 if(*linebuf != NULL)
1528 n_free(*linebuf);
1530 /* Injection in progress? Don't care about the autocommit state here */
1531 if((giip = a_go_ctx->gc_inject) != NULL){
1532 a_go_ctx->gc_inject = giip->gii_next;
1534 /* Simply "reuse" allocation, copy string to front of it */
1535 jinject:
1536 *linesize = giip->gii_len;
1537 *linebuf = (char*)giip;
1538 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
1539 iftype = "INJECTION";
1540 }else{
1541 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
1542 *linesize = 0;
1543 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1544 n = -1;
1545 goto jleave;
1548 ++a_go_ctx->gc_loff;
1549 *linesize = strlen(*linebuf);
1550 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
1551 *linebuf = sbufdup(*linebuf, *linesize);
1553 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
1554 ? "-X OPTION"
1555 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
1557 n = (int)*linesize;
1558 n_pstate |= n_PS_READLINE_NL;
1559 goto jhave_dat;
1560 }else{
1561 /* Injection in progress? */
1562 struct a_go_input_inject **giipp;
1564 giipp = &a_go_ctx->gc_inject;
1566 if((giip = *giipp) != NULL){
1567 *giipp = giip->gii_next;
1569 if(giip->gii_commit){
1570 if(*linebuf != NULL)
1571 n_free(*linebuf);
1572 if(!giip->gii_no_history)
1573 f |= a_HISTOK;
1574 goto jinject; /* (above) */
1575 }else{
1576 string = savestrbuf(giip->gii_dat, giip->gii_len);
1577 n_free(giip);
1582 jforce_stdin:
1583 n_pstate &= ~n_PS_READLINE_NL;
1584 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
1585 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
1586 if(!(n_pstate & n_PS_ROBOT) &&
1587 (n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
1588 (n_PSO_INTERACTIVE | n_PSO_STARTED))
1589 f |= a_HISTOK;
1590 if(!(f & a_HISTOK) || (gif & n_GO_INPUT_FORCE_STDIN))
1591 gif |= n_GO_INPUT_PROMPT_NONE;
1592 else{
1593 f |= a_USE_PROMPT;
1594 if(!ok_blook(line_editor_disable))
1595 f |= a_USE_MLE;
1596 else
1597 (void)n_string_creat_auto(&xprompt);
1598 if(prompt == NULL)
1599 gif |= n_GO_INPUT_PROMPT_EVAL;
1602 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
1603 if((gif & n_GO_INPUT_PROMPT_NONE) && !(f & a_USE_MLE))
1604 fflush(n_stdout);
1606 if(gif & n_GO_INPUT_FORCE_STDIN){
1607 struct a_go_readctl_ctx *grcp;
1608 struct n_dig_msg_ctx *dmcp;
1610 if((dmcp = n_digmsg_read_overlay) != NULL){
1611 ifile = dmcp->dmc_fp;
1612 f |= a_DIGMSG_OVERLAY;
1613 }else if((grcp = n_readctl_read_overlay) == NULL ||
1614 (ifile = grcp->grc_fp) == NULL)
1615 ifile = n_stdin;
1616 }else
1617 ifile = a_go_ctx->gc_file;
1618 if(ifile == NULL){
1619 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
1620 (a_go_ctx->gc_flags & a_GO_MACRO));
1621 ifile = n_stdin;
1624 for(nold = n = 0;;){
1625 if(f & a_USE_MLE){
1626 assert(ifile == n_stdin);
1627 if(string != NULL && (n = (int)strlen(string)) > 0){
1628 if(*linesize > 0)
1629 *linesize += n +1;
1630 else
1631 *linesize = (size_t)n + LINESIZE +1;
1632 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
1633 memcpy(*linebuf, string, (size_t)n +1);
1635 string = NULL;
1637 rele_all_sigs();
1639 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n, histok_or_null
1640 n_MEMORY_DEBUG_ARGSCALL);
1642 hold_all_sigs();
1644 if(n < 0 && !ferror(ifile)) /* EOF never i guess */
1645 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1646 }else{
1647 if(!(gif & n_GO_INPUT_PROMPT_NONE))
1648 n_tty_create_prompt(&xprompt, prompt, gif);
1650 rele_all_sigs();
1652 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
1653 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
1654 fflush(n_stdout);
1657 n = (readline_restart)(ifile, linebuf, linesize, n
1658 n_MEMORY_DEBUG_ARGSCALL);
1660 hold_all_sigs();
1662 if(n < 0 && !ferror(ifile))
1663 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1665 if(n > 0 && nold > 0){
1666 char const *cp;
1667 int i;
1669 i = 0;
1670 cp = &(*linebuf)[nold];
1671 while(spacechar(*cp) && n - i >= nold)
1672 ++cp, ++i;
1673 if(i > 0){
1674 memmove(&(*linebuf)[nold], cp, n - nold - i);
1675 n -= i;
1676 (*linebuf)[n] = '\0';
1681 if(n <= 0)
1682 break;
1684 /* POSIX says:
1685 * TODO This does not take care for current shell quote mode!
1686 * TODO Thus "echo '\<NEWLINE HERE> bla' will never work
1687 * An unquoted <backslash> at the end of a command line shall
1688 * be discarded and the next line shall continue the command */
1689 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\')
1690 break;
1692 /* Definitely outside of quotes, thus the quoting rules are so that an
1693 * uneven number of successive reverse solidus at EOL is a continuation */
1694 if(n > 1){
1695 size_t i, j;
1697 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
1698 if((*linebuf)[i] != '\\')
1699 break;
1700 if(!(j & 1))
1701 break;
1703 (*linebuf)[nold = --n] = '\0';
1704 gif |= n_GO_INPUT_NL_FOLLOW;
1707 if(n < 0)
1708 goto jleave;
1709 if(f & a_USE_MLE)
1710 n_pstate |= n_PS_READLINE_NL;
1711 (*linebuf)[*linesize = n] = '\0';
1713 jhave_dat:
1714 if(n_poption & n_PO_D_VV)
1715 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
1716 jleave:
1717 if (n_pstate & n_PS_PSTATE_PENDMASK)
1718 a_go_update_pstate();
1720 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
1721 * TODO but only established from the outside and we need to drop this
1722 * TODO overlay context somehow; ditto DIGMSG_OVERLAY */
1723 if(n < 0){
1724 if(f & a_DIGMSG_OVERLAY)
1725 n_digmsg_read_overlay = NULL;
1726 if(a_go_ctx->gc_flags & a_GO_SPLICE)
1727 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
1730 if(histok_or_null != NULL && !(f & a_HISTOK))
1731 *histok_or_null = FAL0;
1733 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1734 rele_all_sigs();
1735 NYD2_LEAVE;
1736 return n;
1739 FL char *
1740 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
1741 char const *string){
1742 struct n_sigman sm;
1743 bool_t histadd;
1744 size_t linesize;
1745 char *linebuf, * volatile rv;
1746 int n;
1747 NYD2_ENTER;
1749 linesize = 0;
1750 linebuf = NULL;
1751 rv = NULL;
1753 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1754 case 0:
1755 break;
1756 default:
1757 goto jleave;
1760 histadd = TRU1;
1761 n = n_go_input(gif, prompt, &linebuf, &linesize, string, &histadd);
1762 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
1763 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE) &&
1764 histadd)
1765 n_tty_addhist(rv, gif);
1767 n_sigman_cleanup_ping(&sm);
1768 jleave:
1769 if(linebuf != NULL)
1770 n_free(linebuf);
1771 NYD2_LEAVE;
1772 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1773 return rv;
1776 FL bool_t
1777 n_go_load(char const *name){
1778 struct a_go_ctx *gcp;
1779 size_t i;
1780 FILE *fip;
1781 bool_t rv;
1782 NYD_ENTER;
1784 rv = TRU1;
1786 if(name == NULL || *name == '\0')
1787 goto jleave;
1788 else if((fip = Fopen(name, "r")) == NULL){
1789 if(n_poption & n_PO_D_V)
1790 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
1791 goto jleave;
1794 i = strlen(name) +1;
1795 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
1796 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1798 gcp->gc_file = fip;
1799 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
1800 memcpy(gcp->gc_name, name, i);
1802 if(n_poption & n_PO_D_V)
1803 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
1804 rv = a_go_load(gcp);
1805 jleave:
1806 NYD_LEAVE;
1807 return rv;
1810 FL bool_t
1811 n_go_Xargs(char const **lines, size_t cnt){
1812 static char const name[] = "-X";
1814 union{
1815 bool_t rv;
1816 ui64_t align;
1817 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
1818 } b;
1819 char const *srcp, *xsrcp;
1820 char *cp;
1821 size_t imax, i, len;
1822 struct a_go_ctx *gcp;
1823 NYD_ENTER;
1825 gcp = (void*)b.uf;
1826 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1828 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
1829 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
1830 memcpy(gcp->gc_name, name, sizeof name);
1832 /* The problem being that we want to support reverse solidus newline
1833 * escaping also within multiline -X, i.e., POSIX says:
1834 * An unquoted <backslash> at the end of a command line shall
1835 * be discarded and the next line shall continue the command
1836 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
1837 * entire lines array and set _MACRO_FREE_DATA */
1838 imax = cnt + 1;
1839 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
1841 /* For each of the input lines.. */
1842 for(i = len = 0, cp = NULL; cnt > 0;){
1843 bool_t keep;
1844 size_t j;
1846 if((j = strlen(srcp = *lines)) == 0){
1847 ++lines, --cnt;
1848 continue;
1851 /* Separate one line from a possible multiline input string */
1852 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
1853 *lines = &xsrcp[1];
1854 j = PTR2SIZE(xsrcp - srcp);
1855 }else
1856 ++lines, --cnt;
1858 /* The (separated) string may itself indicate soft newline escaping */
1859 if((keep = (srcp[j - 1] == '\\'))){
1860 size_t xj, xk;
1862 /* Need an uneven number of reverse solidus */
1863 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
1864 if(srcp[xj] != '\\')
1865 break;
1866 if(xk & 1)
1867 --j;
1868 else
1869 keep = FAL0;
1872 /* Strip any leading WS from follow lines, then */
1873 if(cp != NULL)
1874 while(j > 0 && spacechar(*srcp))
1875 ++srcp, --j;
1877 if(j > 0){
1878 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
1879 imax += 4;
1880 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
1881 imax);
1883 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
1884 memcpy(&cp[len], srcp, j);
1885 cp[len += j] = '\0';
1887 if(!keep)
1888 ++i;
1890 if(!keep)
1891 cp = NULL, len = 0;
1893 if(cp != NULL){
1894 assert(i + 1 < imax);
1895 gcp->gc_lines[i++] = cp;
1897 gcp->gc_lines[i] = NULL;
1899 b.rv = a_go_load(gcp);
1900 NYD_LEAVE;
1901 return b.rv;
1904 FL int
1905 c_source(void *v){
1906 int rv;
1907 NYD_ENTER;
1909 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
1910 NYD_LEAVE;
1911 return rv;
1914 FL int
1915 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
1916 int rv;
1917 NYD_ENTER;
1919 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
1920 NYD_LEAVE;
1921 return rv;
1924 FL bool_t
1925 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
1926 void (*on_finalize)(void*), void *finalize_arg){
1927 struct a_go_ctx *gcp;
1928 size_t i;
1929 int rv;
1930 sigset_t osigmask;
1931 NYD_ENTER;
1933 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1935 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1936 (i = strlen(name) +1));
1937 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1939 hold_all_sigs();
1941 gcp->gc_outer = a_go_ctx;
1942 gcp->gc_osigmask = osigmask;
1943 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
1944 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1945 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0) |
1946 ((gif & n_GO_INPUT_NO_XCALL) ? a_GO_XCALL_IS_CALL : 0);
1947 gcp->gc_lines = lines;
1948 gcp->gc_on_finalize = on_finalize;
1949 gcp->gc_finalize_arg = finalize_arg;
1950 memcpy(gcp->gc_name, name, i);
1952 a_go_ctx = gcp;
1953 n_go_data = &gcp->gc_data;
1954 n_pstate |= n_PS_ROBOT;
1955 rv = a_go_event_loop(gcp, gif);
1957 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
1958 if(a_go_xcall != NULL){
1959 void *vp;
1960 struct n_cmd_arg_ctx *cacp;
1962 if(a_go_xcall == (void*)-1)
1963 a_go_xcall = NULL;
1964 else if(((void const*)(cacp = a_go_xcall)->cac_indat) == gcp){
1965 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
1966 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
1967 while(a_go_xcall != NULL){
1968 hold_all_sigs();
1970 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
1972 vp = a_go_xcall;
1973 a_go_xcall = NULL;
1974 cacp = n_cmd_arg_restore_from_heap(vp);
1975 n_free(vp);
1977 rele_all_sigs();
1979 (void)c_call(cacp);
1981 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) == 0);
1982 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1985 NYD_LEAVE;
1986 return rv;
1989 FL bool_t
1990 n_go_command(enum n_go_input_flags gif, char const *cmd){
1991 struct a_go_ctx *gcp;
1992 bool_t rv;
1993 size_t i, ial;
1994 sigset_t osigmask;
1995 NYD_ENTER;
1997 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1999 i = strlen(cmd) +1;
2000 ial = n_ALIGN(i);
2001 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2002 ial + 2*sizeof(char*));
2003 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2005 hold_all_sigs();
2007 gcp->gc_outer = a_go_ctx;
2008 gcp->gc_osigmask = osigmask;
2009 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
2010 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2011 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2012 gcp->gc_lines = (void*)&gcp->gc_name[ial];
2013 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
2014 gcp->gc_lines[1] = NULL;
2016 a_go_ctx = gcp;
2017 n_go_data = &gcp->gc_data;
2018 n_pstate |= n_PS_ROBOT;
2019 rv = a_go_event_loop(gcp, gif);
2020 NYD_LEAVE;
2021 return rv;
2024 FL void
2025 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
2026 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
2027 struct a_go_ctx *gcp;
2028 size_t i;
2029 sigset_t osigmask;
2030 NYD_ENTER;
2032 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2034 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2035 (i = strlen(cmd) +1));
2036 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2038 hold_all_sigs();
2040 gcp->gc_outer = a_go_ctx;
2041 gcp->gc_osigmask = osigmask;
2042 gcp->gc_file = new_stdin;
2043 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE | a_GO_DATACTX_INHERITED;
2044 gcp->gc_on_finalize = on_finalize;
2045 gcp->gc_finalize_arg = finalize_arg;
2046 gcp->gc_splice_stdin = n_stdin;
2047 gcp->gc_splice_stdout = n_stdout;
2048 gcp->gc_splice_psonce = n_psonce;
2049 memcpy(gcp->gc_name, cmd, i);
2051 n_stdin = new_stdin;
2052 n_stdout = new_stdout;
2053 n_psonce = new_psonce;
2054 a_go_ctx = gcp;
2055 /* Do NOT touch n_go_data! */
2056 n_pstate |= n_PS_ROBOT;
2058 rele_all_sigs();
2059 NYD_LEAVE;
2062 FL void
2063 n_go_splice_hack_remove_after_jump(void){
2064 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2067 FL bool_t
2068 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2069 struct a_go_ctx *gcp;
2070 bool_t rv;
2071 NYD2_ENTER;
2073 rv = FAL0;
2075 /* Only when startup completed */
2076 if(!(n_psonce & n_PSO_STARTED))
2077 goto jleave;
2078 /* Only interactive or batch mode (assuming that is ok) */
2079 if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
2080 goto jleave;
2082 /* Not when running any hook */
2083 if(n_pstate & n_PS_HOOK_MASK)
2084 goto jleave;
2086 /* Traverse up the stack:
2087 * . not when controlled by a child process
2088 * TODO . not when there are pipes involved, we neither handle job control,
2089 * TODO nor process groups, that is, controlling terminal acceptably
2090 * . not when sourcing a file */
2091 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2092 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2093 goto jleave;
2096 rv = TRU1;
2097 jleave:
2098 NYD2_LEAVE;
2099 return rv;
2102 FL int
2103 c_eval(void *vp){
2104 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
2105 * TODO ARGV with shell rules, but if that is not possible then simply
2106 * TODO adjust argv/argc of "the CmdCtx" that we will have "exec" real cmd */
2107 struct a_go_eval_ctx gec;
2108 struct n_string s_b, *sp;
2109 size_t i, j;
2110 char const **argv, *cp;
2111 NYD_ENTER;
2113 argv = vp;
2115 for(j = i = 0; (cp = argv[i]) != NULL; ++i)
2116 j += strlen(cp);
2118 sp = n_string_creat_auto(&s_b);
2119 sp = n_string_reserve(sp, j);
2121 for(i = 0; (cp = argv[i]) != NULL; ++i){
2122 if(i > 0)
2123 sp = n_string_push_c(sp, ' ');
2124 sp = n_string_push_cp(sp, cp);
2127 memset(&gec, 0, sizeof gec);
2128 gec.gec_line.s = n_string_cp(sp);
2129 gec.gec_line.l = sp->s_len;
2130 if(n_poption & n_PO_D_VV)
2131 n_err(_("EVAL %" PRIuZ " bytes <%s>\n"), gec.gec_line.l, gec.gec_line.s);
2132 (void)/* XXX */a_go_evaluate(&gec);
2133 NYD_LEAVE;
2134 return (a_go_xcall != NULL ? 0 : n_pstate_ex_no);
2137 FL int
2138 c_xcall(void *vp){
2139 int rv;
2140 struct a_go_ctx *gcp;
2141 NYD2_ENTER;
2143 /* The context can only be a macro context, except that possibly a single
2144 * level of `eval' (TODO: yet) was used to double-expand our arguments */
2145 if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
2146 gcp = gcp->gc_outer;
2147 if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD)
2148 ) != a_GO_MACRO){
2149 if(n_poption & n_PO_D_V)
2150 n_err(_("`xcall': can only be used inside a macro, using `call'\n"));
2151 rv = c_call(vp);
2152 goto jleave;
2155 /* Try to roll up the stack as much as possible.
2156 * See a_GO_XCALL_LOOP flag description for more */
2157 if(!(gcp->gc_flags & a_GO_XCALL_IS_CALL) && gcp->gc_outer != NULL){
2158 if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
2159 gcp = gcp->gc_outer;
2160 }else{
2161 /* Otherwise this macro is "invoked from the top level", in which case we
2162 * silently act as if we were `call'... */
2163 rv = c_call(vp);
2164 /* ...which means we must ensure the rest of the macro that was us
2165 * doesn't become evaluated! */
2166 a_go_xcall = (void*)-1;
2167 goto jleave;
2170 /* C99 */{
2171 struct n_cmd_arg_ctx *cacp;
2173 cacp = n_cmd_arg_save_to_heap(vp);
2174 cacp->cac_indat = (char*)gcp;
2175 a_go_xcall = cacp;
2177 rv = 0;
2178 jleave:
2179 NYD2_LEAVE;
2180 return rv;
2183 FL int
2184 c_exit(void *vp){
2185 char const **argv;
2186 NYD_ENTER;
2188 if(*(argv = vp) != NULL && (n_idec_si32_cp(&n_exit_status, *argv, 0, NULL) &
2189 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2190 ) != n_IDEC_STATE_CONSUMED)
2191 n_exit_status |= n_EXIT_ERR;
2193 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2194 fflush(NULL);
2195 _exit(n_exit_status);
2196 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2197 n_err(_("`exit' delayed until compose mode is left\n")); /* XXX ..log? */
2198 n_psonce |= n_PSO_XIT;
2199 NYD_LEAVE;
2200 return 0;
2203 FL int
2204 c_quit(void *vp){
2205 char const **argv;
2206 NYD_ENTER;
2208 if(*(argv = vp) != NULL && (n_idec_si32_cp(&n_exit_status, *argv, 0, NULL) &
2209 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2210 ) != n_IDEC_STATE_CONSUMED)
2211 n_exit_status |= n_EXIT_ERR;
2213 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2214 fflush(NULL);
2215 _exit(n_exit_status);
2216 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2217 n_err(_("`exit' delayed until compose mode is left\n")); /* XXX ..log? */
2218 n_psonce |= n_PSO_QUIT;
2219 NYD_LEAVE;
2220 return 0;
2223 FL int
2224 c_readctl(void *vp){
2225 /* TODO We would need OnForkEvent and then simply remove some internal
2226 * TODO management; we don't have this, therefore we need global
2227 * TODO n_readctl_read_overlay to be accessible via =NULL, and to make that
2228 * TODO work in turn we need an instance for default STDIN! Sigh. */
2229 static union{
2230 ui64_t alignme;
2231 ui8_t buf[n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name)+1 +1];
2232 } a;
2233 static struct a_go_readctl_ctx *a_stdin;
2235 struct a_go_readctl_ctx *grcp;
2236 char const *emsg;
2237 enum{
2238 a_NONE = 0,
2239 a_ERR = 1u<<0,
2240 a_SET = 1u<<1,
2241 a_CREATE = 1u<<2,
2242 a_REMOVE = 1u<<3
2243 } f;
2244 struct n_cmd_arg *cap;
2245 struct n_cmd_arg_ctx *cacp;
2246 NYD_ENTER;
2248 if(a_stdin == NULL){
2249 a_stdin = (struct a_go_readctl_ctx*)(void*)a.buf;
2250 a_stdin->grc_name[0] = '-';
2251 n_readctl_read_overlay = a_stdin;
2254 n_pstate_err_no = n_ERR_NONE;
2255 cacp = vp;
2256 cap = cacp->cac_arg;
2258 if(cacp->cac_no == 0 || is_asccaseprefix(cap->ca_arg.ca_str.s, "show"))
2259 goto jshow;
2260 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "set"))
2261 f = a_SET;
2262 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "create"))
2263 f = a_CREATE;
2264 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "remove"))
2265 f = a_REMOVE;
2266 else{
2267 emsg = N_("`readctl': invalid subcommand: %s\n");
2268 goto jeinval_quote;
2271 if(cacp->cac_no == 1){ /* TODO better option parser <> subcommand */
2272 n_err(_("`readctl': %s: requires argument\n"), cap->ca_arg.ca_str.s);
2273 goto jeinval;
2275 cap = cap->ca_next;
2277 /* - is special TODO unfortunately also regarding storage */
2278 if(cap->ca_arg.ca_str.l == 1 && *cap->ca_arg.ca_str.s == '-'){
2279 if(f & (a_CREATE | a_REMOVE)){
2280 n_err(_("`readctl': cannot create nor remove -\n"));
2281 goto jeinval;
2283 n_readctl_read_overlay = a_stdin;
2284 goto jleave;
2287 /* Try to find a yet existing instance */
2288 if((grcp = n_readctl_read_overlay) != NULL){
2289 for(; grcp != NULL; grcp = grcp->grc_next)
2290 if(!strcmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2291 goto jfound;
2292 for(grcp = n_readctl_read_overlay; (grcp = grcp->grc_last) != NULL;)
2293 if(!strcmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2294 goto jfound;
2297 if(f & (a_SET | a_REMOVE)){
2298 emsg = N_("`readctl': no such channel: %s\n");
2299 goto jeinval_quote;
2302 jfound:
2303 if(f & a_SET)
2304 n_readctl_read_overlay = grcp;
2305 else if(f & a_REMOVE){
2306 if(n_readctl_read_overlay == grcp)
2307 n_readctl_read_overlay = a_stdin;
2309 if(grcp->grc_last != NULL)
2310 grcp->grc_last->grc_next = grcp->grc_next;
2311 if(grcp->grc_next != NULL)
2312 grcp->grc_next->grc_last = grcp->grc_last;
2313 fclose(grcp->grc_fp);
2314 n_free(grcp);
2315 }else{
2316 FILE *fp;
2317 size_t elen;
2318 si32_t fd;
2320 if(grcp != NULL){
2321 n_err(_("`readctl': channel already exists: %s\n"), /* TODO reopen */
2322 n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2323 n_pstate_err_no = n_ERR_EXIST;
2324 f = a_ERR;
2325 goto jleave;
2328 if((n_idec_si32_cp(&fd, cap->ca_arg.ca_str.s, 0, NULL
2329 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2330 ) != n_IDEC_STATE_CONSUMED){
2331 if((emsg = fexpand(cap->ca_arg.ca_str.s, FEXP_LOCAL | FEXP_NVAR)
2332 ) == NULL){
2333 emsg = N_("`readctl': cannot expand filename %s\n");
2334 goto jeinval_quote;
2336 fd = -1;
2337 elen = strlen(emsg);
2338 fp = safe_fopen(emsg, "r", NULL);
2339 }else if(fd == STDIN_FILENO || fd == STDOUT_FILENO ||
2340 fd == STDERR_FILENO){
2341 n_err(_("`readctl': create: standard descriptors are not allowed\n"));
2342 goto jeinval;
2343 }else{
2344 /* xxx Avoid */
2345 _CLOEXEC_SET(fd);
2346 emsg = NULL;
2347 elen = 0;
2348 fp = fdopen(fd, "r");
2351 if(fp != NULL){
2352 size_t i;
2354 if((i = UIZ_MAX - elen) <= cap->ca_arg.ca_str.l ||
2355 (i -= cap->ca_arg.ca_str.l) <=
2356 n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +2){
2357 n_err(_("`readctl': failed to create storage for %s\n"),
2358 cap->ca_arg.ca_str.s);
2359 n_pstate_err_no = n_ERR_OVERFLOW;
2360 f = a_ERR;
2361 goto jleave;
2364 grcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +
2365 cap->ca_arg.ca_str.l +1 + elen +1);
2366 grcp->grc_last = NULL;
2367 if((grcp->grc_next = n_readctl_read_overlay) != NULL)
2368 grcp->grc_next->grc_last = grcp;
2369 n_readctl_read_overlay = grcp;
2370 grcp->grc_fp = fp;
2371 grcp->grc_fd = fd;
2372 memcpy(grcp->grc_name, cap->ca_arg.ca_str.s, cap->ca_arg.ca_str.l +1);
2373 if(elen == 0)
2374 grcp->grc_expand = NULL;
2375 else{
2376 char *cp;
2378 grcp->grc_expand = cp = &grcp->grc_name[cap->ca_arg.ca_str.l +1];
2379 memcpy(cp, emsg, ++elen);
2381 }else{
2382 emsg = N_("`readctl': failed to create file for %s\n");
2383 goto jeinval_quote;
2387 jleave:
2388 NYD_LEAVE;
2389 return (f & a_ERR) ? 1 : 0;
2390 jeinval_quote:
2391 n_err(V_(emsg), n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2392 jeinval:
2393 n_pstate_err_no = n_ERR_INVAL;
2394 f = a_ERR;
2395 goto jleave;
2397 jshow:
2398 if((grcp = n_readctl_read_overlay) == NULL)
2399 fprintf(n_stdout, _("`readctl': no channels registered\n"));
2400 else{
2401 while(grcp->grc_last != NULL)
2402 grcp = grcp->grc_last;
2404 fprintf(n_stdout, _("`readctl': registered channels:\n"));
2405 for(; grcp != NULL; grcp = grcp->grc_next)
2406 fprintf(n_stdout, _("%c%s %s%s%s%s\n"),
2407 (grcp == n_readctl_read_overlay ? '*' : ' '),
2408 (grcp->grc_fd != -1 ? _("descriptor") : _("name")),
2409 n_shexp_quote_cp(grcp->grc_name, FAL0),
2410 (grcp->grc_expand != NULL ? " (" : n_empty),
2411 (grcp->grc_expand != NULL ? grcp->grc_expand : n_empty),
2412 (grcp->grc_expand != NULL ? ")" : n_empty));
2414 f = a_NONE;
2415 goto jleave;
2418 /* s-it-mode */