Ensure per-command storage for getmsglist() users
[s-mailx.git] / go.c
blobc9dd4757b33eb8edc1afb3ffcd00c3c394a086ed
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 - 2018 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 /* This context has inherited the entire data context from its parent */
89 a_GO_DATACTX_INHERITED = 1u<<18,
91 a_GO_XCALL_IS_CALL = 1u<<24, /* n_GO_INPUT_NO_XCALL */
92 /* `xcall' optimization barrier: n_go_macro() has been finished with
93 * a `xcall' request, and `xcall' set this in the parent a_go_input of the
94 * said n_go_macro() to indicate a barrier: we teardown the a_go_input of
95 * the n_go_macro() away after leaving its _event_loop(), but then,
96 * back in n_go_macro(), that enters a for(;;) loop that directly calls
97 * c_call() -- our `xcall' stack avoidance optimization --, yet this call
98 * will itself end up in a new n_go_macro(), and if that again ends up with
99 * `xcall' this should teardown and leave its own n_go_macro(), unrolling the
100 * stack "up to the barrier level", but which effectively still is the
101 * n_go_macro() that lost its a_go_input and is looping the `xcall'
102 * optimization loop. If no `xcall' is desired that loop is simply left and
103 * the _event_loop() of the outer a_go_ctx will perform a loop tick and
104 * clear this bit again OR become teardown itself */
105 a_GO_XCALL_LOOP = 1u<<25, /* `xcall' optimization barrier level */
106 a_GO_XCALL_LOOP_ERROR = 1u<<26, /* .. state machine error transporter */
107 a_GO_XCALL_LOOP_MASK = a_GO_XCALL_LOOP | a_GO_XCALL_LOOP_ERROR
110 enum a_go_cleanup_mode{
111 a_GO_CLEANUP_UNWIND = 1u<<0, /* Teardown all contexts except outermost */
112 a_GO_CLEANUP_TEARDOWN = 1u<<1, /* Teardown current context */
113 a_GO_CLEANUP_LOOPTICK = 1u<<2, /* Normal looptick cleanup */
114 a_GO_CLEANUP_MODE_MASK = n_BITENUM_MASK(0, 2),
116 a_GO_CLEANUP_ERROR = 1u<<8, /* Error occurred on level */
117 a_GO_CLEANUP_SIGINT = 1u<<9, /* Interrupt signal received */
118 a_GO_CLEANUP_HOLDALLSIGS = 1u<<10 /* hold_all_sigs() active TODO */
121 enum a_go_hist_flags{
122 a_GO_HIST_NONE = 0,
123 a_GO_HIST_ADD = 1u<<0,
124 a_GO_HIST_GABBY = 1u<<1,
125 a_GO_HIST_INIT = 1u<<2
128 struct a_go_eval_ctx{
129 struct str gec_line; /* The terminated data to _evaluate() */
130 ui32_t gec_line_size; /* May be used to store line memory size */
131 bool_t gec_ever_seen; /* Has ever been used (main_loop() only) */
132 ui8_t gec__dummy[2];
133 ui8_t gec_hist_flags; /* enum a_go_hist_flags */
134 char const *gec_hist_cmd; /* If a_GO_HIST_ADD only, cmd and args */
135 char const *gec_hist_args;
138 struct a_go_input_inject{
139 struct a_go_input_inject *gii_next;
140 size_t gii_len;
141 bool_t gii_commit;
142 bool_t gii_no_history;
143 char gii_dat[n_VFIELD_SIZE(6)];
146 struct a_go_ctx{
147 struct a_go_ctx *gc_outer;
148 sigset_t gc_osigmask;
149 ui32_t gc_flags; /* enum a_go_flags */
150 ui32_t gc_loff; /* Pseudo (macro): index in .gc_lines */
151 char **gc_lines; /* Pseudo content, lines unfolded */
152 FILE *gc_file; /* File we were in, if applicable */
153 struct a_go_input_inject *gc_inject; /* To be consumed first */
154 void (*gc_on_finalize)(void *);
155 void *gc_finalize_arg;
156 sigjmp_buf gc_eloop_jmp; /* TODO one day... for _event_loop() */
157 /* SPLICE hacks: saved stdin/stdout, saved pstate */
158 FILE *gc_splice_stdin;
159 FILE *gc_splice_stdout;
160 ui32_t gc_splice_psonce;
161 ui8_t gc_splice__dummy[4];
162 struct n_go_data_ctx gc_data;
163 char gc_name[n_VFIELD_SIZE(0)]; /* Name of file or macro */
166 struct a_go_readctl_ctx{ /* TODO localize n_readctl_overlay, use OnForkEvent! */
167 struct a_go_readctl_ctx *grc_last;
168 struct a_go_readctl_ctx *grc_next;
169 char const *grc_expand; /* If filename based, expanded string */
170 FILE *grc_fp;
171 si32_t grc_fd; /* Based upon file-descriptor */
172 char grc_name[n_VFIELD_SIZE(4)]; /* User input for identification purposes */
175 static sighandler_type a_go_oldpipe;
176 /* a_go_cmd_tab[] after fun protos */
178 /* Our current execution context, and the buffer backing the outermost level */
179 static struct a_go_ctx *a_go_ctx;
181 #define a_GO_MAINCTX_NAME "Main event loop"
182 static union{
183 ui64_t align;
184 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
185 sizeof(a_GO_MAINCTX_NAME)];
186 } a_go__mainctx_b;
188 /* `xcall' stack-avoidance bypass optimization. This actually is
189 * a n_cmd_arg_save_to_heap() buffer with n_cmd_arg_ctx.cac_indat misused to
190 * point to the a_go_ctx to unroll up to */
191 static void *a_go_xcall;
193 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
195 /* n_PS_STATE_PENDMASK requires some actions */
196 static void a_go_update_pstate(void);
198 /* Evaluate a single command */
199 static bool_t a_go_evaluate(struct a_go_eval_ctx *gecp);
201 /* Branch here on hangup signal and simulate "exit" */
202 static void a_go_hangup(int s);
204 /* The following gets called on receipt of an interrupt */
205 static void a_go_onintr(int s);
207 /* Cleanup current execution context, update the program state.
208 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
209 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we hold_all_sigs() */
210 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
212 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
213 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
214 * upon evaluation error, and TRU1 on success */
215 static bool_t a_go_file(char const *file, bool_t silent_open_error);
217 /* System resource file load()ing or -X command line option array traversal */
218 static bool_t a_go_load(struct a_go_ctx *gcp);
220 /* A simplified command loop for recursed state machines */
221 static bool_t a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
223 static void
224 a_go_update_pstate(void){
225 bool_t act;
226 NYD_ENTER;
228 act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
229 n_pstate &= ~n_PS_PSTATE_PENDMASK;
231 if(act){
232 char buf[32];
234 snprintf(buf, sizeof buf, "%d", n_scrnwidth);
235 ok_vset(COLUMNS, buf);
236 snprintf(buf, sizeof buf, "%d", n_scrnheight);
237 ok_vset(LINES, buf);
239 NYD_LEAVE;
242 static bool_t
243 a_go_evaluate(struct a_go_eval_ctx *gecp){
244 /* xxx old style(9), but also old code */
245 /* TODO a_go_evaluate() should be splitted in multiple subfunctions,
246 * TODO `eval' should be a prefix, etc., a Ctx should be passed along */
247 struct str line;
248 struct n_string s, *sp;
249 struct str const *alias_exp;
250 char _wordbuf[2], *argv_stack[3], **argv_base, **argvp, *vput, *cp, *word;
251 char const *alias_name;
252 struct n_cmd_desc const *cdp;
253 si32_t nerrn, nexn; /* TODO n_pstate_ex_no -> si64_t! */
254 int rv, c;
255 enum{
256 a_NONE = 0,
257 a_ALIAS_MASK = n_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
258 a_NOPREFIX = 1u<<4, /* Modifier prefix not allowed right now */
259 a_NOALIAS = 1u<<5, /* "No alias!" expansion modifier */
260 a_IGNERR = 1u<<6, /* ignerr modifier prefix */
261 a_LOCAL = 1u<<7, /* local modifier prefix */
262 a_SCOPE = 1u<<8, /* TODO scope modifier prefix */
263 a_U = 1u<<9, /* TODO UTF-8 modifier prefix */
264 a_VPUT = 1u<<10, /* vput modifier prefix */
265 a_WYSH = 1u<<11, /* XXX v15+ drop wysh modifier prefix */
266 a_MODE_MASK = n_BITENUM_MASK(5, 11),
267 a_NO_ERRNO = 1u<<16 /* Don't set n_pstate_err_no */
268 } flags;
269 NYD_ENTER;
271 if(!(n_pstate & n_PS_ERR_EXIT_MASK))
272 n_exit_status = n_EXIT_OK;
274 flags = a_NONE;
275 rv = 1;
276 nerrn = n_ERR_NONE;
277 nexn = n_EXIT_OK;
278 cdp = NULL;
279 vput = NULL;
280 alias_name = NULL;
281 n_UNINIT(alias_exp, NULL);
282 line = gecp->gec_line; /* TODO const-ify original (buffer)! */
283 assert(line.s[line.l] == '\0');
285 if(line.l > 0 && spacechar(line.s[0]))
286 gecp->gec_hist_flags = a_GO_HIST_NONE;
287 else if(gecp->gec_hist_flags & a_GO_HIST_ADD)
288 gecp->gec_hist_cmd = gecp->gec_hist_args = NULL;
289 sp = NULL;
291 /* Aliases that refer to shell commands or macro expansion restart */
292 jrestart:
293 if(n_str_trim_ifs(&line, TRU1)->l == 0){
294 line.s[0] = '\0';
295 gecp->gec_hist_flags = a_GO_HIST_NONE;
296 goto jempty;
298 (cp = line.s)[line.l] = '\0';
300 /* No-expansion modifier? */
301 if(!(flags & a_NOPREFIX) && *cp == '\\'){
302 line.s = ++cp;
303 --line.l;
304 flags |= a_NOALIAS;
307 /* Note: adding more special treatments must be reflected in the `help' etc.
308 * output in cmd-tab.c! */
310 /* Ignore null commands (comments) */
311 if(*cp == '#'){
312 gecp->gec_hist_flags = a_GO_HIST_NONE;
313 goto jret0;
316 /* Handle ! differently to get the correct lexical conventions */
317 if(*cp == '!')
318 ++cp;
319 /* Isolate the actual command; since it may not necessarily be
320 * separated from the arguments (as in `p1') we need to duplicate it to
321 * be able to create a NUL terminated version.
322 * We must be aware of several special one letter commands here */
323 else if((cp = n_UNCONST(n_cmd_isolate(cp))) == line.s &&
324 (*cp == '|' || *cp == '?'))
325 ++cp;
326 c = (int)PTR2SIZE(cp - line.s);
327 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
328 memcpy(word, line.s, c);
329 word[c] = '\0';
330 line.l -= c;
331 line.s = cp;
333 /* It may be a modifier.
334 * NOTE: changing modifiers must be reflected in `commandalias' handling
335 * code as well as in the manual (of course)! */
336 switch(c){
337 default:
338 break;
339 case sizeof("ignerr") -1:
340 if(!asccasecmp(word, "ignerr")){
341 flags |= a_NOPREFIX | a_IGNERR;
342 goto jrestart;
344 break;
345 /*case sizeof("scope") -1:*/
346 case sizeof("local") -1:
347 if(!asccasecmp(word, "local")){
348 flags |= a_NOPREFIX | a_LOCAL;
349 goto jrestart;
350 }else if(!asccasecmp(word, "scope")){
351 /* This will be an extended per-command `localopts' */
352 n_err(_("Ignoring yet unused `scope' command modifier!"));
353 flags |= a_NOPREFIX | a_SCOPE;
354 goto jrestart;
356 break;
357 case sizeof("u") -1:
358 if(!asccasecmp(word, "u")){
359 n_err(_("Ignoring yet unused `u' command modifier!"));
360 flags |= a_NOPREFIX | a_U;
361 goto jrestart;
363 break;
364 /*case sizeof("vput") -1:*/
365 case sizeof("wysh") -1:
366 if(!asccasecmp(word, "wysh")){
367 flags |= a_NOPREFIX | a_WYSH;
368 goto jrestart;
369 }else if(!asccasecmp(word, "vput")){
370 flags |= a_NOPREFIX | a_VPUT;
371 goto jrestart;
373 break;
376 /* We need to trim for a possible history entry, but do it anyway and insert
377 * a space for argument separation in case of alias expansion. Also, do
378 * terminate again because nothing prevents aliases from introducing WS */
379 n_str_trim_ifs(&line, TRU1);
380 line.s[line.l] = '\0';
382 /* Lengthy history entry setup, possibly even redundant. But having
383 * normalized history entries is a good thing, and this is maybe still
384 * cheaper than parsing a StrList of words per se */
385 if((gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
386 ) == a_GO_HIST_ADD){
387 if(line.l > 0){
388 sp = n_string_creat_auto(&s);
389 sp = n_string_assign_buf(sp, line.s, line.l);
390 gecp->gec_hist_args = n_string_cp(sp);
393 sp = n_string_creat_auto(&s);
394 sp = n_string_reserve(sp, 32);
396 if(flags & a_NOALIAS)
397 sp = n_string_push_c(sp, '\\');
398 if(flags & a_IGNERR)
399 sp = n_string_push_buf(sp, "ignerr ", sizeof("ignerr ") -1);
400 if(flags & a_WYSH)
401 sp = n_string_push_buf(sp, "wysh ", sizeof("wysh ") -1);
402 if(flags & a_VPUT)
403 sp = n_string_push_buf(sp, "vput ", sizeof("vput ") -1);
404 gecp->gec_hist_flags = a_GO_HIST_ADD | a_GO_HIST_INIT;
407 /* Look up the command; if not found, bitch.
408 * Normally, a blank command would map to the first command in the
409 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
410 * confusion; act just the same for aliases */
411 if(*word == '\0'){
412 jempty:
413 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
414 alias_name != NULL){
415 gecp->gec_hist_flags = a_GO_HIST_NONE;
416 goto jret0;
418 cdp = n_cmd_default();
419 goto jexec;
422 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
423 ui8_t expcnt;
425 expcnt = (flags & a_ALIAS_MASK);
426 ++expcnt;
427 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
429 /* Avoid self-recursion; since a commandalias can shadow a command of
430 * equal name allow one level of expansion to return an equal result:
431 * "commandalias q q;commandalias x q;x" should be "x->q->q->quit".
432 * P.S.: should also work for "help x" ... */
433 if(alias_name != NULL && !strcmp(word, alias_name))
434 flags |= a_NOALIAS;
436 if((alias_name = n_commandalias_exists(word, &alias_exp)) != NULL){
437 size_t i;
439 if(sp != NULL){
440 sp = n_string_push_cp(sp, word);
441 gecp->gec_hist_cmd = n_string_cp(sp);
442 sp = NULL;
445 /* And join arguments onto alias expansion */
446 alias_name = word;
447 i = alias_exp->l;
448 cp = line.s;
449 line.s = n_autorec_alloc(i + 1 + line.l +1);
450 memcpy(line.s, alias_exp->s, i);
451 if(line.l > 0){
452 line.s[i++] = ' ';
453 memcpy(&line.s[i], cp, line.l);
455 line.s[i += line.l] = '\0';
456 line.l = i;
457 goto jrestart;
461 if((cdp = n_cmd_firstfit(word)) == NULL){
462 bool_t doskip;
464 if(!(doskip = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
465 n_err(_("Unknown command%s: `%s'\n"),
466 (doskip ? _(" (ignored due to `if' condition)") : n_empty),
467 prstr(word));
468 gecp->gec_hist_flags = a_GO_HIST_NONE;
469 if(doskip)
470 goto jret0;
471 nerrn = n_ERR_NOSYS;
472 goto jleave;
475 /* See if we should execute the command -- if a conditional we always
476 * execute it, otherwise, check the state of cond */
477 jexec:
478 if(!(cdp->cd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip()){
479 gecp->gec_hist_flags = a_GO_HIST_NONE;
480 goto jret0;
483 if(sp != NULL){
484 sp = n_string_push_cp(sp, cdp->cd_name);
485 gecp->gec_hist_cmd = n_string_cp(sp);
486 sp = NULL;
489 nerrn = n_ERR_INVAL;
491 /* Process the arguments to the command, depending on the type it expects */
492 if((cdp->cd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
493 !(n_poption & n_PO_BATCH_FLAG)){
494 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
495 cdp->cd_name);
496 goto jleave;
498 if(!(cdp->cd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
499 n_err(_("May not execute `%s' while sending\n"), cdp->cd_name);
500 goto jleave;
502 if(cdp->cd_caflags & n_CMD_ARG_R){
503 if(n_pstate & n_PS_COMPOSE_MODE){
504 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
505 n_err(_("Cannot invoke `%s' when in compose mode\n"), cdp->cd_name);
506 goto jleave;
508 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
509 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
510 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING) && !n_go_may_yield_control()){
511 n_err(_("Cannot invoke `%s' in this program state\n"),
512 cdp->cd_name);
513 goto jleave;
516 if((cdp->cd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED_CONFIG)){
517 n_err(_("May not execute `%s' during startup\n"), cdp->cd_name);
518 goto jleave;
520 if(!(cdp->cd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
521 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
522 cdp->cd_name);
523 goto jleave;
526 if((cdp->cd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
527 n_err(_("Cannot execute `%s' without active mailbox\n"), cdp->cd_name);
528 goto jleave;
530 if((cdp->cd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
531 n_err(_("May not execute `%s' -- message file is read only\n"),
532 cdp->cd_name);
533 goto jleave;
536 if(cdp->cd_caflags & n_CMD_ARG_O)
537 n_OBSOLETE2(_("this command will be removed"), cdp->cd_name);
539 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
540 * TODO doesn't use any of those list commands which strip this mask,
541 * TODO and for now we misuse bits for checking relation to history;
542 * TODO argument state should be property of a per-command carrier instead */
543 n_pstate &= ~n_PS_ARGLIST_MASK;
545 if((flags & a_WYSH) &&
546 (cdp->cd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
547 n_err(_("`wysh' command modifier does not affect `%s'\n"), cdp->cd_name);
548 goto jleave;
551 if(flags & a_LOCAL){
552 if(!(cdp->cd_caflags & n_CMD_ARG_L)){
553 n_err(_("`local' command modifier does not affect `%s'\n"),
554 cdp->cd_name);
555 goto jleave;
557 flags |= a_WYSH;
558 n_pstate |= n_PS_ARGMOD_LOCAL; /* TODO YET useless since stripped later
559 * TODO on in getrawlist() etc., i.e., the argument vector producers,
560 * TODO therefore yet needs to be set again based on flags&a_LOCAL! */
563 if(flags & a_VPUT){
564 if(cdp->cd_caflags & n_CMD_ARG_V){
565 char const *emsg;
567 emsg = line.s; /* xxx Cannot pass &char* as char const**, so no cp */
568 vput = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIM_SPACE |
569 n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_LOG |
570 n_SHEXP_PARSE_META_SEMICOLON | n_SHEXP_PARSE_META_KEEP), &emsg);
571 line.l -= PTR2SIZE(emsg - line.s);
572 line.s = cp = n_UNCONST(emsg);
573 if(cp == NULL)
574 emsg = N_("could not parse input token");
575 else if(!n_shexp_is_valid_varname(vput))
576 emsg = N_("not a valid variable name");
577 else if(!n_var_is_user_writable(vput))
578 emsg = N_("either not a user writable, or a boolean variable");
579 else
580 emsg = NULL;
581 if(emsg != NULL){
582 n_err("`%s': vput: %s: %s\n",
583 cdp->cd_name, V_(emsg), n_shexp_quote_cp(vput, FAL0));
584 nerrn = n_ERR_NOTSUP;
585 rv = -1;
586 goto jleave;
588 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
589 * TODO on in getrawlist() etc., i.e., the argument vector producers,
590 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
591 }else{
592 n_err(_("`vput' prefix does not affect `%s'\n"), cdp->cd_name);
593 flags &= ~a_VPUT;
597 switch(cdp->cd_caflags & n_CMD_ARG_TYPE_MASK){
598 case n_CMD_ARG_TYPE_MSGLIST:
599 /* Message list defaulting to nearest forward legal message */
600 if(n_msgvec == NULL)
601 goto jmsglist_err;
602 if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_msgflag)) < 0){
603 nerrn = n_ERR_NOMSG;
604 flags |= a_NO_ERRNO;
605 break;
607 if(c == 0){
608 if((n_msgvec[0] = first(cdp->cd_msgflag, cdp->cd_msgmask)) != 0)
609 c = 1;
610 else{
611 jmsglist_err:
612 if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
613 (n_poption & n_PO_D_V))
614 fprintf(n_stdout, _("No applicable messages\n"));
615 nerrn = n_ERR_NOMSG;
616 flags |= a_NO_ERRNO;
617 break;
620 jmsglist_go:
621 /* C99 */{
622 int *mvp;
624 mvp = n_autorec_calloc(c +1, sizeof *mvp);
625 while(c-- > 0)
626 mvp[c] = n_msgvec[c];
627 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /*XXX*/
628 n_err_no = 0;
629 rv = (*cdp->cd_func)(mvp);
631 break;
633 case n_CMD_ARG_TYPE_NDMLIST:
634 /* Message list with no defaults, but no error if none exist */
635 if(n_msgvec == NULL)
636 goto jmsglist_err;
637 if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_msgflag)) < 0){
638 nerrn = n_ERR_NOMSG;
639 flags |= a_NO_ERRNO;
640 break;
642 goto jmsglist_go;
644 case n_CMD_ARG_TYPE_STRING:
645 /* Just the straight string, old style, with leading blanks removed */
646 for(cp = line.s; spacechar(*cp);)
647 ++cp;
648 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
649 n_err_no = 0;
650 rv = (*cdp->cd_func)(cp);
651 break;
653 case n_CMD_ARG_TYPE_RAWDAT:
654 /* Just the straight string, placed in argv[] */
655 argvp = argv_stack;
656 if(flags & a_VPUT)
657 *argvp++ = vput;
658 *argvp++ = line.s;
659 *argvp = NULL;
660 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
661 n_err_no = 0;
662 rv = (*cdp->cd_func)(argv_stack);
663 break;
665 case n_CMD_ARG_TYPE_WYSH:
666 c = 1;
667 if(0){
668 /* FALLTHRU */
669 case n_CMD_ARG_TYPE_WYRA:
670 c = (flags & a_WYSH) ? 1 : 0;
671 if(0){
672 case n_CMD_ARG_TYPE_RAWLIST:
673 c = 0;
676 argvp = argv_base = n_autorec_alloc(sizeof(*argv_base) * n_MAXARGC);
677 if(flags & a_VPUT)
678 *argvp++ = vput;
679 if((c = getrawlist((c != 0), argvp,
680 (n_MAXARGC - ((flags & a_VPUT) != 0)), line.s, line.l)) < 0){
681 n_err(_("Invalid argument list\n"));
682 flags |= a_NO_ERRNO;
683 break;
686 if(c < cdp->cd_minargs){
687 n_err(_("`%s' requires at least %u arg(s)\n"),
688 cdp->cd_name, (ui32_t)cdp->cd_minargs);
689 flags |= a_NO_ERRNO;
690 break;
692 #undef cd_minargs
693 if(c > cdp->cd_maxargs){
694 n_err(_("`%s' takes no more than %u arg(s)\n"),
695 cdp->cd_name, (ui32_t)cdp->cd_maxargs);
696 flags |= a_NO_ERRNO;
697 break;
699 #undef cd_maxargs
701 if(flags & a_LOCAL)
702 n_pstate |= n_PS_ARGMOD_LOCAL;
703 if(flags & a_VPUT)
704 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO due to getrawlist(), as above */
706 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
707 n_err_no = 0;
708 rv = (*cdp->cd_func)(argv_base);
709 if(a_go_xcall != NULL)
710 goto jret0;
711 break;
713 case n_CMD_ARG_TYPE_ARG:{
714 /* TODO The _ARG_TYPE_ARG is preliminary, in the end we should have a
715 * TODO per command-ctx carrier that also has slots for it arguments,
716 * TODO and that should be passed along all the way. No more arglists
717 * TODO here, etc. */
718 struct n_cmd_arg_ctx cac;
720 cac.cac_desc = cdp->cd_cadp;
721 cac.cac_indat = line.s;
722 cac.cac_inlen = line.l;
723 cac.cac_msgflag = cdp->cd_msgflag;
724 cac.cac_msgmask = cdp->cd_msgmask;
725 if(!n_cmd_arg_parse(&cac)){
726 flags |= a_NO_ERRNO;
727 break;
730 if(flags & a_VPUT){
731 cac.cac_vput = vput;
732 /* Global "hack" not used: n_pstate |= n_PS_ARGMOD_VPUT; */
733 }else
734 cac.cac_vput = NULL;
736 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
737 n_err_no = 0;
738 rv = (*cdp->cd_func)(&cac);
739 if(a_go_xcall != NULL)
740 goto jret0;
741 }break;
743 default:
744 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
745 cdp->cd_caflags & n_CMD_ARG_TYPE_MASK); )
746 nerrn = n_ERR_NOTOBACCO;
747 nexn = 1;
748 goto jret0;
751 if(gecp->gec_hist_flags & a_GO_HIST_ADD){
752 if(cdp->cd_caflags & n_CMD_ARG_H)
753 gecp->gec_hist_flags = a_GO_HIST_NONE;
754 else if((cdp->cd_caflags & n_CMD_ARG_G) ||
755 (n_pstate & n_PS_MSGLIST_GABBY))
756 gecp->gec_hist_flags |= a_GO_HIST_GABBY;
759 if(rv != 0){
760 if(!(flags & a_NO_ERRNO)){
761 if(cdp->cd_caflags & n_CMD_ARG_EM)
762 flags |= a_NO_ERRNO;
763 else if((nerrn = n_err_no) == 0)
764 nerrn = n_ERR_INVAL;
765 }/*else
766 flags ^= a_NO_ERRNO;*/
767 }else if(cdp->cd_caflags & n_CMD_ARG_EM)
768 flags |= a_NO_ERRNO;
769 else
770 nerrn = n_ERR_NONE;
771 jleave:
772 nexn = rv;
774 if(flags & a_IGNERR){
775 n_pstate &= ~n_PS_ERR_EXIT_MASK;
776 if(!(n_pstate & n_PS_ERR_EXIT_MASK))
777 n_exit_status = n_EXIT_OK;
778 }else if(rv != 0){
779 bool_t bo;
781 if((bo = ok_blook(batch_exit_on_error))){
782 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
783 if(!(n_poption & n_PO_BATCH_FLAG))
784 bo = FAL0;
786 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
787 n_pstate |= n_PS_ERR_QUIT;
788 else if(ok_blook(posix)){
789 if(n_psonce & n_PSO_STARTED)
790 rv = 0;
791 else if(!(n_psonce & n_PSO_INTERACTIVE))
792 n_pstate |= n_PS_ERR_XIT;
793 }else
794 rv = 0;
796 if(rv != 0){
797 if(n_exit_status == n_EXIT_OK)
798 n_exit_status = n_EXIT_ERR;
799 if((n_poption & n_PO_D_V) &&
800 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
801 n_alert(_("Non-interactive, bailing out due to errors "
802 "in startup load phase"));
803 goto jret;
807 if(cdp == NULL)
808 goto jret0;
809 if((cdp->cd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
810 if(visible(dot))
811 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
812 sizeof("\\type") -1);
814 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
815 !(cdp->cd_caflags & n_CMD_ARG_T))
816 n_pstate |= n_PS_SAW_COMMAND;
817 jret0:
818 rv = 0;
819 jret:
820 if(!(flags & a_NO_ERRNO))
821 n_pstate_err_no = nerrn;
822 n_pstate_ex_no = nexn;
823 NYD_LEAVE;
824 return (rv == 0);
827 static void
828 a_go_hangup(int s){
829 NYD_X; /* Signal handler */
830 n_UNUSED(s);
831 /* nothing to do? */
832 exit(n_EXIT_ERR);
835 #ifdef HAVE_IMAP
836 FL void n_go_onintr_for_imap(void){a_go_onintr(0);}
837 #endif
838 static void
839 a_go_onintr(int s){ /* TODO block signals while acting */
840 NYD_X; /* Signal handler */
841 n_UNUSED(s);
843 safe_signal(SIGINT, a_go_onintr);
845 termios_state_reset();
847 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
849 if(interrupts != 1)
850 n_err_sighdl(_("Interrupt\n"));
851 safe_signal(SIGPIPE, a_go_oldpipe);
852 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
855 static void
856 a_go_cleanup(enum a_go_cleanup_mode gcm){
857 /* Signals blocked */
858 struct a_go_ctx *gcp;
859 NYD_ENTER;
861 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
862 hold_all_sigs();
863 jrestart:
864 gcp = a_go_ctx;
866 /* Free input injections of this level first */
867 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
868 struct a_go_input_inject **giipp, *giip;
870 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
871 *giipp = giip->gii_next;
872 n_free(giip);
876 /* Cleanup non-crucial external stuff */
877 n_COLOUR(
878 if(gcp->gc_data.gdc_colour != NULL)
879 n_colour_stack_del(&gcp->gc_data);
882 /* Work the actual context (according to cleanup mode) */
883 if(gcp->gc_outer == NULL){
884 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
885 if(a_go_xcall != NULL){
886 n_free(a_go_xcall);
887 a_go_xcall = NULL;
889 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
890 n_pstate &= ~n_PS_ERR_EXIT_MASK;
891 close_all_files();
892 }else{
893 if(!(n_pstate & n_PS_SOURCING))
894 close_all_files();
897 n_memory_reset();
899 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
900 assert(a_go_xcall == NULL);
901 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
902 assert(gcp->gc_on_finalize == NULL);
903 n_COLOUR( assert(gcp->gc_data.gdc_colour == NULL); )
904 goto jxleave;
905 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
906 n_memory_reset();
907 goto jxleave;
908 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
909 n_stdin = gcp->gc_splice_stdin;
910 n_stdout = gcp->gc_splice_stdout;
911 n_psonce = gcp->gc_splice_psonce;
912 goto jstackpop;
915 /* Cleanup crucial external stuff */
916 if(gcp->gc_data.gdc_ifcond != NULL){
917 n_cnd_if_stack_del(&gcp->gc_data);
918 if(!(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
919 !(gcp->gc_flags & a_GO_FORCE_EOF) && a_go_xcall == NULL &&
920 !(n_psonce & n_PSO_EXIT_MASK)){
921 n_err(_("Unmatched `if' at end of %s %s\n"),
922 ((gcp->gc_flags & a_GO_MACRO
923 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
924 : _("`source'd file"))),
925 gcp->gc_name);
926 gcm |= a_GO_CLEANUP_ERROR;
930 /* Teardown context */
931 if(gcp->gc_flags & a_GO_MACRO){
932 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
933 char **lp;
935 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
936 n_free(*lp);
937 ++gcp->gc_loff;
939 /* Part of gcp's memory chunk, then */
940 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
941 n_free(gcp->gc_lines);
943 }else if(gcp->gc_flags & a_GO_PIPE)
944 /* XXX command manager should -TERM then -KILL instead of hoping
945 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
946 Pclose(gcp->gc_file, TRU1);
947 else if(gcp->gc_flags & a_GO_FILE)
948 Fclose(gcp->gc_file);
950 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
951 if(gcp->gc_data.gdc_mempool != NULL)
952 n_memory_pool_pop(NULL);
953 }else
954 n_memory_reset();
956 jstackpop:
957 /* Update a_go_ctx and n_go_data, n_pstate ... */
958 a_go_ctx = gcp->gc_outer;
959 assert(a_go_ctx != NULL);
960 /* C99 */{
961 struct a_go_ctx *x;
963 for(x = a_go_ctx; x->gc_flags & a_GO_DATACTX_INHERITED;){
964 x = x->gc_outer;
965 assert(x != NULL);
967 n_go_data = &x->gc_data;
970 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
971 (a_GO_MACRO | a_GO_SUPER_MACRO)){
972 n_pstate &= ~n_PS_SOURCING;
973 assert(n_pstate & n_PS_ROBOT);
974 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
975 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
976 else
977 assert(n_pstate & n_PS_ROBOT);
979 if(gcp->gc_on_finalize != NULL)
980 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
982 if(gcm & a_GO_CLEANUP_ERROR){
983 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
984 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
985 goto jerr;
987 jleave:
988 if(gcp->gc_flags & a_GO_FREE)
989 n_free(gcp);
991 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
992 goto jrestart;
994 jxleave:
995 NYD_LEAVE;
996 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
997 rele_all_sigs();
998 return;
1000 jerr:
1001 /* With *posix* we follow what POSIX says:
1002 * Any errors in the start-up file shall either cause mailx to
1003 * terminate with a diagnostic message and a non-zero status or to
1004 * continue after writing a diagnostic message, ignoring the
1005 * remainder of the lines in the start-up file
1006 * Print the diagnostic only for the outermost resource unless the user
1007 * is debugging or in verbose mode */
1008 if((n_poption & n_PO_D_V) ||
1009 (!(n_psonce & n_PSO_STARTED) &&
1010 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1011 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
1012 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1013 n_alert(_("Stopped %s %s due to errors%s"),
1014 (n_psonce & n_PSO_STARTED
1015 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1016 : (gcp->gc_flags & a_GO_MACRO
1017 ? (gcp->gc_flags & a_GO_MACRO_CMD
1018 ? _("evaluating command") : _("evaluating macro"))
1019 : (gcp->gc_flags & a_GO_PIPE
1020 ? _("executing `source'd pipe")
1021 : (gcp->gc_flags & a_GO_FILE
1022 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1024 : (gcp->gc_flags & a_GO_MACRO
1025 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
1026 ? _("evaluating command line") : _("evaluating macro"))
1027 : _("loading initialization resource"))),
1028 n_shexp_quote_cp(gcp->gc_name, FAL0),
1029 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
1030 goto jleave;
1033 static bool_t
1034 a_go_file(char const *file, bool_t silent_open_error){
1035 struct a_go_ctx *gcp;
1036 sigset_t osigmask;
1037 size_t nlen;
1038 char *nbuf;
1039 bool_t ispipe;
1040 FILE *fip;
1041 NYD_ENTER;
1043 fip = NULL;
1045 /* Being a command argument file is space-trimmed *//* TODO v15 with
1046 * TODO WYRALIST this is no longer necessary true, and for that we
1047 * TODO don't set _PARSE_TRIM_SPACE because we cannot! -> cmd-tab.h!! */
1048 #if 0
1049 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
1050 file[--nlen] == '|')))
1051 #else
1052 ispipe = FAL0;
1053 if(!silent_open_error){
1054 for(nlen = strlen(file); nlen > 0;){
1055 char c;
1057 c = file[--nlen];
1058 if(!spacechar(c)){
1059 if(c == '|'){
1060 nbuf = savestrbuf(file, nlen);
1061 ispipe = TRU1;
1063 break;
1067 #endif
1069 if(ispipe){
1070 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
1071 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
1072 goto jeopencheck;
1073 }else if((nbuf = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL)
1074 goto jeopencheck;
1075 else if((fip = Fopen(nbuf, "r")) == NULL){
1076 jeopencheck:
1077 if(!silent_open_error || (n_poption & n_PO_D_V))
1078 n_perr(nbuf, 0);
1079 if(silent_open_error)
1080 fip = (FILE*)-1;
1081 goto jleave;
1084 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1086 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1087 (nlen = strlen(nbuf) +1));
1088 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1090 hold_all_sigs();
1092 gcp->gc_outer = a_go_ctx;
1093 gcp->gc_osigmask = osigmask;
1094 gcp->gc_file = fip;
1095 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1096 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1097 memcpy(gcp->gc_name, nbuf, nlen);
1099 a_go_ctx = gcp;
1100 n_go_data = &gcp->gc_data;
1101 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1102 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1103 fip = NULL;
1104 jleave:
1105 NYD_LEAVE;
1106 return (fip != NULL);
1109 static bool_t
1110 a_go_load(struct a_go_ctx *gcp){
1111 NYD2_ENTER;
1113 assert(!(n_psonce & n_PSO_STARTED));
1114 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1116 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1117 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1119 hold_all_sigs();
1121 /* POSIX:
1122 * Any errors in the start-up file shall either cause mailx to terminate
1123 * with a diagnostic message and a non-zero status or to continue after
1124 * writing a diagnostic message, ignoring the remainder of the lines in
1125 * the start-up file. */
1126 gcp->gc_outer = a_go_ctx;
1127 a_go_ctx = gcp;
1128 n_go_data = &gcp->gc_data;
1129 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1130 n_pstate |= n_PS_ROBOT |
1131 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1133 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1135 rele_all_sigs();
1137 n_go_main_loop();
1138 NYD2_LEAVE;
1139 return (((n_psonce & n_PSO_EXIT_MASK) |
1140 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1143 static void
1144 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1145 NYD_X; /* Signal handler */
1146 n_UNUSED(sig);
1147 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1150 static bool_t
1151 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1152 sighandler_type soldhdl;
1153 struct a_go_eval_ctx gec;
1154 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1155 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1156 sigset_t osigmask;
1157 NYD2_ENTER;
1159 memset(&gec, 0, sizeof gec);
1160 osigmask = gcp->gc_osigmask;
1161 hadint = FAL0;
1162 f = a_RETOK;
1164 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1165 safe_signal(SIGINT, &a_go__eloopint);
1166 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1167 hold_all_sigs();
1168 hadint = TRU1;
1169 f &= ~a_RETOK;
1170 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1171 goto jjump;
1175 for(;; f |= a_TICKED){
1176 int n;
1178 if(f & a_TICKED)
1179 n_memory_reset();
1181 /* Read a line of commands and handle end of file specially */
1182 gec.gec_line.l = gec.gec_line_size;
1183 rele_all_sigs();
1184 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL, NULL);
1185 hold_all_sigs();
1186 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1187 gec.gec_line.l = (ui32_t)n;
1189 if(n < 0)
1190 break;
1192 rele_all_sigs();
1193 assert(gec.gec_hist_flags == a_GO_HIST_NONE);
1194 if(!a_go_evaluate(&gec))
1195 f &= ~a_RETOK;
1196 hold_all_sigs();
1198 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1199 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1200 break;
1203 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1204 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1205 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1206 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1208 if(gec.gec_line.s != NULL)
1209 n_free(gec.gec_line.s);
1211 if(soldhdl != SIG_IGN)
1212 safe_signal(SIGINT, soldhdl);
1213 NYD2_LEAVE;
1214 rele_all_sigs();
1215 if(hadint){
1216 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1217 n_raise(SIGINT);
1219 return (f & a_RETOK);
1222 FL void
1223 n_go_init(void){
1224 struct a_go_ctx *gcp;
1225 NYD2_ENTER;
1227 assert(n_stdin != NULL);
1229 gcp = (void*)a_go__mainctx_b.uf;
1230 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1231 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1232 gcp->gc_file = n_stdin;
1233 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1234 a_go_ctx = gcp;
1235 n_go_data = &gcp->gc_data;
1237 n_child_manager_start();
1238 NYD2_LEAVE;
1241 FL bool_t
1242 n_go_main_loop(void){ /* FIXME */
1243 struct a_go_eval_ctx gec;
1244 int n, eofcnt;
1245 bool_t volatile rv;
1246 NYD_ENTER;
1248 rv = TRU1;
1250 if (!(n_pstate & n_PS_SOURCING)) {
1251 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1252 safe_signal(SIGINT, &a_go_onintr);
1253 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1254 safe_signal(SIGHUP, &a_go_hangup);
1256 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1257 safe_signal(SIGPIPE, a_go_oldpipe);
1259 memset(&gec, 0, sizeof gec);
1261 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1262 hold_all_sigs();
1264 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1265 interrupts = 0;
1267 if(gec.gec_ever_seen)
1268 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1270 if (!(n_pstate & n_PS_SOURCING)) {
1271 char *cp;
1273 /* TODO Note: this buffer may contain a password. We should redefine
1274 * TODO the code flow which has to do that */
1275 if ((cp = termios_state.ts_linebuf) != NULL) {
1276 termios_state.ts_linebuf = NULL;
1277 termios_state.ts_linesize = 0;
1278 n_free(cp); /* TODO pool give-back */
1280 if (gec.gec_line.l > LINESIZE * 3) {
1281 n_free(gec.gec_line.s);
1282 gec.gec_line.s = NULL;
1283 gec.gec_line.l = gec.gec_line_size = 0;
1287 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1288 char *cp;
1290 if ((cp = ok_vlook(newmail)) != NULL) { /* TODO bla */
1291 struct stat st;
1293 if(mb.mb_type == MB_FILE){
1294 if(!stat(mailname, &st) && st.st_size > mailsize) Jnewmail:{
1295 ui32_t odid;
1296 size_t odot;
1298 odot = PTR2SIZE(dot - message);
1299 odid = (n_pstate & n_PS_DID_PRINT_DOT);
1301 rele_all_sigs();
1302 n = setfile(mailname,
1303 (FEDIT_NEWMAIL |
1304 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)));
1305 hold_all_sigs();
1307 if(n < 0) {
1308 n_exit_status |= n_EXIT_ERR;
1309 rv = FAL0;
1310 break;
1312 #ifdef HAVE_IMAP
1313 if(mb.mb_type != MB_IMAP){
1314 #endif
1315 dot = &message[odot];
1316 n_pstate |= odid;
1317 #ifdef HAVE_IMAP
1319 #endif
1321 }else{
1322 #if defined HAVE_MAILDIR || defined HAVE_IMAP
1323 n = (cp != NULL && strcmp(cp, "nopoll"));
1324 #endif
1326 #ifdef HAVE_MAILDIR
1327 if(mb.mb_type == MB_MAILDIR){
1328 if(n != 0)
1329 goto Jnewmail;
1331 #endif
1332 #ifdef HAVE_IMAP
1333 if(mb.mb_type == MB_IMAP){
1334 if(!n)
1335 n = (cp != NULL && strcmp(cp, "noimap"));
1337 if(imap_newmail(n) > (cp == NULL))
1338 goto Jnewmail;
1340 #endif
1345 /* Read a line of commands and handle end of file specially */
1346 gec.gec_line.l = gec.gec_line_size;
1347 /* C99 */{
1348 bool_t histadd;
1350 histadd = (!(n_pstate & n_PS_SOURCING) &&
1351 (n_psonce & n_PSO_INTERACTIVE));
1352 rele_all_sigs();
1353 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1354 &gec.gec_line.s, &gec.gec_line.l, NULL, &histadd);
1355 hold_all_sigs();
1357 gec.gec_hist_flags = histadd ? a_GO_HIST_ADD : a_GO_HIST_NONE;
1359 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1360 gec.gec_line.l = (ui32_t)n;
1362 if (n < 0) {
1363 if (!(n_pstate & n_PS_ROBOT) &&
1364 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1365 ++eofcnt < 4) {
1366 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1367 n_go_input_clearerr();
1368 continue;
1370 break;
1373 n_pstate &= ~n_PS_HOOK_MASK;
1374 rele_all_sigs();
1375 rv = a_go_evaluate(&gec);
1376 hold_all_sigs();
1378 if(gec.gec_hist_flags & a_GO_HIST_ADD){
1379 char const *cc, *ca;
1381 cc = gec.gec_hist_cmd;
1382 ca = gec.gec_hist_args;
1383 if(cc != NULL && ca != NULL)
1384 cc = savecatsep(cc, ' ', ca);
1385 else if(ca != NULL)
1386 cc = ca;
1387 assert(cc != NULL);
1388 n_tty_addhist(cc, (n_GO_INPUT_CTX_DEFAULT |
1389 (gec.gec_hist_flags & a_GO_HIST_GABBY ? n_GO_INPUT_HIST_GABBY
1390 : n_GO_INPUT_NONE)));
1393 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1394 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1395 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1396 default: break;
1398 if(n_psonce & n_PSO_EXIT_MASK)
1399 break;
1401 if(!rv)
1402 break;
1405 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1406 (rv ? 0 : a_GO_CLEANUP_ERROR));
1408 if (gec.gec_line.s != NULL)
1409 n_free(gec.gec_line.s);
1411 rele_all_sigs();
1412 NYD_LEAVE;
1413 return rv;
1416 FL void
1417 n_go_input_clearerr(void){
1418 FILE *fp;
1419 NYD2_ENTER;
1421 fp = NULL;
1423 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1424 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1425 fp = a_go_ctx->gc_file;
1427 if(fp != NULL){
1428 a_go_ctx->gc_flags &= ~a_GO_IS_EOF;
1429 clearerr(fp);
1431 NYD2_LEAVE;
1434 FL void
1435 n_go_input_force_eof(void){
1436 NYD2_ENTER;
1437 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1438 NYD2_LEAVE;
1441 FL bool_t
1442 n_go_input_is_eof(void){
1443 bool_t rv;
1444 NYD2_ENTER;
1446 rv = ((a_go_ctx->gc_flags & a_GO_IS_EOF) != 0);
1447 NYD2_LEAVE;
1448 return rv;
1451 FL void
1452 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1453 size_t len){
1454 NYD_ENTER;
1455 if(len == UIZ_MAX)
1456 len = strlen(buf);
1458 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1459 len > 0){
1460 struct a_go_input_inject *giip, **giipp;
1462 hold_all_sigs();
1464 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1465 ) + 1 + len +1);
1466 giipp = &a_go_ctx->gc_inject;
1467 giip->gii_next = *giipp;
1468 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1469 giip->gii_no_history = ((giif & n_GO_INPUT_INJECT_HISTORY) == 0);
1470 memcpy(&giip->gii_dat[0], buf, len);
1471 giip->gii_dat[giip->gii_len = len] = '\0';
1472 *giipp = giip;
1474 rele_all_sigs();
1476 NYD_LEAVE;
1479 FL int
1480 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1481 size_t *linesize, char const *string, bool_t *histok_or_null
1482 n_MEMORY_DEBUG_ARGS){
1483 /* TODO readline: linebuf pool!; n_go_input should return si64_t */
1484 struct n_string xprompt;
1485 FILE *ifile;
1486 bool_t doprompt, dotty;
1487 char const *iftype;
1488 struct a_go_input_inject *giip;
1489 int nold, n;
1490 bool_t histok;
1491 NYD2_ENTER;
1493 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1494 hold_all_sigs();
1496 histok = FAL0;
1498 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
1499 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1500 n = -1;
1501 goto jleave;
1504 if(gif & n_GO_INPUT_FORCE_STDIN)
1505 goto jforce_stdin;
1507 /* Special case macro mode: never need to prompt, lines have always been
1508 * unfolded already */
1509 if(a_go_ctx->gc_flags & a_GO_MACRO){
1510 if(*linebuf != NULL)
1511 n_free(*linebuf);
1513 /* Injection in progress? Don't care about the autocommit state here */
1514 if((giip = a_go_ctx->gc_inject) != NULL){
1515 a_go_ctx->gc_inject = giip->gii_next;
1517 /* Simply "reuse" allocation, copy string to front of it */
1518 jinject:
1519 *linesize = giip->gii_len;
1520 *linebuf = (char*)giip;
1521 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
1522 iftype = "INJECTION";
1523 }else{
1524 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
1525 *linesize = 0;
1526 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1527 n = -1;
1528 goto jleave;
1531 ++a_go_ctx->gc_loff;
1532 *linesize = strlen(*linebuf);
1533 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
1534 *linebuf = sbufdup(*linebuf, *linesize);
1536 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
1537 ? "-X OPTION"
1538 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
1540 n = (int)*linesize;
1541 n_pstate |= n_PS_READLINE_NL;
1542 goto jhave_dat;
1543 }else{
1544 /* Injection in progress? */
1545 struct a_go_input_inject **giipp;
1547 giipp = &a_go_ctx->gc_inject;
1549 if((giip = *giipp) != NULL){
1550 *giipp = giip->gii_next;
1552 if(giip->gii_commit){
1553 if(*linebuf != NULL)
1554 n_free(*linebuf);
1555 histok = !giip->gii_no_history;
1556 goto jinject; /* (above) */
1557 }else{
1558 string = savestrbuf(giip->gii_dat, giip->gii_len);
1559 n_free(giip);
1564 jforce_stdin:
1565 n_pstate &= ~n_PS_READLINE_NL;
1566 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
1567 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
1568 histok = (n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
1569 (n_PSO_INTERACTIVE | n_PSO_STARTED) && !(n_pstate & n_PS_ROBOT);
1570 doprompt = !(gif & n_GO_INPUT_FORCE_STDIN) && histok;
1571 dotty = (doprompt && !ok_blook(line_editor_disable));
1572 if(!doprompt)
1573 gif |= n_GO_INPUT_PROMPT_NONE;
1574 else{
1575 if(!dotty)
1576 n_string_creat_auto(&xprompt);
1577 if(prompt == NULL)
1578 gif |= n_GO_INPUT_PROMPT_EVAL;
1581 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
1582 if(!dotty && (gif & n_GO_INPUT_PROMPT_NONE))
1583 fflush(n_stdout);
1585 if(gif & n_GO_INPUT_FORCE_STDIN){
1586 struct a_go_readctl_ctx *grcp;
1588 grcp = n_readctl_overlay;
1589 ifile = (grcp == NULL || grcp->grc_fp == NULL) ? n_stdin : grcp->grc_fp;
1590 }else
1591 ifile = a_go_ctx->gc_file;
1592 if(ifile == NULL){
1593 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
1594 (a_go_ctx->gc_flags & a_GO_MACRO));
1595 ifile = n_stdin;
1598 for(nold = n = 0;;){
1599 if(dotty){
1600 assert(ifile == n_stdin);
1601 if(string != NULL && (n = (int)strlen(string)) > 0){
1602 if(*linesize > 0)
1603 *linesize += n +1;
1604 else
1605 *linesize = (size_t)n + LINESIZE +1;
1606 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
1607 memcpy(*linebuf, string, (size_t)n +1);
1609 string = NULL;
1611 rele_all_sigs();
1613 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n, histok_or_null
1614 n_MEMORY_DEBUG_ARGSCALL);
1616 hold_all_sigs();
1618 if(n < 0 && !ferror(ifile)) /* EOF never i guess */
1619 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1620 }else{
1621 if(!(gif & n_GO_INPUT_PROMPT_NONE))
1622 n_tty_create_prompt(&xprompt, prompt, gif);
1624 rele_all_sigs();
1626 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
1627 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
1628 fflush(n_stdout);
1631 n = (readline_restart)(ifile, linebuf, linesize, n
1632 n_MEMORY_DEBUG_ARGSCALL);
1634 hold_all_sigs();
1636 if(n < 0 && !ferror(ifile))
1637 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1639 if(n > 0 && nold > 0){
1640 char const *cp;
1641 int i;
1643 i = 0;
1644 cp = &(*linebuf)[nold];
1645 while(spacechar(*cp) && n - i >= nold)
1646 ++cp, ++i;
1647 if(i > 0){
1648 memmove(&(*linebuf)[nold], cp, n - nold - i);
1649 n -= i;
1650 (*linebuf)[n] = '\0';
1655 if(n <= 0)
1656 break;
1658 /* POSIX says:
1659 * TODO This does not take care for current shell quote mode!
1660 * TODO Thus "echo '\<NEWLINE HERE> bla' will never work
1661 * An unquoted <backslash> at the end of a command line shall
1662 * be discarded and the next line shall continue the command */
1663 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\')
1664 break;
1666 /* Definitely outside of quotes, thus the quoting rules are so that an
1667 * uneven number of successive reverse solidus at EOL is a continuation */
1668 if(n > 1){
1669 size_t i, j;
1671 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
1672 if((*linebuf)[i] != '\\')
1673 break;
1674 if(!(j & 1))
1675 break;
1677 (*linebuf)[nold = --n] = '\0';
1678 gif |= n_GO_INPUT_NL_FOLLOW;
1681 if(n < 0)
1682 goto jleave;
1683 if(dotty)
1684 n_pstate |= n_PS_READLINE_NL;
1685 (*linebuf)[*linesize = n] = '\0';
1687 jhave_dat:
1688 if(n_poption & n_PO_D_VV)
1689 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
1690 jleave:
1691 if (n_pstate & n_PS_PSTATE_PENDMASK)
1692 a_go_update_pstate();
1694 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
1695 * TODO but only established from the outside and we need to drop this
1696 * TODO overlay context somehow */
1697 if(n < 0 && (a_go_ctx->gc_flags & a_GO_SPLICE))
1698 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
1700 if(histok_or_null != NULL && !histok)
1701 *histok_or_null = FAL0;
1703 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1704 rele_all_sigs();
1705 NYD2_LEAVE;
1706 return n;
1709 FL char *
1710 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
1711 char const *string){
1712 struct n_sigman sm;
1713 bool_t histadd;
1714 size_t linesize;
1715 char *linebuf, * volatile rv;
1716 int n;
1717 NYD2_ENTER;
1719 linesize = 0;
1720 linebuf = NULL;
1721 rv = NULL;
1723 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1724 case 0:
1725 break;
1726 default:
1727 goto jleave;
1730 histadd = TRU1;
1731 n = n_go_input(gif, prompt, &linebuf, &linesize, string, &histadd);
1732 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
1733 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE) &&
1734 histadd)
1735 n_tty_addhist(rv, gif);
1737 n_sigman_cleanup_ping(&sm);
1738 jleave:
1739 if(linebuf != NULL)
1740 n_free(linebuf);
1741 NYD2_LEAVE;
1742 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1743 return rv;
1746 FL bool_t
1747 n_go_load(char const *name){
1748 struct a_go_ctx *gcp;
1749 size_t i;
1750 FILE *fip;
1751 bool_t rv;
1752 NYD_ENTER;
1754 rv = TRU1;
1756 if(name == NULL || *name == '\0')
1757 goto jleave;
1758 else if((fip = Fopen(name, "r")) == NULL){
1759 if(n_poption & n_PO_D_V)
1760 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
1761 goto jleave;
1764 i = strlen(name) +1;
1765 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
1766 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1768 gcp->gc_file = fip;
1769 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
1770 memcpy(gcp->gc_name, name, i);
1772 if(n_poption & n_PO_D_V)
1773 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
1774 rv = a_go_load(gcp);
1775 jleave:
1776 NYD_LEAVE;
1777 return rv;
1780 FL bool_t
1781 n_go_Xargs(char const **lines, size_t cnt){
1782 static char const name[] = "-X";
1784 union{
1785 bool_t rv;
1786 ui64_t align;
1787 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
1788 } b;
1789 char const *srcp, *xsrcp;
1790 char *cp;
1791 size_t imax, i, len;
1792 struct a_go_ctx *gcp;
1793 NYD_ENTER;
1795 gcp = (void*)b.uf;
1796 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1798 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
1799 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
1800 memcpy(gcp->gc_name, name, sizeof name);
1802 /* The problem being that we want to support reverse solidus newline
1803 * escaping also within multiline -X, i.e., POSIX says:
1804 * An unquoted <backslash> at the end of a command line shall
1805 * be discarded and the next line shall continue the command
1806 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
1807 * entire lines array and set _MACRO_FREE_DATA */
1808 imax = cnt + 1;
1809 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
1811 /* For each of the input lines.. */
1812 for(i = len = 0, cp = NULL; cnt > 0;){
1813 bool_t keep;
1814 size_t j;
1816 if((j = strlen(srcp = *lines)) == 0){
1817 ++lines, --cnt;
1818 continue;
1821 /* Separate one line from a possible multiline input string */
1822 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
1823 *lines = &xsrcp[1];
1824 j = PTR2SIZE(xsrcp - srcp);
1825 }else
1826 ++lines, --cnt;
1828 /* The (separated) string may itself indicate soft newline escaping */
1829 if((keep = (srcp[j - 1] == '\\'))){
1830 size_t xj, xk;
1832 /* Need an uneven number of reverse solidus */
1833 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
1834 if(srcp[xj] != '\\')
1835 break;
1836 if(xk & 1)
1837 --j;
1838 else
1839 keep = FAL0;
1842 /* Strip any leading WS from follow lines, then */
1843 if(cp != NULL)
1844 while(j > 0 && spacechar(*srcp))
1845 ++srcp, --j;
1847 if(j > 0){
1848 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
1849 imax += 4;
1850 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
1851 imax);
1853 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
1854 memcpy(&cp[len], srcp, j);
1855 cp[len += j] = '\0';
1857 if(!keep)
1858 ++i;
1860 if(!keep)
1861 cp = NULL, len = 0;
1863 if(cp != NULL){
1864 assert(i + 1 < imax);
1865 gcp->gc_lines[i++] = cp;
1867 gcp->gc_lines[i] = NULL;
1869 b.rv = a_go_load(gcp);
1870 NYD_LEAVE;
1871 return b.rv;
1874 FL int
1875 c_source(void *v){
1876 int rv;
1877 NYD_ENTER;
1879 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
1880 NYD_LEAVE;
1881 return rv;
1884 FL int
1885 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
1886 int rv;
1887 NYD_ENTER;
1889 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
1890 NYD_LEAVE;
1891 return rv;
1894 FL bool_t
1895 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
1896 void (*on_finalize)(void*), void *finalize_arg){
1897 struct a_go_ctx *gcp;
1898 size_t i;
1899 int rv;
1900 sigset_t osigmask;
1901 NYD_ENTER;
1903 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1905 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1906 (i = strlen(name) +1));
1907 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1909 hold_all_sigs();
1911 gcp->gc_outer = a_go_ctx;
1912 gcp->gc_osigmask = osigmask;
1913 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
1914 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1915 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0) |
1916 ((gif & n_GO_INPUT_NO_XCALL) ? a_GO_XCALL_IS_CALL : 0);
1917 gcp->gc_lines = lines;
1918 gcp->gc_on_finalize = on_finalize;
1919 gcp->gc_finalize_arg = finalize_arg;
1920 memcpy(gcp->gc_name, name, i);
1922 a_go_ctx = gcp;
1923 n_go_data = &gcp->gc_data;
1924 n_pstate |= n_PS_ROBOT;
1925 rv = a_go_event_loop(gcp, gif);
1927 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
1928 if(a_go_xcall != NULL){
1929 void *vp;
1930 struct n_cmd_arg_ctx *cacp;
1932 if(a_go_xcall == (void*)-1)
1933 a_go_xcall = NULL;
1934 else if(((void const*)(cacp = a_go_xcall)->cac_indat) == gcp){
1935 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
1936 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
1937 while(a_go_xcall != NULL){
1938 hold_all_sigs();
1940 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
1942 vp = a_go_xcall;
1943 a_go_xcall = NULL;
1944 cacp = n_cmd_arg_restore_from_heap(vp);
1945 n_free(vp);
1947 rele_all_sigs();
1949 (void)c_call(cacp);
1951 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) == 0);
1952 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1955 NYD_LEAVE;
1956 return rv;
1959 FL bool_t
1960 n_go_command(enum n_go_input_flags gif, char const *cmd){
1961 struct a_go_ctx *gcp;
1962 bool_t rv;
1963 size_t i, ial;
1964 sigset_t osigmask;
1965 NYD_ENTER;
1967 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1969 i = strlen(cmd) +1;
1970 ial = n_ALIGN(i);
1971 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1972 ial + 2*sizeof(char*));
1973 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1975 hold_all_sigs();
1977 gcp->gc_outer = a_go_ctx;
1978 gcp->gc_osigmask = osigmask;
1979 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
1980 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1981 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
1982 gcp->gc_lines = (void*)&gcp->gc_name[ial];
1983 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
1984 gcp->gc_lines[1] = NULL;
1986 a_go_ctx = gcp;
1987 n_go_data = &gcp->gc_data;
1988 n_pstate |= n_PS_ROBOT;
1989 rv = a_go_event_loop(gcp, gif);
1990 NYD_LEAVE;
1991 return rv;
1994 FL void
1995 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
1996 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
1997 struct a_go_ctx *gcp;
1998 size_t i;
1999 sigset_t osigmask;
2000 NYD_ENTER;
2002 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2004 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2005 (i = strlen(cmd) +1));
2006 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2008 hold_all_sigs();
2010 gcp->gc_outer = a_go_ctx;
2011 gcp->gc_osigmask = osigmask;
2012 gcp->gc_file = new_stdin;
2013 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE | a_GO_DATACTX_INHERITED;
2014 gcp->gc_on_finalize = on_finalize;
2015 gcp->gc_finalize_arg = finalize_arg;
2016 gcp->gc_splice_stdin = n_stdin;
2017 gcp->gc_splice_stdout = n_stdout;
2018 gcp->gc_splice_psonce = n_psonce;
2019 memcpy(gcp->gc_name, cmd, i);
2021 n_stdin = new_stdin;
2022 n_stdout = new_stdout;
2023 n_psonce = new_psonce;
2024 a_go_ctx = gcp;
2025 /* Do NOT touch n_go_data! */
2026 n_pstate |= n_PS_ROBOT;
2028 rele_all_sigs();
2029 NYD_LEAVE;
2032 FL void
2033 n_go_splice_hack_remove_after_jump(void){
2034 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2037 FL bool_t
2038 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2039 struct a_go_ctx *gcp;
2040 bool_t rv;
2041 NYD2_ENTER;
2043 rv = FAL0;
2045 /* Only when startup completed */
2046 if(!(n_psonce & n_PSO_STARTED))
2047 goto jleave;
2048 /* Only interactive or batch mode (assuming that is ok) */
2049 if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
2050 goto jleave;
2052 /* Not when running any hook */
2053 if(n_pstate & n_PS_HOOK_MASK)
2054 goto jleave;
2056 /* Traverse up the stack:
2057 * . not when controlled by a child process
2058 * TODO . not when there are pipes involved, we neither handle job control,
2059 * TODO nor process groups, that is, controlling terminal acceptably
2060 * . not when sourcing a file */
2061 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2062 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2063 goto jleave;
2066 rv = TRU1;
2067 jleave:
2068 NYD2_LEAVE;
2069 return rv;
2072 FL int
2073 c_eval(void *vp){
2074 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
2075 * TODO ARGV with shell rules, but if that is not possible then simply
2076 * TODO adjust argv/argc of "the CmdCtx" that we will have "exec" real cmd */
2077 struct a_go_eval_ctx gec;
2078 struct n_string s_b, *sp;
2079 size_t i, j;
2080 char const **argv, *cp;
2081 NYD_ENTER;
2083 argv = vp;
2085 for(j = i = 0; (cp = argv[i]) != NULL; ++i)
2086 j += strlen(cp);
2088 sp = n_string_creat_auto(&s_b);
2089 sp = n_string_reserve(sp, j);
2091 for(i = 0; (cp = argv[i]) != NULL; ++i){
2092 if(i > 0)
2093 sp = n_string_push_c(sp, ' ');
2094 sp = n_string_push_cp(sp, cp);
2097 memset(&gec, 0, sizeof gec);
2098 gec.gec_line.s = n_string_cp(sp);
2099 gec.gec_line.l = sp->s_len;
2100 if(n_poption & n_PO_D_VV)
2101 n_err(_("EVAL %" PRIuZ " bytes <%s>\n"), gec.gec_line.l, gec.gec_line.s);
2102 (void)/* XXX */a_go_evaluate(&gec);
2103 NYD_LEAVE;
2104 return (a_go_xcall != NULL ? 0 : n_pstate_ex_no);
2107 FL int
2108 c_xcall(void *vp){
2109 int rv;
2110 struct a_go_ctx *gcp;
2111 NYD2_ENTER;
2113 /* The context can only be a macro context, except that possibly a single
2114 * level of `eval' (TODO: yet) was used to double-expand our arguments */
2115 if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
2116 gcp = gcp->gc_outer;
2117 if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD)
2118 ) != a_GO_MACRO){
2119 if(n_poption & n_PO_D_V)
2120 n_err(_("`xcall': can only be used inside a macro, using `call'\n"));
2121 rv = c_call(vp);
2122 goto jleave;
2125 /* Try to roll up the stack as much as possible.
2126 * See a_GO_XCALL_LOOP flag description for more */
2127 if(!(gcp->gc_flags & a_GO_XCALL_IS_CALL) && gcp->gc_outer != NULL){
2128 if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
2129 gcp = gcp->gc_outer;
2130 }else{
2131 /* Otherwise this macro is "invoked from the top level", in which case we
2132 * silently act as if we were `call'... */
2133 rv = c_call(vp);
2134 /* ...which means we must ensure the rest of the macro that was us
2135 * doesn't become evaluated! */
2136 a_go_xcall = (void*)-1;
2137 goto jleave;
2140 /* C99 */{
2141 struct n_cmd_arg_ctx *cacp;
2143 cacp = n_cmd_arg_save_to_heap(vp);
2144 cacp->cac_indat = (char*)gcp;
2145 a_go_xcall = cacp;
2147 rv = 0;
2148 jleave:
2149 NYD2_LEAVE;
2150 return rv;
2153 FL int
2154 c_exit(void *vp){
2155 char const **argv;
2156 NYD_ENTER;
2158 if(*(argv = vp) != NULL && (n_idec_si32_cp(&n_exit_status, *argv, 0, NULL) &
2159 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2160 ) != n_IDEC_STATE_CONSUMED)
2161 n_exit_status |= n_EXIT_ERR;
2163 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2164 fflush(NULL);
2165 _exit(n_exit_status);
2166 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2167 n_err(_("`exit' delayed until compose mode is left\n")); /* XXX ..log? */
2168 n_psonce |= n_PSO_XIT;
2169 NYD_LEAVE;
2170 return 0;
2173 FL int
2174 c_quit(void *vp){
2175 char const **argv;
2176 NYD_ENTER;
2178 if(*(argv = vp) != NULL && (n_idec_si32_cp(&n_exit_status, *argv, 0, NULL) &
2179 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2180 ) != n_IDEC_STATE_CONSUMED)
2181 n_exit_status |= n_EXIT_ERR;
2183 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2184 fflush(NULL);
2185 _exit(n_exit_status);
2186 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2187 n_err(_("`exit' delayed until compose mode is left\n")); /* XXX ..log? */
2188 n_psonce |= n_PSO_QUIT;
2189 NYD_LEAVE;
2190 return 0;
2193 FL int
2194 c_readctl(void *vp){
2195 /* TODO We would need OnForkEvent and then simply remove some internal
2196 * TODO management; we don't have this, therefore we need global
2197 * TODO n_readctl_overlay to be accessible via =NULL, and to make that
2198 * TODO work in turn we need an instance for default STDIN! Sigh. */
2199 static union{
2200 ui64_t alignme;
2201 ui8_t buf[n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name)+1 +1];
2202 } a;
2203 static struct a_go_readctl_ctx *a_stdin;
2205 struct a_go_readctl_ctx *grcp;
2206 char const *emsg;
2207 enum{
2208 a_NONE = 0,
2209 a_ERR = 1u<<0,
2210 a_SET = 1u<<1,
2211 a_CREATE = 1u<<2,
2212 a_REMOVE = 1u<<3
2213 } f;
2214 struct n_cmd_arg *cap;
2215 struct n_cmd_arg_ctx *cacp;
2216 NYD_ENTER;
2218 if(a_stdin == NULL){
2219 a_stdin = (struct a_go_readctl_ctx*)(void*)a.buf;
2220 a_stdin->grc_name[0] = '-';
2221 n_readctl_overlay = a_stdin;
2224 n_pstate_err_no = n_ERR_NONE;
2225 cacp = vp;
2226 cap = cacp->cac_arg;
2228 if(cacp->cac_no == 0 || is_asccaseprefix(cap->ca_arg.ca_str.s, "show"))
2229 goto jshow;
2230 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "set"))
2231 f = a_SET;
2232 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "create"))
2233 f = a_CREATE;
2234 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "remove"))
2235 f = a_REMOVE;
2236 else{
2237 emsg = N_("`readctl': invalid subcommand: %s\n");
2238 goto jeinval_quote;
2241 if(cacp->cac_no == 1){ /* TODO better option parser <> subcommand */
2242 n_err(_("`readctl': %s: requires argument\n"), cap->ca_arg.ca_str.s);
2243 goto jeinval;
2245 cap = cap->ca_next;
2247 /* - is special TODO unfortunately also regarding storage */
2248 if(cap->ca_arg.ca_str.l == 1 && *cap->ca_arg.ca_str.s == '-'){
2249 if(f & (a_CREATE | a_REMOVE)){
2250 n_err(_("`readctl': cannot create nor remove -\n"));
2251 goto jeinval;
2253 n_readctl_overlay = a_stdin;
2254 goto jleave;
2257 /* Try to find a yet existing instance */
2258 if((grcp = n_readctl_overlay) != NULL){
2259 for(; grcp != NULL; grcp = grcp->grc_next)
2260 if(!strcmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2261 goto jfound;
2262 for(grcp = n_readctl_overlay; (grcp = grcp->grc_last) != NULL;)
2263 if(!strcmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2264 goto jfound;
2267 if(f & (a_SET | a_REMOVE)){
2268 emsg = N_("`readctl': no such channel: %s\n");
2269 goto jeinval_quote;
2272 jfound:
2273 if(f & a_SET)
2274 n_readctl_overlay = grcp;
2275 else if(f & a_REMOVE){
2276 if(n_readctl_overlay == grcp)
2277 n_readctl_overlay = a_stdin;
2279 if(grcp->grc_last != NULL)
2280 grcp->grc_last->grc_next = grcp->grc_next;
2281 if(grcp->grc_next != NULL)
2282 grcp->grc_next->grc_last = grcp->grc_last;
2283 fclose(grcp->grc_fp);
2284 n_free(grcp);
2285 }else{
2286 FILE *fp;
2287 size_t elen;
2288 si32_t fd;
2290 if(grcp != NULL){
2291 n_err(_("`readctl': channel already exists: %s\n"), /* TODO reopen */
2292 n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2293 n_pstate_err_no = n_ERR_EXIST;
2294 f = a_ERR;
2295 goto jleave;
2298 if((n_idec_si32_cp(&fd, cap->ca_arg.ca_str.s, 0, NULL
2299 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2300 ) != n_IDEC_STATE_CONSUMED){
2301 if((emsg = fexpand(cap->ca_arg.ca_str.s, FEXP_LOCAL | FEXP_NVAR)
2302 ) == NULL){
2303 emsg = N_("`readctl': cannot expand filename %s\n");
2304 goto jeinval_quote;
2306 fd = -1;
2307 elen = strlen(emsg);
2308 fp = safe_fopen(emsg, "r", NULL);
2309 }else if(fd == STDIN_FILENO || fd == STDOUT_FILENO ||
2310 fd == STDERR_FILENO){
2311 n_err(_("`readctl': create: standard descriptors are not allowed\n"));
2312 goto jeinval;
2313 }else{
2314 /* xxx Avoid */
2315 _CLOEXEC_SET(fd);
2316 emsg = NULL;
2317 elen = 0;
2318 fp = fdopen(fd, "r");
2321 if(fp != NULL){
2322 size_t i;
2324 if((i = UIZ_MAX - elen) <= cap->ca_arg.ca_str.l ||
2325 (i -= cap->ca_arg.ca_str.l) <=
2326 n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +2){
2327 n_err(_("`readctl': failed to create storage for %s\n"),
2328 cap->ca_arg.ca_str.s);
2329 n_pstate_err_no = n_ERR_OVERFLOW;
2330 f = a_ERR;
2331 goto jleave;
2334 grcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +
2335 cap->ca_arg.ca_str.l +1 + elen +1);
2336 grcp->grc_last = NULL;
2337 if((grcp->grc_next = n_readctl_overlay) != NULL)
2338 grcp->grc_next->grc_last = grcp;
2339 n_readctl_overlay = grcp;
2340 grcp->grc_fp = fp;
2341 grcp->grc_fd = fd;
2342 memcpy(grcp->grc_name, cap->ca_arg.ca_str.s, cap->ca_arg.ca_str.l +1);
2343 if(elen == 0)
2344 grcp->grc_expand = NULL;
2345 else{
2346 char *cp;
2348 grcp->grc_expand = cp = &grcp->grc_name[cap->ca_arg.ca_str.l +1];
2349 memcpy(cp, emsg, ++elen);
2351 }else{
2352 emsg = N_("`readctl': failed to create file for %s\n");
2353 goto jeinval_quote;
2357 jleave:
2358 NYD_LEAVE;
2359 return (f & a_ERR) ? 1 : 0;
2360 jeinval_quote:
2361 n_err(V_(emsg), n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2362 jeinval:
2363 n_pstate_err_no = n_ERR_INVAL;
2364 f = a_ERR;
2365 goto jleave;
2367 jshow:
2368 if((grcp = n_readctl_overlay) == NULL)
2369 fprintf(n_stdout, _("`readctl': no channels registered\n"));
2370 else{
2371 while(grcp->grc_last != NULL)
2372 grcp = grcp->grc_last;
2374 fprintf(n_stdout, _("`readctl': registered channels:\n"));
2375 for(; grcp != NULL; grcp = grcp->grc_next)
2376 fprintf(n_stdout, _("%c%s %s%s%s%s\n"),
2377 (grcp == n_readctl_overlay ? '*' : ' '),
2378 (grcp->grc_fd != -1 ? _("descriptor") : _("name")),
2379 n_shexp_quote_cp(grcp->grc_name, FAL0),
2380 (grcp->grc_expand != NULL ? " (" : n_empty),
2381 (grcp->grc_expand != NULL ? grcp->grc_expand : n_empty),
2382 (grcp->grc_expand != NULL ? ")" : n_empty));
2384 f = a_NONE;
2385 goto jleave;
2388 /* s-it-mode */