smime_decrypt_assemble(): someone defined "lines" to something, sic!
[s-mailx.git] / go.c
blob7e240a403767bac045ad0969a4308cd9bd152c73
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.
4 *@ TODO - go_input(): with IO::Device we could have CStringListDevice, for
5 *@ TODO example to handle injections, and also `readctl' channels!
6 *@ TODO (Including sh(1)ell HERE strings and such.)
8 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
9 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
10 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
13 * Copyright (c) 1980, 1993
14 * The Regents of the University of California. All rights reserved.
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
18 * are met:
19 * 1. Redistributions of source code must retain the above copyright
20 * notice, this list of conditions and the following disclaimer.
21 * 2. Redistributions in binary form must reproduce the above copyright
22 * notice, this list of conditions and the following disclaimer in the
23 * documentation and/or other materials provided with the distribution.
24 * 3. Neither the name of the University nor the names of its contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
40 #undef n_FILE
41 #define n_FILE go
43 #ifndef HAVE_AMALGAMATION
44 # include "nail.h"
45 #endif
47 enum a_go_flags{
48 a_GO_NONE,
49 a_GO_FREE = 1u<<0, /* Structure was allocated, n_free() it */
50 a_GO_PIPE = 1u<<1, /* Open on a pipe */
51 a_GO_FILE = 1u<<2, /* Loading or sourcing a file */
52 a_GO_MACRO = 1u<<3, /* Running a macro */
53 a_GO_MACRO_FREE_DATA = 1u<<4, /* Lines are allocated, n_free() once done */
54 /* TODO For simplicity this is yet _MACRO plus specialization overlay
55 * TODO (_X_OPTION, _CMD) -- these should be types on their own! */
56 a_GO_MACRO_X_OPTION = 1u<<5, /* Macro indeed command line -X option */
57 a_GO_MACRO_CMD = 1u<<6, /* Macro indeed single-line: ~:COMMAND */
58 /* TODO a_GO_SPLICE: the right way to support *on-compose-splice(-shell)?*
59 * TODO would be a command_loop object that emits an on_read_line event, and
60 * TODO have a special handler for the compose mode; with that, then,
61 * TODO _event_loop() would not call _evaluate() but CTX->on_read_line,
62 * TODO and _evaluate() would be the standard impl.,
63 * TODO whereas the COMMAND ESCAPE switch in collect.c would be another one.
64 * TODO With this generic accmacvar.c:temporary_compose_mode_hook_call()
65 * TODO could be dropped, and n_go_macro() could become extended,
66 * TODO and/or we would add a n_go_anything(), which would allow special
67 * TODO input handlers, special I/O input and output, special `localopts'
68 * TODO etc., to be glued to the new execution context. And all I/O all
69 * TODO over this software should not use stdin/stdout, but CTX->in/out.
70 * TODO The pstate must be a property of the current execution context, too.
71 * TODO This not today. :( For now we invent a special SPLICE execution
72 * TODO context overlay that at least allows to temporarily modify the
73 * TODO global pstate, and the global stdin and stdout pointers. HACK!
74 * TODO This splice thing is very special and has to go again. HACK!!
75 * TODO a_go_input() will drop it once it sees EOF (HACK!), but care for
76 * TODO jumps must be taken by splice creators. HACK!!! But works. ;} */
77 a_GO_SPLICE = 1u<<7,
78 /* If it is none of those, it must be the outermost, the global one */
79 a_GO_TYPE_MASK = a_GO_PIPE | a_GO_FILE | a_GO_MACRO |
80 /* a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD | */ a_GO_SPLICE,
82 a_GO_FORCE_EOF = 1u<<8, /* go_input() shall return EOF next */
83 a_GO_IS_EOF = 1u<<9,
85 a_GO_SUPER_MACRO = 1u<<16, /* *Not* inheriting n_PS_SOURCING state */
86 /* This context has inherited the memory pool from its parent.
87 * In practice only used for resource file loading and -X args, which enter
88 * a top level n_go_main_loop() and should (re)use the in practice already
89 * allocated memory pool of the global context */
90 a_GO_MEMPOOL_INHERITED = 1u<<17,
92 /* This context has inherited the entire data context from its parent */
93 a_GO_DATACTX_INHERITED = 1u<<18,
95 a_GO_XCALL_IS_CALL = 1u<<24, /* n_GO_INPUT_NO_XCALL */
96 /* `xcall' optimization barrier: n_go_macro() has been finished with
97 * a `xcall' request, and `xcall' set this in the parent a_go_input of the
98 * said n_go_macro() to indicate a barrier: we teardown the a_go_input of
99 * the n_go_macro() away after leaving its _event_loop(), but then,
100 * back in n_go_macro(), that enters a for(;;) loop that directly calls
101 * c_call() -- our `xcall' stack avoidance optimization --, yet this call
102 * will itself end up in a new n_go_macro(), and if that again ends up with
103 * `xcall' this should teardown and leave its own n_go_macro(), unrolling the
104 * stack "up to the barrier level", but which effectively still is the
105 * n_go_macro() that lost its a_go_input and is looping the `xcall'
106 * optimization loop. If no `xcall' is desired that loop is simply left and
107 * the _event_loop() of the outer a_go_ctx will perform a loop tick and
108 * clear this bit again OR become teardown itself */
109 a_GO_XCALL_LOOP = 1u<<25, /* `xcall' optimization barrier level */
110 a_GO_XCALL_LOOP_ERROR = 1u<<26, /* .. state machine error transporter */
111 a_GO_XCALL_LOOP_MASK = a_GO_XCALL_LOOP | a_GO_XCALL_LOOP_ERROR
114 enum a_go_cleanup_mode{
115 a_GO_CLEANUP_UNWIND = 1u<<0, /* Teardown all contexts except outermost */
116 a_GO_CLEANUP_TEARDOWN = 1u<<1, /* Teardown current context */
117 a_GO_CLEANUP_LOOPTICK = 1u<<2, /* Normal looptick cleanup */
118 a_GO_CLEANUP_MODE_MASK = n_BITENUM_MASK(0, 2),
120 a_GO_CLEANUP_ERROR = 1u<<8, /* Error occurred on level */
121 a_GO_CLEANUP_SIGINT = 1u<<9, /* Interrupt signal received */
122 a_GO_CLEANUP_HOLDALLSIGS = 1u<<10 /* hold_all_sigs() active TODO */
125 enum a_go_hist_flags{
126 a_GO_HIST_NONE = 0,
127 a_GO_HIST_ADD = 1u<<0,
128 a_GO_HIST_GABBY = 1u<<1,
129 a_GO_HIST_INIT = 1u<<2
132 struct a_go_eval_ctx{
133 struct str gec_line; /* The terminated data to _evaluate() */
134 ui32_t gec_line_size; /* May be used to store line memory size */
135 bool_t gec_ever_seen; /* Has ever been used (main_loop() only) */
136 ui8_t gec__dummy[2];
137 ui8_t gec_hist_flags; /* enum a_go_hist_flags */
138 char const *gec_hist_cmd; /* If a_GO_HIST_ADD only, cmd and args */
139 char const *gec_hist_args;
142 struct a_go_input_inject{
143 struct a_go_input_inject *gii_next;
144 size_t gii_len;
145 bool_t gii_commit;
146 bool_t gii_no_history;
147 char gii_dat[n_VFIELD_SIZE(6)];
150 struct a_go_ctx{
151 struct a_go_ctx *gc_outer;
152 sigset_t gc_osigmask;
153 ui32_t gc_flags; /* enum a_go_flags */
154 ui32_t gc_loff; /* Pseudo (macro): index in .gc_lines */
155 char **gc_lines; /* Pseudo content, lines unfolded */
156 FILE *gc_file; /* File we were in, if applicable */
157 struct a_go_input_inject *gc_inject; /* To be consumed first */
158 void (*gc_on_finalize)(void *);
159 void *gc_finalize_arg;
160 sigjmp_buf gc_eloop_jmp; /* TODO one day... for _event_loop() */
161 /* SPLICE hacks: saved stdin/stdout, saved pstate */
162 FILE *gc_splice_stdin;
163 FILE *gc_splice_stdout;
164 ui32_t gc_splice_psonce;
165 ui8_t gc_splice__dummy[4];
166 struct n_go_data_ctx gc_data;
167 char gc_name[n_VFIELD_SIZE(0)]; /* Name of file or macro */
170 struct a_go_readctl_ctx{ /* TODO localize readctl_read_overlay: OnForkEvent! */
171 struct a_go_readctl_ctx *grc_last;
172 struct a_go_readctl_ctx *grc_next;
173 char const *grc_expand; /* If filename based, expanded string */
174 FILE *grc_fp;
175 si32_t grc_fd; /* Based upon file-descriptor */
176 char grc_name[n_VFIELD_SIZE(4)]; /* User input for identification purposes */
179 static sighandler_type a_go_oldpipe;
180 /* a_go_cmd_tab[] after fun protos */
182 /* Our current execution context, and the buffer backing the outermost level */
183 static struct a_go_ctx *a_go_ctx;
185 #define a_GO_MAINCTX_NAME "Main event loop"
186 static union{
187 ui64_t align;
188 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
189 sizeof(a_GO_MAINCTX_NAME)];
190 } a_go__mainctx_b;
192 /* `xcall' stack-avoidance bypass optimization. This actually is
193 * a n_cmd_arg_save_to_heap() buffer with n_cmd_arg_ctx.cac_indat misused to
194 * point to the a_go_ctx to unroll up to */
195 static void *a_go_xcall;
197 static sigjmp_buf a_go_srbuf; /* TODO GET RID */
199 /* n_PS_STATE_PENDMASK requires some actions */
200 static void a_go_update_pstate(void);
202 /* Evaluate a single command */
203 static bool_t a_go_evaluate(struct a_go_eval_ctx *gecp);
205 /* Branch here on hangup signal and simulate "exit" */
206 static void a_go_hangup(int s);
208 /* The following gets called on receipt of an interrupt */
209 static void a_go_onintr(int s);
211 /* Cleanup current execution context, update the program state.
212 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
213 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we hold_all_sigs() */
214 static void a_go_cleanup(enum a_go_cleanup_mode gcm);
216 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
217 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
218 * upon evaluation error, and TRU1 on success */
219 static bool_t a_go_file(char const *file, bool_t silent_open_error);
221 /* System resource file load()ing or -X command line option array traversal */
222 static bool_t a_go_load(struct a_go_ctx *gcp);
224 /* A simplified command loop for recursed state machines */
225 static bool_t a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif);
227 static void
228 a_go_update_pstate(void){
229 bool_t act;
230 NYD_ENTER;
232 act = ((n_pstate & n_PS_SIGWINCH_PEND) != 0);
233 n_pstate &= ~n_PS_PSTATE_PENDMASK;
235 if(act){
236 char buf[32];
238 snprintf(buf, sizeof buf, "%d", n_scrnwidth);
239 ok_vset(COLUMNS, buf);
240 snprintf(buf, sizeof buf, "%d", n_scrnheight);
241 ok_vset(LINES, buf);
243 NYD_LEAVE;
246 static bool_t
247 a_go_evaluate(struct a_go_eval_ctx *gecp){
248 /* xxx old style(9), but also old code */
249 /* TODO a_go_evaluate() should be splitted in multiple subfunctions,
250 * TODO `eval' should be a prefix, etc., a Ctx should be passed along */
251 struct str line;
252 struct n_string s, *sp;
253 struct str const *alias_exp;
254 char _wordbuf[2], *argv_stack[3], **argv_base, **argvp, *vput, *cp, *word;
255 char const *alias_name;
256 struct n_cmd_desc const *cdp;
257 si32_t nerrn, nexn; /* TODO n_pstate_ex_no -> si64_t! */
258 int rv, c;
259 enum{
260 a_NONE = 0,
261 a_ALIAS_MASK = n_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
262 a_NOPREFIX = 1u<<4, /* Modifier prefix not allowed right now */
263 a_NOALIAS = 1u<<5, /* "No alias!" expansion modifier */
264 a_IGNERR = 1u<<6, /* ignerr modifier prefix */
265 a_LOCAL = 1u<<7, /* local modifier prefix */
266 a_SCOPE = 1u<<8, /* TODO scope modifier prefix */
267 a_U = 1u<<9, /* TODO UTF-8 modifier prefix */
268 a_VPUT = 1u<<10, /* vput modifier prefix */
269 a_WYSH = 1u<<11, /* XXX v15+ drop wysh modifier prefix */
270 a_MODE_MASK = n_BITENUM_MASK(5, 11),
271 a_NO_ERRNO = 1u<<16 /* Don't set n_pstate_err_no */
272 } flags;
273 NYD_ENTER;
275 if(!(n_pstate & n_PS_ERR_EXIT_MASK))
276 n_exit_status = n_EXIT_OK;
278 flags = a_NONE;
279 rv = 1;
280 nerrn = n_ERR_NONE;
281 nexn = n_EXIT_OK;
282 cdp = NULL;
283 vput = NULL;
284 alias_name = NULL;
285 n_UNINIT(alias_exp, NULL);
286 line = gecp->gec_line; /* TODO const-ify original (buffer)! */
287 assert(line.s[line.l] == '\0');
289 if(line.l > 0 && spacechar(line.s[0]))
290 gecp->gec_hist_flags = a_GO_HIST_NONE;
291 else if(gecp->gec_hist_flags & a_GO_HIST_ADD)
292 gecp->gec_hist_cmd = gecp->gec_hist_args = NULL;
293 sp = NULL;
295 /* Aliases that refer to shell commands or macro expansion restart */
296 jrestart:
297 if(n_str_trim_ifs(&line, TRU1)->l == 0){
298 line.s[0] = '\0';
299 gecp->gec_hist_flags = a_GO_HIST_NONE;
300 goto jempty;
302 (cp = line.s)[line.l] = '\0';
304 /* No-expansion modifier? */
305 if(!(flags & a_NOPREFIX) && *cp == '\\'){
306 line.s = ++cp;
307 --line.l;
308 flags |= a_NOALIAS;
311 /* Note: adding more special treatments must be reflected in the `help' etc.
312 * output in cmd-tab.c! */
314 /* Ignore null commands (comments) */
315 if(*cp == '#'){
316 gecp->gec_hist_flags = a_GO_HIST_NONE;
317 goto jret0;
320 /* Handle ! differently to get the correct lexical conventions */
321 if(*cp == '!')
322 ++cp;
323 /* Isolate the actual command; since it may not necessarily be
324 * separated from the arguments (as in `p1') we need to duplicate it to
325 * be able to create a NUL terminated version.
326 * We must be aware of several special one letter commands here */
327 else if((cp = n_UNCONST(n_cmd_isolate(cp))) == line.s &&
328 (*cp == '|' || *cp == '?'))
329 ++cp;
330 c = (int)PTR2SIZE(cp - line.s);
331 word = UICMP(z, c, <, sizeof _wordbuf) ? _wordbuf : n_autorec_alloc(c +1);
332 memcpy(word, line.s, c);
333 word[c] = '\0';
334 line.l -= c;
335 line.s = cp;
337 /* It may be a modifier.
338 * NOTE: changing modifiers must be reflected in `commandalias' handling
339 * code as well as in the manual (of course)! */
340 switch(c){
341 default:
342 break;
343 case sizeof("ignerr") -1:
344 if(!asccasecmp(word, "ignerr")){
345 flags |= a_NOPREFIX | a_IGNERR;
346 goto jrestart;
348 break;
349 /*case sizeof("scope") -1:*/
350 case sizeof("local") -1:
351 if(!asccasecmp(word, "local")){
352 flags |= a_NOPREFIX | a_LOCAL;
353 goto jrestart;
354 }else if(!asccasecmp(word, "scope")){
355 /* This will be an extended per-command `localopts' */
356 n_err(_("Ignoring yet unused `scope' command modifier!"));
357 flags |= a_NOPREFIX | a_SCOPE;
358 goto jrestart;
360 break;
361 case sizeof("u") -1:
362 if(!asccasecmp(word, "u")){
363 n_err(_("Ignoring yet unused `u' command modifier!"));
364 flags |= a_NOPREFIX | a_U;
365 goto jrestart;
367 break;
368 /*case sizeof("vput") -1:*/
369 case sizeof("wysh") -1:
370 if(!asccasecmp(word, "wysh")){
371 flags |= a_NOPREFIX | a_WYSH;
372 goto jrestart;
373 }else if(!asccasecmp(word, "vput")){
374 flags |= a_NOPREFIX | a_VPUT;
375 goto jrestart;
377 break;
380 /* We need to trim for a possible history entry, but do it anyway and insert
381 * a space for argument separation in case of alias expansion. Also, do
382 * terminate again because nothing prevents aliases from introducing WS */
383 n_str_trim_ifs(&line, TRU1);
384 line.s[line.l] = '\0';
386 /* Lengthy history entry setup, possibly even redundant. But having
387 * normalized history entries is a good thing, and this is maybe still
388 * cheaper than parsing a StrList of words per se */
389 if((gecp->gec_hist_flags & (a_GO_HIST_ADD | a_GO_HIST_INIT)
390 ) == a_GO_HIST_ADD){
391 if(line.l > 0){
392 sp = n_string_creat_auto(&s);
393 sp = n_string_assign_buf(sp, line.s, line.l);
394 gecp->gec_hist_args = n_string_cp(sp);
397 sp = n_string_creat_auto(&s);
398 sp = n_string_reserve(sp, 32);
400 if(flags & a_NOALIAS)
401 sp = n_string_push_c(sp, '\\');
402 if(flags & a_IGNERR)
403 sp = n_string_push_buf(sp, "ignerr ", sizeof("ignerr ") -1);
404 if(flags & a_WYSH)
405 sp = n_string_push_buf(sp, "wysh ", sizeof("wysh ") -1);
406 if(flags & a_VPUT)
407 sp = n_string_push_buf(sp, "vput ", sizeof("vput ") -1);
408 gecp->gec_hist_flags = a_GO_HIST_ADD | a_GO_HIST_INIT;
411 /* Look up the command; if not found, bitch.
412 * Normally, a blank command would map to the first command in the
413 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
414 * confusion; act just the same for aliases */
415 if(*word == '\0'){
416 jempty:
417 if((n_pstate & n_PS_ROBOT) || !(n_psonce & n_PSO_INTERACTIVE) ||
418 alias_name != NULL){
419 gecp->gec_hist_flags = a_GO_HIST_NONE;
420 goto jret0;
422 cdp = n_cmd_default();
423 goto jexec;
426 if(!(flags & a_NOALIAS) && (flags & a_ALIAS_MASK) != a_ALIAS_MASK){
427 ui8_t expcnt;
429 expcnt = (flags & a_ALIAS_MASK);
430 ++expcnt;
431 flags = (flags & ~(a_ALIAS_MASK | a_NOPREFIX)) | expcnt;
433 /* Avoid self-recursion; since a commandalias can shadow a command of
434 * equal name allow one level of expansion to return an equal result:
435 * "commandalias q q;commandalias x q;x" should be "x->q->q->quit".
436 * P.S.: should also work for "help x" ... */
437 if(alias_name != NULL && !strcmp(word, alias_name))
438 flags |= a_NOALIAS;
440 if((alias_name = n_commandalias_exists(word, &alias_exp)) != NULL){
441 size_t i;
443 if(sp != NULL){
444 sp = n_string_push_cp(sp, word);
445 gecp->gec_hist_cmd = n_string_cp(sp);
446 sp = NULL;
449 /* And join arguments onto alias expansion */
450 alias_name = word;
451 i = alias_exp->l;
452 cp = line.s;
453 line.s = n_autorec_alloc(i + 1 + line.l +1);
454 memcpy(line.s, alias_exp->s, i);
455 if(line.l > 0){
456 line.s[i++] = ' ';
457 memcpy(&line.s[i], cp, line.l);
459 line.s[i += line.l] = '\0';
460 line.l = i;
461 goto jrestart;
465 if((cdp = n_cmd_firstfit(word)) == NULL){
466 bool_t doskip;
468 if(!(doskip = n_cnd_if_isskip()) || (n_poption & n_PO_D_V))
469 n_err(_("Unknown command%s: `%s'\n"),
470 (doskip ? _(" (ignored due to `if' condition)") : n_empty),
471 prstr(word));
472 gecp->gec_hist_flags = a_GO_HIST_NONE;
473 if(doskip)
474 goto jret0;
475 nerrn = n_ERR_NOSYS;
476 goto jleave;
479 /* See if we should execute the command -- if a conditional we always
480 * execute it, otherwise, check the state of cond */
481 jexec:
482 if(!(cdp->cd_caflags & n_CMD_ARG_F) && n_cnd_if_isskip()){
483 gecp->gec_hist_flags = a_GO_HIST_NONE;
484 goto jret0;
487 if(sp != NULL){
488 sp = n_string_push_cp(sp, cdp->cd_name);
489 gecp->gec_hist_cmd = n_string_cp(sp);
490 sp = NULL;
493 nerrn = n_ERR_INVAL;
495 /* Process the arguments to the command, depending on the type it expects */
496 if((cdp->cd_caflags & n_CMD_ARG_I) && !(n_psonce & n_PSO_INTERACTIVE) &&
497 !(n_poption & n_PO_BATCH_FLAG)){
498 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
499 cdp->cd_name);
500 goto jleave;
502 if(!(cdp->cd_caflags & n_CMD_ARG_M) && (n_psonce & n_PSO_SENDMODE)){
503 n_err(_("May not execute `%s' while sending\n"), cdp->cd_name);
504 goto jleave;
506 if(cdp->cd_caflags & n_CMD_ARG_R){
507 if(n_pstate & n_PS_COMPOSE_MODE){
508 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
509 n_err(_("Cannot invoke `%s' when in compose mode\n"), cdp->cd_name);
510 goto jleave;
512 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
513 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
514 if(n_pstate & (n_PS_ROBOT | n_PS_SOURCING) && !n_go_may_yield_control()){
515 n_err(_("Cannot invoke `%s' in this program state\n"),
516 cdp->cd_name);
517 goto jleave;
520 if((cdp->cd_caflags & n_CMD_ARG_S) && !(n_psonce & n_PSO_STARTED_CONFIG)){
521 n_err(_("May not execute `%s' during startup\n"), cdp->cd_name);
522 goto jleave;
524 if(!(cdp->cd_caflags & n_CMD_ARG_X) && (n_pstate & n_PS_COMPOSE_FORKHOOK)){
525 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
526 cdp->cd_name);
527 goto jleave;
530 if((cdp->cd_caflags & n_CMD_ARG_A) && mb.mb_type == MB_VOID){
531 n_err(_("Cannot execute `%s' without active mailbox\n"), cdp->cd_name);
532 goto jleave;
534 if((cdp->cd_caflags & n_CMD_ARG_W) && !(mb.mb_perm & MB_DELE)){
535 n_err(_("May not execute `%s' -- message file is read only\n"),
536 cdp->cd_name);
537 goto jleave;
540 if(cdp->cd_caflags & n_CMD_ARG_O)
541 n_OBSOLETE2(_("this command will be removed"), cdp->cd_name);
543 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
544 * TODO doesn't use any of those list commands which strip this mask,
545 * TODO and for now we misuse bits for checking relation to history;
546 * TODO argument state should be property of a per-command carrier instead */
547 n_pstate &= ~n_PS_ARGLIST_MASK;
549 if((flags & a_WYSH) &&
550 (cdp->cd_caflags & n_CMD_ARG_TYPE_MASK) != n_CMD_ARG_TYPE_WYRA){
551 n_err(_("`wysh' command modifier does not affect `%s'\n"), cdp->cd_name);
552 goto jleave;
555 if(flags & a_LOCAL){
556 if(!(cdp->cd_caflags & n_CMD_ARG_L)){
557 n_err(_("`local' command modifier does not affect `%s'\n"),
558 cdp->cd_name);
559 goto jleave;
561 flags |= a_WYSH;
562 n_pstate |= n_PS_ARGMOD_LOCAL; /* TODO YET useless since stripped later
563 * TODO on in getrawlist() etc., i.e., the argument vector producers,
564 * TODO therefore yet needs to be set again based on flags&a_LOCAL! */
567 if(flags & a_VPUT){
568 if(cdp->cd_caflags & n_CMD_ARG_V){
569 char const *emsg;
571 emsg = line.s; /* xxx Cannot pass &char* as char const**, so no cp */
572 vput = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIM_SPACE |
573 n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_LOG |
574 n_SHEXP_PARSE_META_SEMICOLON | n_SHEXP_PARSE_META_KEEP), &emsg);
575 line.l -= PTR2SIZE(emsg - line.s);
576 line.s = cp = n_UNCONST(emsg);
577 if(cp == NULL)
578 emsg = N_("could not parse input token");
579 else if(!n_shexp_is_valid_varname(vput))
580 emsg = N_("not a valid variable name");
581 else if(!n_var_is_user_writable(vput))
582 emsg = N_("either not a user writable, or a boolean variable");
583 else
584 emsg = NULL;
585 if(emsg != NULL){
586 n_err("`%s': vput: %s: %s\n",
587 cdp->cd_name, V_(emsg), n_shexp_quote_cp(vput, FAL0));
588 nerrn = n_ERR_NOTSUP;
589 rv = -1;
590 goto jleave;
592 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO YET useless since stripped later
593 * TODO on in getrawlist() etc., i.e., the argument vector producers,
594 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
595 }else{
596 n_err(_("`vput' prefix does not affect `%s'\n"), cdp->cd_name);
597 flags &= ~a_VPUT;
601 switch(cdp->cd_caflags & n_CMD_ARG_TYPE_MASK){
602 case n_CMD_ARG_TYPE_MSGLIST:
603 /* Message list defaulting to nearest forward legal message */
604 if(n_msgvec == NULL)
605 goto jmsglist_err;
606 if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_msgflag, NULL)) < 0){
607 nerrn = n_ERR_NOMSG;
608 flags |= a_NO_ERRNO;
609 break;
611 if(c == 0){
612 if((n_msgvec[0] = first(cdp->cd_msgflag, cdp->cd_msgmask)) != 0)
613 c = 1;
614 else{
615 jmsglist_err:
616 if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
617 (n_poption & n_PO_D_V))
618 fprintf(n_stdout, _("No applicable messages\n"));
619 nerrn = n_ERR_NOMSG;
620 flags |= a_NO_ERRNO;
621 break;
624 jmsglist_go:
625 /* C99 */{
626 int *mvp;
628 mvp = n_autorec_calloc(c +1, sizeof *mvp);
629 while(c-- > 0)
630 mvp[c] = n_msgvec[c];
631 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /*XXX*/
632 n_err_no = 0;
633 rv = (*cdp->cd_func)(mvp);
635 break;
637 case n_CMD_ARG_TYPE_NDMLIST:
638 /* Message list with no defaults, but no error if none exist */
639 if(n_msgvec == NULL)
640 goto jmsglist_err;
641 if((c = n_getmsglist(line.s, n_msgvec, cdp->cd_msgflag, NULL)) < 0){
642 nerrn = n_ERR_NOMSG;
643 flags |= a_NO_ERRNO;
644 break;
646 goto jmsglist_go;
648 case n_CMD_ARG_TYPE_STRING:
649 /* Just the straight string, old style, with leading blanks removed */
650 for(cp = line.s; spacechar(*cp);)
651 ++cp;
652 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
653 n_err_no = 0;
654 rv = (*cdp->cd_func)(cp);
655 break;
657 case n_CMD_ARG_TYPE_RAWDAT:
658 /* Just the straight string, placed in argv[] */
659 argvp = argv_stack;
660 if(flags & a_VPUT)
661 *argvp++ = vput;
662 *argvp++ = line.s;
663 *argvp = NULL;
664 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
665 n_err_no = 0;
666 rv = (*cdp->cd_func)(argv_stack);
667 break;
669 case n_CMD_ARG_TYPE_WYSH:
670 c = 1;
671 if(0){
672 /* FALLTHRU */
673 case n_CMD_ARG_TYPE_WYRA:
674 c = (flags & a_WYSH) ? 1 : 0;
675 if(0){
676 case n_CMD_ARG_TYPE_RAWLIST:
677 c = 0;
680 argvp = argv_base = n_autorec_alloc(sizeof(*argv_base) * n_MAXARGC);
681 if(flags & a_VPUT)
682 *argvp++ = vput;
683 if((c = getrawlist((c != 0), argvp,
684 (n_MAXARGC - ((flags & a_VPUT) != 0)), line.s, line.l)) < 0){
685 n_err(_("Invalid argument list\n"));
686 flags |= a_NO_ERRNO;
687 break;
690 if(UICMP(32, c, <, cdp->cd_minargs)){
691 n_err(_("`%s' requires at least %u arg(s)\n"),
692 cdp->cd_name, (ui32_t)cdp->cd_minargs);
693 flags |= a_NO_ERRNO;
694 break;
696 #undef cd_minargs
697 if(UICMP(32, c, >, cdp->cd_maxargs)){
698 n_err(_("`%s' takes no more than %u arg(s)\n"),
699 cdp->cd_name, (ui32_t)cdp->cd_maxargs);
700 flags |= a_NO_ERRNO;
701 break;
703 #undef cd_maxargs
705 if(flags & a_LOCAL)
706 n_pstate |= n_PS_ARGMOD_LOCAL;
707 if(flags & a_VPUT)
708 n_pstate |= n_PS_ARGMOD_VPUT; /* TODO due to getrawlist(), as above */
710 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
711 n_err_no = 0;
712 rv = (*cdp->cd_func)(argv_base);
713 if(a_go_xcall != NULL)
714 goto jret0;
715 break;
717 case n_CMD_ARG_TYPE_ARG:{
718 /* TODO The _ARG_TYPE_ARG is preliminary, in the end we should have a
719 * TODO per command-ctx carrier that also has slots for it arguments,
720 * TODO and that should be passed along all the way. No more arglists
721 * TODO here, etc. */
722 struct n_cmd_arg_ctx cac;
724 cac.cac_desc = cdp->cd_cadp;
725 cac.cac_indat = line.s;
726 cac.cac_inlen = line.l;
727 cac.cac_msgflag = cdp->cd_msgflag;
728 cac.cac_msgmask = cdp->cd_msgmask;
729 if(!n_cmd_arg_parse(&cac)){
730 flags |= a_NO_ERRNO;
731 break;
734 if(flags & a_VPUT){
735 cac.cac_vput = vput;
736 /* Global "hack" not used: n_pstate |= n_PS_ARGMOD_VPUT; */
737 }else
738 cac.cac_vput = NULL;
740 if(!(flags & a_NO_ERRNO) && !(cdp->cd_caflags & n_CMD_ARG_EM)) /* XXX */
741 n_err_no = 0;
742 rv = (*cdp->cd_func)(&cac);
743 if(a_go_xcall != NULL)
744 goto jret0;
745 }break;
747 default:
748 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
749 cdp->cd_caflags & n_CMD_ARG_TYPE_MASK); )
750 nerrn = n_ERR_NOTOBACCO;
751 nexn = 1;
752 goto jret0;
755 if(gecp->gec_hist_flags & a_GO_HIST_ADD){
756 if(cdp->cd_caflags & n_CMD_ARG_H)
757 gecp->gec_hist_flags = a_GO_HIST_NONE;
758 else if((cdp->cd_caflags & n_CMD_ARG_G) ||
759 (n_pstate & n_PS_MSGLIST_GABBY))
760 gecp->gec_hist_flags |= a_GO_HIST_GABBY;
763 if(rv != 0){
764 if(!(flags & a_NO_ERRNO)){
765 if(cdp->cd_caflags & n_CMD_ARG_EM)
766 flags |= a_NO_ERRNO;
767 else if((nerrn = n_err_no) == 0)
768 nerrn = n_ERR_INVAL;
769 }/*else
770 flags ^= a_NO_ERRNO;*/
771 }else if(cdp->cd_caflags & n_CMD_ARG_EM)
772 flags |= a_NO_ERRNO;
773 else
774 nerrn = n_ERR_NONE;
775 jleave:
776 nexn = rv;
778 if(flags & a_IGNERR){
779 n_pstate &= ~n_PS_ERR_EXIT_MASK;
780 if(!(n_pstate & n_PS_ERR_EXIT_MASK))
781 n_exit_status = n_EXIT_OK;
782 }else if(rv != 0){
783 bool_t bo;
785 if((bo = ok_blook(batch_exit_on_error))){
786 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
787 if(!(n_poption & n_PO_BATCH_FLAG))
788 bo = FAL0;
790 if(ok_blook(errexit) || bo) /* TODO v15: drop bo */
791 n_pstate |= n_PS_ERR_QUIT;
792 else if(ok_blook(posix)){
793 if(n_psonce & n_PSO_STARTED)
794 rv = 0;
795 else if(!(n_psonce & n_PSO_INTERACTIVE))
796 n_pstate |= n_PS_ERR_XIT;
797 }else
798 rv = 0;
800 if(rv != 0){
801 if(n_exit_status == n_EXIT_OK)
802 n_exit_status = n_EXIT_ERR;
803 if((n_poption & n_PO_D_V) &&
804 !(n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)))
805 n_alert(_("Non-interactive, bailing out due to errors "
806 "in startup load phase"));
807 goto jret;
811 if(cdp == NULL)
812 goto jret0;
813 if((cdp->cd_caflags & n_CMD_ARG_P) && ok_blook(autoprint))
814 if(visible(dot))
815 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, "\\type",
816 sizeof("\\type") -1);
818 if(!(n_pstate & (n_PS_SOURCING | n_PS_HOOK_MASK)) &&
819 !(cdp->cd_caflags & n_CMD_ARG_T))
820 n_pstate |= n_PS_SAW_COMMAND;
821 jret0:
822 rv = 0;
823 jret:
824 if(!(flags & a_NO_ERRNO))
825 n_pstate_err_no = nerrn;
826 n_pstate_ex_no = nexn;
827 NYD_LEAVE;
828 return (rv == 0);
831 static void
832 a_go_hangup(int s){
833 NYD_X; /* Signal handler */
834 n_UNUSED(s);
835 /* nothing to do? */
836 exit(n_EXIT_ERR);
839 #ifdef HAVE_IMAP
840 FL void n_go_onintr_for_imap(void){a_go_onintr(0);}
841 #endif
842 static void
843 a_go_onintr(int s){ /* TODO block signals while acting */
844 NYD_X; /* Signal handler */
845 n_UNUSED(s);
847 safe_signal(SIGINT, a_go_onintr);
849 termios_state_reset();
851 a_go_cleanup(a_GO_CLEANUP_UNWIND | /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS);
853 if(interrupts != 1)
854 n_err_sighdl(_("Interrupt\n"));
855 safe_signal(SIGPIPE, a_go_oldpipe);
856 siglongjmp(a_go_srbuf, 0); /* FIXME get rid */
859 static void
860 a_go_cleanup(enum a_go_cleanup_mode gcm){
861 /* Signals blocked */
862 struct a_go_ctx *gcp;
863 NYD_ENTER;
865 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
866 hold_all_sigs();
867 jrestart:
868 gcp = a_go_ctx;
870 /* Free input injections of this level first */
871 if(!(gcm & a_GO_CLEANUP_LOOPTICK)){
872 struct a_go_input_inject **giipp, *giip;
874 for(giipp = &gcp->gc_inject; (giip = *giipp) != NULL;){
875 *giipp = giip->gii_next;
876 n_free(giip);
880 /* Cleanup non-crucial external stuff */
881 n_COLOUR(
882 if(gcp->gc_data.gdc_colour != NULL)
883 n_colour_stack_del(&gcp->gc_data);
886 /* Work the actual context (according to cleanup mode) */
887 if(gcp->gc_outer == NULL){
888 if(gcm & (a_GO_CLEANUP_UNWIND | a_GO_CLEANUP_SIGINT)){
889 if(a_go_xcall != NULL){
890 n_free(a_go_xcall);
891 a_go_xcall = NULL;
893 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
894 n_pstate &= ~n_PS_ERR_EXIT_MASK;
895 close_all_files();
896 }else{
897 if(!(n_pstate & n_PS_SOURCING))
898 close_all_files();
901 n_memory_reset();
903 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
904 assert(a_go_xcall == NULL);
905 assert(!(gcp->gc_flags & a_GO_XCALL_LOOP_MASK));
906 assert(gcp->gc_on_finalize == NULL);
907 n_COLOUR( assert(gcp->gc_data.gdc_colour == NULL); )
908 goto jxleave;
909 }else if(gcm & a_GO_CLEANUP_LOOPTICK){
910 n_memory_reset();
911 goto jxleave;
912 }else if(gcp->gc_flags & a_GO_SPLICE){ /* TODO Temporary hack */
913 n_stdin = gcp->gc_splice_stdin;
914 n_stdout = gcp->gc_splice_stdout;
915 n_psonce = gcp->gc_splice_psonce;
916 goto jstackpop;
919 /* Cleanup crucial external stuff */
920 if(gcp->gc_data.gdc_ifcond != NULL){
921 n_cnd_if_stack_del(&gcp->gc_data);
922 if(!(gcm & (a_GO_CLEANUP_ERROR | a_GO_CLEANUP_SIGINT)) &&
923 !(gcp->gc_flags & a_GO_FORCE_EOF) && a_go_xcall == NULL &&
924 !(n_psonce & n_PSO_EXIT_MASK)){
925 n_err(_("Unmatched `if' at end of %s %s\n"),
926 ((gcp->gc_flags & a_GO_MACRO
927 ? (gcp->gc_flags & a_GO_MACRO_CMD ? _("command") : _("macro"))
928 : _("`source'd file"))),
929 gcp->gc_name);
930 gcm |= a_GO_CLEANUP_ERROR;
934 /* Teardown context */
935 if(gcp->gc_flags & a_GO_MACRO){
936 if(gcp->gc_flags & a_GO_MACRO_FREE_DATA){
937 char **lp;
939 while(*(lp = &gcp->gc_lines[gcp->gc_loff]) != NULL){
940 n_free(*lp);
941 ++gcp->gc_loff;
943 /* Part of gcp's memory chunk, then */
944 if(!(gcp->gc_flags & a_GO_MACRO_CMD))
945 n_free(gcp->gc_lines);
947 }else if(gcp->gc_flags & a_GO_PIPE)
948 /* XXX command manager should -TERM then -KILL instead of hoping
949 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
950 Pclose(gcp->gc_file, TRU1);
951 else if(gcp->gc_flags & a_GO_FILE)
952 Fclose(gcp->gc_file);
954 if(!(gcp->gc_flags & a_GO_MEMPOOL_INHERITED)){
955 if(gcp->gc_data.gdc_mempool != NULL)
956 n_memory_pool_pop(NULL, TRU1);
957 }else
958 n_memory_reset();
960 jstackpop:
961 /* Update a_go_ctx and n_go_data, n_pstate ... */
962 a_go_ctx = gcp->gc_outer;
963 assert(a_go_ctx != NULL);
964 /* C99 */{
965 struct a_go_ctx *x;
967 for(x = a_go_ctx; x->gc_flags & a_GO_DATACTX_INHERITED;){
968 x = x->gc_outer;
969 assert(x != NULL);
971 n_go_data = &x->gc_data;
974 if((a_go_ctx->gc_flags & (a_GO_MACRO | a_GO_SUPER_MACRO)) ==
975 (a_GO_MACRO | a_GO_SUPER_MACRO)){
976 n_pstate &= ~n_PS_SOURCING;
977 assert(n_pstate & n_PS_ROBOT);
978 }else if(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK))
979 n_pstate &= ~(n_PS_SOURCING | n_PS_ROBOT);
980 else
981 assert(n_pstate & n_PS_ROBOT);
983 if(gcp->gc_on_finalize != NULL)
984 (*gcp->gc_on_finalize)(gcp->gc_finalize_arg);
986 if(gcm & a_GO_CLEANUP_ERROR){
987 if(a_go_ctx->gc_flags & a_GO_XCALL_LOOP)
988 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP_ERROR;
989 goto jerr;
991 jleave:
992 if(gcp->gc_flags & a_GO_FREE)
993 n_free(gcp);
995 if(n_UNLIKELY((gcm & a_GO_CLEANUP_UNWIND) && gcp != a_go_ctx))
996 goto jrestart;
998 jxleave:
999 NYD_LEAVE;
1000 if(!(gcm & a_GO_CLEANUP_HOLDALLSIGS))
1001 rele_all_sigs();
1002 return;
1004 jerr:
1005 /* With *posix* we follow what POSIX says:
1006 * Any errors in the start-up file shall either cause mailx to
1007 * terminate with a diagnostic message and a non-zero status or to
1008 * continue after writing a diagnostic message, ignoring the
1009 * remainder of the lines in the start-up file
1010 * Print the diagnostic only for the outermost resource unless the user
1011 * is debugging or in verbose mode */
1012 if((n_poption & n_PO_D_V) ||
1013 (!(n_psonce & n_PSO_STARTED) &&
1014 !(gcp->gc_flags & (a_GO_SPLICE | a_GO_MACRO)) &&
1015 !(gcp->gc_outer->gc_flags & a_GO_TYPE_MASK)))
1016 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1017 n_alert(_("Stopped %s %s due to errors%s"),
1018 (n_psonce & n_PSO_STARTED
1019 ? (gcp->gc_flags & a_GO_SPLICE ? _("spliced in program")
1020 : (gcp->gc_flags & a_GO_MACRO
1021 ? (gcp->gc_flags & a_GO_MACRO_CMD
1022 ? _("evaluating command") : _("evaluating macro"))
1023 : (gcp->gc_flags & a_GO_PIPE
1024 ? _("executing `source'd pipe")
1025 : (gcp->gc_flags & a_GO_FILE
1026 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME))))
1028 : (gcp->gc_flags & a_GO_MACRO
1029 ? (gcp->gc_flags & a_GO_MACRO_X_OPTION
1030 ? _("evaluating command line") : _("evaluating macro"))
1031 : _("loading initialization resource"))),
1032 n_shexp_quote_cp(gcp->gc_name, FAL0),
1033 (n_poption & n_PO_DEBUG ? n_empty : _(" (enable *debug* for trace)")));
1034 goto jleave;
1037 static bool_t
1038 a_go_file(char const *file, bool_t silent_open_error){
1039 struct a_go_ctx *gcp;
1040 sigset_t osigmask;
1041 size_t nlen;
1042 char *nbuf;
1043 bool_t ispipe;
1044 FILE *fip;
1045 NYD_ENTER;
1047 fip = NULL;
1049 /* Being a command argument file is space-trimmed *//* TODO v15 with
1050 * TODO WYRALIST this is no longer necessary true, and for that we
1051 * TODO don't set _PARSE_TRIM_SPACE because we cannot! -> cmd-tab.h!! */
1052 #if 0
1053 ((ispipe = (!silent_open_error && (nlen = strlen(file)) > 0 &&
1054 file[--nlen] == '|')))
1055 #else
1056 ispipe = FAL0;
1057 if(!silent_open_error){
1058 for(nlen = strlen(file); nlen > 0;){
1059 char c;
1061 c = file[--nlen];
1062 if(!spacechar(c)){
1063 if(c == '|'){
1064 nbuf = savestrbuf(file, nlen);
1065 ispipe = TRU1;
1067 break;
1071 #endif
1073 if(ispipe){
1074 if((fip = Popen(nbuf /* #if 0 above = savestrbuf(file, nlen)*/, "r",
1075 ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)) == NULL)
1076 goto jeopencheck;
1077 }else if((nbuf = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL)
1078 goto jeopencheck;
1079 else if((fip = Fopen(nbuf, "r")) == NULL){
1080 jeopencheck:
1081 if(!silent_open_error || (n_poption & n_PO_D_V))
1082 n_perr(nbuf, 0);
1083 if(silent_open_error)
1084 fip = (FILE*)-1;
1085 goto jleave;
1088 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1090 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1091 (nlen = strlen(nbuf) +1));
1092 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1094 hold_all_sigs();
1096 gcp->gc_outer = a_go_ctx;
1097 gcp->gc_osigmask = osigmask;
1098 gcp->gc_file = fip;
1099 gcp->gc_flags = (ispipe ? a_GO_FREE | a_GO_PIPE : a_GO_FREE | a_GO_FILE) |
1100 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO ? a_GO_SUPER_MACRO : 0);
1101 memcpy(gcp->gc_name, nbuf, nlen);
1103 a_go_ctx = gcp;
1104 n_go_data = &gcp->gc_data;
1105 n_pstate |= n_PS_SOURCING | n_PS_ROBOT;
1106 if(!a_go_event_loop(gcp, n_GO_INPUT_NONE | n_GO_INPUT_NL_ESC))
1107 fip = NULL;
1108 jleave:
1109 NYD_LEAVE;
1110 return (fip != NULL);
1113 static bool_t
1114 a_go_load(struct a_go_ctx *gcp){
1115 NYD2_ENTER;
1117 assert(!(n_psonce & n_PSO_STARTED));
1118 assert(!(a_go_ctx->gc_flags & a_GO_TYPE_MASK));
1120 gcp->gc_flags |= a_GO_MEMPOOL_INHERITED;
1121 gcp->gc_data.gdc_mempool = n_go_data->gdc_mempool;
1123 hold_all_sigs();
1125 /* POSIX:
1126 * Any errors in the start-up file shall either cause mailx to terminate
1127 * with a diagnostic message and a non-zero status or to continue after
1128 * writing a diagnostic message, ignoring the remainder of the lines in
1129 * the start-up file. */
1130 gcp->gc_outer = a_go_ctx;
1131 a_go_ctx = gcp;
1132 n_go_data = &gcp->gc_data;
1133 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1134 n_pstate |= n_PS_ROBOT |
1135 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1137 n_pstate |= n_PS_ROBOT | n_PS_SOURCING;
1139 rele_all_sigs();
1141 n_go_main_loop();
1142 NYD2_LEAVE;
1143 return (((n_psonce & n_PSO_EXIT_MASK) |
1144 (n_pstate & n_PS_ERR_EXIT_MASK)) == 0);
1147 static void
1148 a_go__eloopint(int sig){ /* TODO one day, we don't need it no more */
1149 NYD_X; /* Signal handler */
1150 n_UNUSED(sig);
1151 siglongjmp(a_go_ctx->gc_eloop_jmp, 1);
1154 static bool_t
1155 a_go_event_loop(struct a_go_ctx *gcp, enum n_go_input_flags gif){
1156 sighandler_type soldhdl;
1157 struct a_go_eval_ctx gec;
1158 enum {a_RETOK = TRU1, a_TICKED = 1<<1} volatile f;
1159 volatile int hadint; /* TODO get rid of shitty signal stuff (see signal.c) */
1160 sigset_t osigmask;
1161 NYD2_ENTER;
1163 memset(&gec, 0, sizeof gec);
1164 osigmask = gcp->gc_osigmask;
1165 hadint = FAL0;
1166 f = a_RETOK;
1168 if((soldhdl = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN){
1169 safe_signal(SIGINT, &a_go__eloopint);
1170 if(sigsetjmp(gcp->gc_eloop_jmp, 1)){
1171 hold_all_sigs();
1172 hadint = TRU1;
1173 f &= ~a_RETOK;
1174 gcp->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1175 goto jjump;
1179 for(;; f |= a_TICKED){
1180 int n;
1182 if(f & a_TICKED)
1183 n_memory_reset();
1185 /* Read a line of commands and handle end of file specially */
1186 gec.gec_line.l = gec.gec_line_size;
1187 rele_all_sigs();
1188 n = n_go_input(gif, NULL, &gec.gec_line.s, &gec.gec_line.l, NULL, NULL);
1189 hold_all_sigs();
1190 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1191 gec.gec_line.l = (ui32_t)n;
1193 if(n < 0)
1194 break;
1196 rele_all_sigs();
1197 assert(gec.gec_hist_flags == a_GO_HIST_NONE);
1198 if(!a_go_evaluate(&gec))
1199 f &= ~a_RETOK;
1200 hold_all_sigs();
1202 if(!(f & a_RETOK) || a_go_xcall != NULL ||
1203 (n_psonce & n_PSO_EXIT_MASK) || (n_pstate & n_PS_ERR_EXIT_MASK))
1204 break;
1207 jjump: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1208 a_go_cleanup(a_GO_CLEANUP_TEARDOWN |
1209 (f & a_RETOK ? 0 : a_GO_CLEANUP_ERROR) |
1210 (hadint ? a_GO_CLEANUP_SIGINT : 0) | a_GO_CLEANUP_HOLDALLSIGS);
1212 if(gec.gec_line.s != NULL)
1213 n_free(gec.gec_line.s);
1215 if(soldhdl != SIG_IGN)
1216 safe_signal(SIGINT, soldhdl);
1217 NYD2_LEAVE;
1218 rele_all_sigs();
1219 if(hadint){
1220 sigprocmask(SIG_SETMASK, &osigmask, NULL);
1221 n_raise(SIGINT);
1223 return (f & a_RETOK);
1226 FL void
1227 n_go_init(void){
1228 struct a_go_ctx *gcp;
1229 NYD2_ENTER;
1231 assert(n_stdin != NULL);
1233 gcp = (void*)a_go__mainctx_b.uf;
1234 DBGOR( memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name)),
1235 memset(&gcp->gc_data, 0, sizeof gcp->gc_data) );
1236 gcp->gc_file = n_stdin;
1237 memcpy(gcp->gc_name, a_GO_MAINCTX_NAME, sizeof(a_GO_MAINCTX_NAME));
1238 a_go_ctx = gcp;
1239 n_go_data = &gcp->gc_data;
1241 n_child_manager_start();
1242 NYD2_LEAVE;
1245 FL bool_t
1246 n_go_main_loop(void){ /* FIXME */
1247 struct a_go_eval_ctx gec;
1248 int n, eofcnt;
1249 bool_t volatile rv;
1250 NYD_ENTER;
1252 rv = TRU1;
1254 if (!(n_pstate & n_PS_SOURCING)) {
1255 if (safe_signal(SIGINT, SIG_IGN) != SIG_IGN)
1256 safe_signal(SIGINT, &a_go_onintr);
1257 if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN)
1258 safe_signal(SIGHUP, &a_go_hangup);
1260 a_go_oldpipe = safe_signal(SIGPIPE, SIG_IGN);
1261 safe_signal(SIGPIPE, a_go_oldpipe);
1263 memset(&gec, 0, sizeof gec);
1265 (void)sigsetjmp(a_go_srbuf, 1); /* FIXME get rid */
1266 hold_all_sigs();
1268 for (eofcnt = 0;; gec.gec_ever_seen = TRU1) {
1269 interrupts = 0;
1271 if(gec.gec_ever_seen)
1272 a_go_cleanup(a_GO_CLEANUP_LOOPTICK | a_GO_CLEANUP_HOLDALLSIGS);
1274 if (!(n_pstate & n_PS_SOURCING)) {
1275 char *cp;
1277 /* TODO Note: this buffer may contain a password. We should redefine
1278 * TODO the code flow which has to do that */
1279 if ((cp = termios_state.ts_linebuf) != NULL) {
1280 termios_state.ts_linebuf = NULL;
1281 termios_state.ts_linesize = 0;
1282 n_free(cp); /* TODO pool give-back */
1284 if (gec.gec_line.l > LINESIZE * 3) {
1285 n_free(gec.gec_line.s);
1286 gec.gec_line.s = NULL;
1287 gec.gec_line.l = gec.gec_line_size = 0;
1291 if (!(n_pstate & n_PS_SOURCING) && (n_psonce & n_PSO_INTERACTIVE)) {
1292 char *cp;
1294 if ((cp = ok_vlook(newmail)) != NULL) { /* TODO bla */
1295 struct stat st;
1297 if(mb.mb_type == MB_FILE){
1298 if(!stat(mailname, &st) && st.st_size > mailsize) Jnewmail:{
1299 ui32_t odid;
1300 size_t odot;
1302 odot = PTR2SIZE(dot - message);
1303 odid = (n_pstate & n_PS_DID_PRINT_DOT);
1305 rele_all_sigs();
1306 n = setfile(mailname,
1307 (FEDIT_NEWMAIL |
1308 ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)));
1309 hold_all_sigs();
1311 if(n < 0) {
1312 n_exit_status |= n_EXIT_ERR;
1313 rv = FAL0;
1314 break;
1316 #ifdef HAVE_IMAP
1317 if(mb.mb_type != MB_IMAP){
1318 #endif
1319 dot = &message[odot];
1320 n_pstate |= odid;
1321 #ifdef HAVE_IMAP
1323 #endif
1325 }else{
1326 #if defined HAVE_MAILDIR || defined HAVE_IMAP
1327 n = (cp != NULL && strcmp(cp, "nopoll"));
1328 #endif
1330 #ifdef HAVE_MAILDIR
1331 if(mb.mb_type == MB_MAILDIR){
1332 if(n != 0)
1333 goto Jnewmail;
1335 #endif
1336 #ifdef HAVE_IMAP
1337 if(mb.mb_type == MB_IMAP){
1338 if(!n)
1339 n = (cp != NULL && strcmp(cp, "noimap"));
1341 if(imap_newmail(n) > (cp == NULL))
1342 goto Jnewmail;
1344 #endif
1349 /* Read a line of commands and handle end of file specially */
1350 gec.gec_line.l = gec.gec_line_size;
1351 /* C99 */{
1352 bool_t histadd;
1354 histadd = (!(n_pstate & n_PS_SOURCING) &&
1355 (n_psonce & n_PSO_INTERACTIVE));
1356 rele_all_sigs();
1357 n = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, NULL,
1358 &gec.gec_line.s, &gec.gec_line.l, NULL, &histadd);
1359 hold_all_sigs();
1361 gec.gec_hist_flags = histadd ? a_GO_HIST_ADD : a_GO_HIST_NONE;
1363 gec.gec_line_size = (ui32_t)gec.gec_line.l;
1364 gec.gec_line.l = (ui32_t)n;
1366 if (n < 0) {
1367 if (!(n_pstate & n_PS_ROBOT) &&
1368 (n_psonce & n_PSO_INTERACTIVE) && ok_blook(ignoreeof) &&
1369 ++eofcnt < 4) {
1370 fprintf(n_stdout, _("*ignoreeof* set, use `quit' to quit.\n"));
1371 n_go_input_clearerr();
1372 continue;
1374 break;
1377 n_pstate &= ~n_PS_HOOK_MASK;
1378 rele_all_sigs();
1379 rv = a_go_evaluate(&gec);
1380 hold_all_sigs();
1382 if(gec.gec_hist_flags & a_GO_HIST_ADD){
1383 char const *cc, *ca;
1385 cc = gec.gec_hist_cmd;
1386 ca = gec.gec_hist_args;
1387 if(cc != NULL && ca != NULL)
1388 cc = savecatsep(cc, ' ', ca);
1389 else if(ca != NULL)
1390 cc = ca;
1391 assert(cc != NULL);
1392 n_tty_addhist(cc, (n_GO_INPUT_CTX_DEFAULT |
1393 (gec.gec_hist_flags & a_GO_HIST_GABBY ? n_GO_INPUT_HIST_GABBY
1394 : n_GO_INPUT_NONE)));
1397 switch(n_pstate & n_PS_ERR_EXIT_MASK){
1398 case n_PS_ERR_XIT: n_psonce |= n_PSO_XIT; break;
1399 case n_PS_ERR_QUIT: n_psonce |= n_PSO_QUIT; break;
1400 default: break;
1402 if(n_psonce & n_PSO_EXIT_MASK)
1403 break;
1405 if(!rv)
1406 break;
1409 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS |
1410 (rv ? 0 : a_GO_CLEANUP_ERROR));
1412 if (gec.gec_line.s != NULL)
1413 n_free(gec.gec_line.s);
1415 rele_all_sigs();
1416 NYD_LEAVE;
1417 return rv;
1420 FL void
1421 n_go_input_clearerr(void){
1422 FILE *fp;
1423 NYD2_ENTER;
1425 fp = NULL;
1427 if(!(a_go_ctx->gc_flags & (a_GO_FORCE_EOF |
1428 a_GO_PIPE | a_GO_MACRO | a_GO_SPLICE)))
1429 fp = a_go_ctx->gc_file;
1431 if(fp != NULL){
1432 a_go_ctx->gc_flags &= ~a_GO_IS_EOF;
1433 clearerr(fp);
1435 NYD2_LEAVE;
1438 FL void
1439 n_go_input_force_eof(void){
1440 NYD2_ENTER;
1441 a_go_ctx->gc_flags |= a_GO_FORCE_EOF;
1442 NYD2_LEAVE;
1445 FL bool_t
1446 n_go_input_is_eof(void){
1447 bool_t rv;
1448 NYD2_ENTER;
1450 rv = ((a_go_ctx->gc_flags & a_GO_IS_EOF) != 0);
1451 NYD2_LEAVE;
1452 return rv;
1455 FL void
1456 n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf,
1457 size_t len){
1458 NYD_ENTER;
1459 if(len == UIZ_MAX)
1460 len = strlen(buf);
1462 if(UIZ_MAX - n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat) -1 > len &&
1463 len > 0){
1464 struct a_go_input_inject *giip, **giipp;
1466 hold_all_sigs();
1468 giip = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject, gii_dat
1469 ) + 1 + len +1);
1470 giipp = &a_go_ctx->gc_inject;
1471 giip->gii_next = *giipp;
1472 giip->gii_commit = ((giif & n_GO_INPUT_INJECT_COMMIT) != 0);
1473 giip->gii_no_history = ((giif & n_GO_INPUT_INJECT_HISTORY) == 0);
1474 memcpy(&giip->gii_dat[0], buf, len);
1475 giip->gii_dat[giip->gii_len = len] = '\0';
1476 *giipp = giip;
1478 rele_all_sigs();
1480 NYD_LEAVE;
1483 FL int
1484 (n_go_input)(enum n_go_input_flags gif, char const *prompt, char **linebuf,
1485 size_t *linesize, char const *string, bool_t *histok_or_null
1486 n_MEMORY_DEBUG_ARGS){
1487 /* TODO readline: linebuf pool!; n_go_input should return si64_t.
1488 * TODO This thing should be replaced by a(n) (stack of) event generator(s)
1489 * TODO and consumed by OnLineCompletedEvent listeners */
1490 struct n_string xprompt;
1491 FILE *ifile;
1492 char const *iftype;
1493 struct a_go_input_inject *giip;
1494 int nold, n;
1495 enum{
1496 a_NONE,
1497 a_HISTOK = 1u<<0,
1498 a_USE_PROMPT = 1u<<1,
1499 a_USE_MLE = 1u<<2,
1500 a_DIGMSG_OVERLAY = 1u<<16
1501 } f;
1502 NYD2_ENTER;
1504 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1505 hold_all_sigs();
1507 f = a_NONE;
1509 if(a_go_ctx->gc_flags & a_GO_FORCE_EOF){
1510 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1511 n = -1;
1512 goto jleave;
1515 if(gif & n_GO_INPUT_FORCE_STDIN)
1516 goto jforce_stdin;
1518 /* Special case macro mode: never need to prompt, lines have always been
1519 * unfolded already */
1520 if(a_go_ctx->gc_flags & a_GO_MACRO){
1521 if(*linebuf != NULL)
1522 n_free(*linebuf);
1524 /* Injection in progress? Don't care about the autocommit state here */
1525 if((giip = a_go_ctx->gc_inject) != NULL){
1526 a_go_ctx->gc_inject = giip->gii_next;
1528 /* Simply "reuse" allocation, copy string to front of it */
1529 jinject:
1530 *linesize = giip->gii_len;
1531 *linebuf = (char*)giip;
1532 memmove(*linebuf, giip->gii_dat, giip->gii_len +1);
1533 iftype = "INJECTION";
1534 }else{
1535 if((*linebuf = a_go_ctx->gc_lines[a_go_ctx->gc_loff]) == NULL){
1536 *linesize = 0;
1537 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1538 n = -1;
1539 goto jleave;
1542 ++a_go_ctx->gc_loff;
1543 *linesize = strlen(*linebuf);
1544 if(!(a_go_ctx->gc_flags & a_GO_MACRO_FREE_DATA))
1545 *linebuf = sbufdup(*linebuf, *linesize);
1547 iftype = (a_go_ctx->gc_flags & a_GO_MACRO_X_OPTION)
1548 ? "-X OPTION"
1549 : (a_go_ctx->gc_flags & a_GO_MACRO_CMD) ? "CMD" : "MACRO";
1551 n = (int)*linesize;
1552 n_pstate |= n_PS_READLINE_NL;
1553 goto jhave_dat;
1554 }else{
1555 /* Injection in progress? */
1556 struct a_go_input_inject **giipp;
1558 giipp = &a_go_ctx->gc_inject;
1560 if((giip = *giipp) != NULL){
1561 *giipp = giip->gii_next;
1563 if(giip->gii_commit){
1564 if(*linebuf != NULL)
1565 n_free(*linebuf);
1566 if(!giip->gii_no_history)
1567 f |= a_HISTOK;
1568 goto jinject; /* (above) */
1569 }else{
1570 string = savestrbuf(giip->gii_dat, giip->gii_len);
1571 n_free(giip);
1576 jforce_stdin:
1577 n_pstate &= ~n_PS_READLINE_NL;
1578 iftype = (!(n_psonce & n_PSO_STARTED) ? "LOAD"
1579 : (n_pstate & n_PS_SOURCING) ? "SOURCE" : "READ");
1580 if(!(n_pstate & n_PS_ROBOT) &&
1581 (n_psonce & (n_PSO_INTERACTIVE | n_PSO_STARTED)) ==
1582 (n_PSO_INTERACTIVE | n_PSO_STARTED))
1583 f |= a_HISTOK;
1584 if(!(f & a_HISTOK) || (gif & n_GO_INPUT_FORCE_STDIN))
1585 gif |= n_GO_INPUT_PROMPT_NONE;
1586 else{
1587 f |= a_USE_PROMPT;
1588 if(!ok_blook(line_editor_disable))
1589 f |= a_USE_MLE;
1590 else
1591 (void)n_string_creat_auto(&xprompt);
1592 if(prompt == NULL)
1593 gif |= n_GO_INPUT_PROMPT_EVAL;
1596 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
1597 if((gif & n_GO_INPUT_PROMPT_NONE) && !(f & a_USE_MLE))
1598 fflush(n_stdout);
1600 if(gif & n_GO_INPUT_FORCE_STDIN){
1601 struct a_go_readctl_ctx *grcp;
1602 struct n_dig_msg_ctx *dmcp;
1604 if((dmcp = n_digmsg_read_overlay) != NULL){
1605 ifile = dmcp->dmc_fp;
1606 f |= a_DIGMSG_OVERLAY;
1607 }else if((grcp = n_readctl_read_overlay) == NULL ||
1608 (ifile = grcp->grc_fp) == NULL)
1609 ifile = n_stdin;
1610 }else
1611 ifile = a_go_ctx->gc_file;
1612 if(ifile == NULL){
1613 assert((n_pstate & n_PS_COMPOSE_FORKHOOK) &&
1614 (a_go_ctx->gc_flags & a_GO_MACRO));
1615 ifile = n_stdin;
1618 for(nold = n = 0;;){
1619 if(f & a_USE_MLE){
1620 assert(ifile == n_stdin);
1621 if(string != NULL && (n = (int)strlen(string)) > 0){
1622 if(*linesize > 0)
1623 *linesize += n +1;
1624 else
1625 *linesize = (size_t)n + LINESIZE +1;
1626 *linebuf = (n_realloc)(*linebuf, *linesize n_MEMORY_DEBUG_ARGSCALL);
1627 memcpy(*linebuf, string, (size_t)n +1);
1629 string = NULL;
1631 rele_all_sigs();
1633 n = (n_tty_readline)(gif, prompt, linebuf, linesize, n, histok_or_null
1634 n_MEMORY_DEBUG_ARGSCALL);
1636 hold_all_sigs();
1638 if(n < 0 && !ferror(ifile)) /* EOF never i guess */
1639 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1640 }else{
1641 if(!(gif & n_GO_INPUT_PROMPT_NONE))
1642 n_tty_create_prompt(&xprompt, prompt, gif);
1644 rele_all_sigs();
1646 if(!(gif & n_GO_INPUT_PROMPT_NONE) && xprompt.s_len > 0){
1647 fwrite(xprompt.s_dat, 1, xprompt.s_len, n_stdout);
1648 fflush(n_stdout);
1651 n = (readline_restart)(ifile, linebuf, linesize, n
1652 n_MEMORY_DEBUG_ARGSCALL);
1654 hold_all_sigs();
1656 if(n < 0 && !ferror(ifile))
1657 a_go_ctx->gc_flags |= a_GO_IS_EOF;
1659 if(n > 0 && nold > 0){
1660 char const *cp;
1661 int i;
1663 i = 0;
1664 cp = &(*linebuf)[nold];
1665 while(spacechar(*cp) && n - i >= nold)
1666 ++cp, ++i;
1667 if(i > 0){
1668 memmove(&(*linebuf)[nold], cp, n - nold - i);
1669 n -= i;
1670 (*linebuf)[n] = '\0';
1675 if(n <= 0)
1676 break;
1678 /* POSIX says:
1679 * TODO This does not take care for current shell quote mode!
1680 * TODO Thus "echo '\<NEWLINE HERE> bla' will never work
1681 * An unquoted <backslash> at the end of a command line shall
1682 * be discarded and the next line shall continue the command */
1683 if(!(gif & n_GO_INPUT_NL_ESC) || (*linebuf)[n - 1] != '\\')
1684 break;
1686 /* Definitely outside of quotes, thus the quoting rules are so that an
1687 * uneven number of successive reverse solidus at EOL is a continuation */
1688 if(n > 1){
1689 size_t i, j;
1691 for(j = 1, i = (size_t)n - 1; i-- > 0; ++j)
1692 if((*linebuf)[i] != '\\')
1693 break;
1694 if(!(j & 1))
1695 break;
1697 (*linebuf)[nold = --n] = '\0';
1698 gif |= n_GO_INPUT_NL_FOLLOW;
1701 if(n < 0)
1702 goto jleave;
1703 if(f & a_USE_MLE)
1704 n_pstate |= n_PS_READLINE_NL;
1705 (*linebuf)[*linesize = n] = '\0';
1707 jhave_dat:
1708 if(n_poption & n_PO_D_VV)
1709 n_err(_("%s %d bytes <%s>\n"), iftype, n, *linebuf);
1710 jleave:
1711 if (n_pstate & n_PS_PSTATE_PENDMASK)
1712 a_go_update_pstate();
1714 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
1715 * TODO but only established from the outside and we need to drop this
1716 * TODO overlay context somehow; ditto DIGMSG_OVERLAY */
1717 if(n < 0){
1718 if(f & a_DIGMSG_OVERLAY)
1719 n_digmsg_read_overlay = NULL;
1720 if(a_go_ctx->gc_flags & a_GO_SPLICE)
1721 a_go_cleanup(a_GO_CLEANUP_TEARDOWN | a_GO_CLEANUP_HOLDALLSIGS);
1724 if(histok_or_null != NULL && !(f & a_HISTOK))
1725 *histok_or_null = FAL0;
1727 if(!(gif & n_GO_INPUT_HOLDALLSIGS))
1728 rele_all_sigs();
1729 NYD2_LEAVE;
1730 return n;
1733 FL char *
1734 n_go_input_cp(enum n_go_input_flags gif, char const *prompt,
1735 char const *string){
1736 struct n_sigman sm;
1737 bool_t histadd;
1738 size_t linesize;
1739 char *linebuf, * volatile rv;
1740 int n;
1741 NYD2_ENTER;
1743 linesize = 0;
1744 linebuf = NULL;
1745 rv = NULL;
1747 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
1748 case 0:
1749 break;
1750 default:
1751 goto jleave;
1754 histadd = TRU1;
1755 n = n_go_input(gif, prompt, &linebuf, &linesize, string, &histadd);
1756 if(n > 0 && *(rv = savestrbuf(linebuf, (size_t)n)) != '\0' &&
1757 (gif & n_GO_INPUT_HIST_ADD) && (n_psonce & n_PSO_INTERACTIVE) &&
1758 histadd)
1759 n_tty_addhist(rv, gif);
1761 n_sigman_cleanup_ping(&sm);
1762 jleave:
1763 if(linebuf != NULL)
1764 n_free(linebuf);
1765 NYD2_LEAVE;
1766 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
1767 return rv;
1770 FL bool_t
1771 n_go_load(char const *name){
1772 struct a_go_ctx *gcp;
1773 size_t i;
1774 FILE *fip;
1775 bool_t rv;
1776 NYD_ENTER;
1778 rv = TRU1;
1780 if(name == NULL || *name == '\0')
1781 goto jleave;
1782 else if((fip = Fopen(name, "r")) == NULL){
1783 if(n_poption & n_PO_D_V)
1784 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name, FAL0));
1785 goto jleave;
1788 i = strlen(name) +1;
1789 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + i);
1790 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1792 gcp->gc_file = fip;
1793 gcp->gc_flags = a_GO_FREE | a_GO_FILE;
1794 memcpy(gcp->gc_name, name, i);
1796 if(n_poption & n_PO_D_V)
1797 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp->gc_name, FAL0));
1798 rv = a_go_load(gcp);
1799 jleave:
1800 NYD_LEAVE;
1801 return rv;
1804 FL bool_t
1805 n_go_Xargs(char const **lines, size_t cnt){
1806 static char const name[] = "-X";
1808 union{
1809 bool_t rv;
1810 ui64_t align;
1811 char uf[n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) + sizeof(name)];
1812 } b;
1813 char const *srcp, *xsrcp;
1814 char *cp;
1815 size_t imax, i, len;
1816 struct a_go_ctx *gcp;
1817 NYD_ENTER;
1819 gcp = (void*)b.uf;
1820 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1822 gcp->gc_flags = a_GO_MACRO | a_GO_MACRO_X_OPTION |
1823 a_GO_SUPER_MACRO | a_GO_MACRO_FREE_DATA;
1824 memcpy(gcp->gc_name, name, sizeof name);
1826 /* The problem being that we want to support reverse solidus newline
1827 * escaping also within multiline -X, i.e., POSIX says:
1828 * An unquoted <backslash> at the end of a command line shall
1829 * be discarded and the next line shall continue the command
1830 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
1831 * entire lines array and set _MACRO_FREE_DATA */
1832 imax = cnt + 1;
1833 gcp->gc_lines = n_alloc(sizeof(*gcp->gc_lines) * imax);
1835 /* For each of the input lines.. */
1836 for(i = len = 0, cp = NULL; cnt > 0;){
1837 bool_t keep;
1838 size_t j;
1840 if((j = strlen(srcp = *lines)) == 0){
1841 ++lines, --cnt;
1842 continue;
1845 /* Separate one line from a possible multiline input string */
1846 if((xsrcp = memchr(srcp, '\n', j)) != NULL){
1847 *lines = &xsrcp[1];
1848 j = PTR2SIZE(xsrcp - srcp);
1849 }else
1850 ++lines, --cnt;
1852 /* The (separated) string may itself indicate soft newline escaping */
1853 if((keep = (srcp[j - 1] == '\\'))){
1854 size_t xj, xk;
1856 /* Need an uneven number of reverse solidus */
1857 for(xk = 1, xj = j - 1; xj-- > 0; ++xk)
1858 if(srcp[xj] != '\\')
1859 break;
1860 if(xk & 1)
1861 --j;
1862 else
1863 keep = FAL0;
1866 /* Strip any leading WS from follow lines, then */
1867 if(cp != NULL)
1868 while(j > 0 && spacechar(*srcp))
1869 ++srcp, --j;
1871 if(j > 0){
1872 if(i + 2 >= imax){ /* TODO need a vector (main.c, here, ++) */
1873 imax += 4;
1874 gcp->gc_lines = n_realloc(gcp->gc_lines, sizeof(*gcp->gc_lines) *
1875 imax);
1877 gcp->gc_lines[i] = cp = n_realloc(cp, len + j +1);
1878 memcpy(&cp[len], srcp, j);
1879 cp[len += j] = '\0';
1881 if(!keep)
1882 ++i;
1884 if(!keep)
1885 cp = NULL, len = 0;
1887 if(cp != NULL){
1888 assert(i + 1 < imax);
1889 gcp->gc_lines[i++] = cp;
1891 gcp->gc_lines[i] = NULL;
1893 b.rv = a_go_load(gcp);
1894 NYD_LEAVE;
1895 return b.rv;
1898 FL int
1899 c_source(void *v){
1900 int rv;
1901 NYD_ENTER;
1903 rv = (a_go_file(*(char**)v, FAL0) == TRU1) ? 0 : 1;
1904 NYD_LEAVE;
1905 return rv;
1908 FL int
1909 c_source_if(void *v){ /* XXX obsolete?, support file tests in `if' etc.! */
1910 int rv;
1911 NYD_ENTER;
1913 rv = (a_go_file(*(char**)v, TRU1) == TRU1) ? 0 : 1;
1914 NYD_LEAVE;
1915 return rv;
1918 FL bool_t
1919 n_go_macro(enum n_go_input_flags gif, char const *name, char **lines,
1920 void (*on_finalize)(void*), void *finalize_arg){
1921 struct a_go_ctx *gcp;
1922 size_t i;
1923 int rv;
1924 sigset_t osigmask;
1925 NYD_ENTER;
1927 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1929 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1930 (i = strlen(name) +1));
1931 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1933 hold_all_sigs();
1935 gcp->gc_outer = a_go_ctx;
1936 gcp->gc_osigmask = osigmask;
1937 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_FREE_DATA |
1938 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
1939 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0) |
1940 ((gif & n_GO_INPUT_NO_XCALL) ? a_GO_XCALL_IS_CALL : 0);
1941 gcp->gc_lines = lines;
1942 gcp->gc_on_finalize = on_finalize;
1943 gcp->gc_finalize_arg = finalize_arg;
1944 memcpy(gcp->gc_name, name, i);
1946 a_go_ctx = gcp;
1947 n_go_data = &gcp->gc_data;
1948 n_pstate |= n_PS_ROBOT;
1949 rv = a_go_event_loop(gcp, gif);
1951 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
1952 if(a_go_xcall != NULL){
1953 void *vp;
1954 struct n_cmd_arg_ctx *cacp;
1956 if(a_go_xcall == (void*)-1)
1957 a_go_xcall = NULL;
1958 else if(((void const*)(cacp = a_go_xcall)->cac_indat) == gcp){
1959 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
1960 a_go_ctx->gc_flags |= a_GO_XCALL_LOOP;
1961 while(a_go_xcall != NULL){
1962 hold_all_sigs();
1964 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_ERROR;
1966 vp = a_go_xcall;
1967 a_go_xcall = NULL;
1968 cacp = n_cmd_arg_restore_from_heap(vp);
1969 n_free(vp);
1971 rele_all_sigs();
1973 (void)c_call(cacp);
1975 rv = ((a_go_ctx->gc_flags & a_GO_XCALL_LOOP_ERROR) == 0);
1976 a_go_ctx->gc_flags &= ~a_GO_XCALL_LOOP_MASK;
1979 NYD_LEAVE;
1980 return rv;
1983 FL bool_t
1984 n_go_command(enum n_go_input_flags gif, char const *cmd){
1985 struct a_go_ctx *gcp;
1986 bool_t rv;
1987 size_t i, ial;
1988 sigset_t osigmask;
1989 NYD_ENTER;
1991 sigprocmask(SIG_BLOCK, NULL, &osigmask);
1993 i = strlen(cmd) +1;
1994 ial = n_ALIGN(i);
1995 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
1996 ial + 2*sizeof(char*));
1997 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
1999 hold_all_sigs();
2001 gcp->gc_outer = a_go_ctx;
2002 gcp->gc_osigmask = osigmask;
2003 gcp->gc_flags = a_GO_FREE | a_GO_MACRO | a_GO_MACRO_CMD |
2004 ((!(a_go_ctx->gc_flags & a_GO_TYPE_MASK) ||
2005 (a_go_ctx->gc_flags & a_GO_SUPER_MACRO)) ? a_GO_SUPER_MACRO : 0);
2006 gcp->gc_lines = (void*)&gcp->gc_name[ial];
2007 memcpy(gcp->gc_lines[0] = &gcp->gc_name[0], cmd, i);
2008 gcp->gc_lines[1] = NULL;
2010 a_go_ctx = gcp;
2011 n_go_data = &gcp->gc_data;
2012 n_pstate |= n_PS_ROBOT;
2013 rv = a_go_event_loop(gcp, gif);
2014 NYD_LEAVE;
2015 return rv;
2018 FL void
2019 n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout,
2020 ui32_t new_psonce, void (*on_finalize)(void*), void *finalize_arg){
2021 struct a_go_ctx *gcp;
2022 size_t i;
2023 sigset_t osigmask;
2024 NYD_ENTER;
2026 sigprocmask(SIG_BLOCK, NULL, &osigmask);
2028 gcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name) +
2029 (i = strlen(cmd) +1));
2030 memset(gcp, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx, gc_name));
2032 hold_all_sigs();
2034 gcp->gc_outer = a_go_ctx;
2035 gcp->gc_osigmask = osigmask;
2036 gcp->gc_file = new_stdin;
2037 gcp->gc_flags = a_GO_FREE | a_GO_SPLICE | a_GO_DATACTX_INHERITED;
2038 gcp->gc_on_finalize = on_finalize;
2039 gcp->gc_finalize_arg = finalize_arg;
2040 gcp->gc_splice_stdin = n_stdin;
2041 gcp->gc_splice_stdout = n_stdout;
2042 gcp->gc_splice_psonce = n_psonce;
2043 memcpy(gcp->gc_name, cmd, i);
2045 n_stdin = new_stdin;
2046 n_stdout = new_stdout;
2047 n_psonce = new_psonce;
2048 a_go_ctx = gcp;
2049 /* Do NOT touch n_go_data! */
2050 n_pstate |= n_PS_ROBOT;
2052 rele_all_sigs();
2053 NYD_LEAVE;
2056 FL void
2057 n_go_splice_hack_remove_after_jump(void){
2058 a_go_cleanup(a_GO_CLEANUP_TEARDOWN);
2061 FL bool_t
2062 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2063 struct a_go_ctx *gcp;
2064 bool_t rv;
2065 NYD2_ENTER;
2067 rv = FAL0;
2069 /* Only when startup completed */
2070 if(!(n_psonce & n_PSO_STARTED))
2071 goto jleave;
2072 /* Only interactive or batch mode (assuming that is ok) */
2073 if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG))
2074 goto jleave;
2076 /* Not when running any hook */
2077 if(n_pstate & n_PS_HOOK_MASK)
2078 goto jleave;
2080 /* Traverse up the stack:
2081 * . not when controlled by a child process
2082 * TODO . not when there are pipes involved, we neither handle job control,
2083 * TODO nor process groups, that is, controlling terminal acceptably
2084 * . not when sourcing a file */
2085 for(gcp = a_go_ctx; gcp != NULL; gcp = gcp->gc_outer){
2086 if(gcp->gc_flags & (a_GO_PIPE | a_GO_FILE | a_GO_SPLICE))
2087 goto jleave;
2090 rv = TRU1;
2091 jleave:
2092 NYD2_LEAVE;
2093 return rv;
2096 FL int
2097 c_eval(void *vp){
2098 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
2099 * TODO ARGV with shell rules, but if that is not possible then simply
2100 * TODO adjust argv/argc of "the CmdCtx" that we will have "exec" real cmd */
2101 struct a_go_eval_ctx gec;
2102 struct n_string s_b, *sp;
2103 size_t i, j;
2104 char const **argv, *cp;
2105 NYD_ENTER;
2107 argv = vp;
2109 for(j = i = 0; (cp = argv[i]) != NULL; ++i)
2110 j += strlen(cp);
2112 sp = n_string_creat_auto(&s_b);
2113 sp = n_string_reserve(sp, j);
2115 for(i = 0; (cp = argv[i]) != NULL; ++i){
2116 if(i > 0)
2117 sp = n_string_push_c(sp, ' ');
2118 sp = n_string_push_cp(sp, cp);
2121 memset(&gec, 0, sizeof gec);
2122 gec.gec_line.s = n_string_cp(sp);
2123 gec.gec_line.l = sp->s_len;
2124 if(n_poption & n_PO_D_VV)
2125 n_err(_("EVAL %" PRIuZ " bytes <%s>\n"), gec.gec_line.l, gec.gec_line.s);
2126 (void)/* XXX */a_go_evaluate(&gec);
2127 NYD_LEAVE;
2128 return (a_go_xcall != NULL ? 0 : n_pstate_ex_no);
2131 FL int
2132 c_xcall(void *vp){
2133 int rv;
2134 struct a_go_ctx *gcp;
2135 NYD2_ENTER;
2137 /* The context can only be a macro context, except that possibly a single
2138 * level of `eval' (TODO: yet) was used to double-expand our arguments */
2139 if((gcp = a_go_ctx)->gc_flags & a_GO_MACRO_CMD)
2140 gcp = gcp->gc_outer;
2141 if((gcp->gc_flags & (a_GO_MACRO | a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD)
2142 ) != a_GO_MACRO){
2143 if(n_poption & n_PO_D_V)
2144 n_err(_("`xcall': can only be used inside a macro, using `call'\n"));
2145 rv = c_call(vp);
2146 goto jleave;
2149 /* Try to roll up the stack as much as possible.
2150 * See a_GO_XCALL_LOOP flag description for more */
2151 if(!(gcp->gc_flags & a_GO_XCALL_IS_CALL) && gcp->gc_outer != NULL){
2152 if(gcp->gc_outer->gc_flags & a_GO_XCALL_LOOP)
2153 gcp = gcp->gc_outer;
2154 }else{
2155 /* Otherwise this macro is "invoked from the top level", in which case we
2156 * silently act as if we were `call'... */
2157 rv = c_call(vp);
2158 /* ...which means we must ensure the rest of the macro that was us
2159 * doesn't become evaluated! */
2160 a_go_xcall = (void*)-1;
2161 goto jleave;
2164 /* C99 */{
2165 struct n_cmd_arg_ctx *cacp;
2167 cacp = n_cmd_arg_save_to_heap(vp);
2168 cacp->cac_indat = (char*)gcp;
2169 a_go_xcall = cacp;
2171 rv = 0;
2172 jleave:
2173 NYD2_LEAVE;
2174 return rv;
2177 FL int
2178 c_exit(void *vp){
2179 char const **argv;
2180 NYD_ENTER;
2182 if(*(argv = vp) != NULL && (n_idec_si32_cp(&n_exit_status, *argv, 0, NULL) &
2183 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2184 ) != n_IDEC_STATE_CONSUMED)
2185 n_exit_status |= n_EXIT_ERR;
2187 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2188 fflush(NULL);
2189 _exit(n_exit_status);
2190 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2191 n_err(_("`exit' delayed until compose mode is left\n")); /* XXX ..log? */
2192 n_psonce |= n_PSO_XIT;
2193 NYD_LEAVE;
2194 return 0;
2197 FL int
2198 c_quit(void *vp){
2199 char const **argv;
2200 NYD_ENTER;
2202 if(*(argv = vp) != NULL && (n_idec_si32_cp(&n_exit_status, *argv, 0, NULL) &
2203 (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2204 ) != n_IDEC_STATE_CONSUMED)
2205 n_exit_status |= n_EXIT_ERR;
2207 if(n_pstate & n_PS_COMPOSE_FORKHOOK){ /* TODO sic */
2208 fflush(NULL);
2209 _exit(n_exit_status);
2210 }else if(n_pstate & n_PS_COMPOSE_MODE) /* XXX really.. */
2211 n_err(_("`exit' delayed until compose mode is left\n")); /* XXX ..log? */
2212 n_psonce |= n_PSO_QUIT;
2213 NYD_LEAVE;
2214 return 0;
2217 FL int
2218 c_readctl(void *vp){
2219 /* TODO We would need OnForkEvent and then simply remove some internal
2220 * TODO management; we don't have this, therefore we need global
2221 * TODO n_readctl_read_overlay to be accessible via =NULL, and to make that
2222 * TODO work in turn we need an instance for default STDIN! Sigh. */
2223 static union{
2224 ui64_t alignme;
2225 ui8_t buf[n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name)+1 +1];
2226 } a;
2227 static struct a_go_readctl_ctx *a_stdin;
2229 struct a_go_readctl_ctx *grcp;
2230 char const *emsg;
2231 enum{
2232 a_NONE = 0,
2233 a_ERR = 1u<<0,
2234 a_SET = 1u<<1,
2235 a_CREATE = 1u<<2,
2236 a_REMOVE = 1u<<3
2237 } f;
2238 struct n_cmd_arg *cap;
2239 struct n_cmd_arg_ctx *cacp;
2240 NYD_ENTER;
2242 if(a_stdin == NULL){
2243 a_stdin = (struct a_go_readctl_ctx*)(void*)a.buf;
2244 a_stdin->grc_name[0] = '-';
2245 n_readctl_read_overlay = a_stdin;
2248 n_pstate_err_no = n_ERR_NONE;
2249 cacp = vp;
2250 cap = cacp->cac_arg;
2252 if(cacp->cac_no == 0 || is_asccaseprefix(cap->ca_arg.ca_str.s, "show"))
2253 goto jshow;
2254 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "set"))
2255 f = a_SET;
2256 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "create"))
2257 f = a_CREATE;
2258 else if(is_asccaseprefix(cap->ca_arg.ca_str.s, "remove"))
2259 f = a_REMOVE;
2260 else{
2261 emsg = N_("`readctl': invalid subcommand: %s\n");
2262 goto jeinval_quote;
2265 if(cacp->cac_no == 1){ /* TODO better option parser <> subcommand */
2266 n_err(_("`readctl': %s: requires argument\n"), cap->ca_arg.ca_str.s);
2267 goto jeinval;
2269 cap = cap->ca_next;
2271 /* - is special TODO unfortunately also regarding storage */
2272 if(cap->ca_arg.ca_str.l == 1 && *cap->ca_arg.ca_str.s == '-'){
2273 if(f & (a_CREATE | a_REMOVE)){
2274 n_err(_("`readctl': cannot create nor remove -\n"));
2275 goto jeinval;
2277 n_readctl_read_overlay = a_stdin;
2278 goto jleave;
2281 /* Try to find a yet existing instance */
2282 if((grcp = n_readctl_read_overlay) != NULL){
2283 for(; grcp != NULL; grcp = grcp->grc_next)
2284 if(!strcmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2285 goto jfound;
2286 for(grcp = n_readctl_read_overlay; (grcp = grcp->grc_last) != NULL;)
2287 if(!strcmp(grcp->grc_name, cap->ca_arg.ca_str.s))
2288 goto jfound;
2291 if(f & (a_SET | a_REMOVE)){
2292 emsg = N_("`readctl': no such channel: %s\n");
2293 goto jeinval_quote;
2296 jfound:
2297 if(f & a_SET)
2298 n_readctl_read_overlay = grcp;
2299 else if(f & a_REMOVE){
2300 if(n_readctl_read_overlay == grcp)
2301 n_readctl_read_overlay = a_stdin;
2303 if(grcp->grc_last != NULL)
2304 grcp->grc_last->grc_next = grcp->grc_next;
2305 if(grcp->grc_next != NULL)
2306 grcp->grc_next->grc_last = grcp->grc_last;
2307 fclose(grcp->grc_fp);
2308 n_free(grcp);
2309 }else{
2310 FILE *fp;
2311 size_t elen;
2312 si32_t fd;
2314 if(grcp != NULL){
2315 n_err(_("`readctl': channel already exists: %s\n"), /* TODO reopen */
2316 n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2317 n_pstate_err_no = n_ERR_EXIST;
2318 f = a_ERR;
2319 goto jleave;
2322 if((n_idec_si32_cp(&fd, cap->ca_arg.ca_str.s, 0, NULL
2323 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
2324 ) != n_IDEC_STATE_CONSUMED){
2325 if((emsg = fexpand(cap->ca_arg.ca_str.s, FEXP_LOCAL | FEXP_NVAR)
2326 ) == NULL){
2327 emsg = N_("`readctl': cannot expand filename %s\n");
2328 goto jeinval_quote;
2330 fd = -1;
2331 elen = strlen(emsg);
2332 fp = safe_fopen(emsg, "r", NULL);
2333 }else if(fd == STDIN_FILENO || fd == STDOUT_FILENO ||
2334 fd == STDERR_FILENO){
2335 n_err(_("`readctl': create: standard descriptors are not allowed\n"));
2336 goto jeinval;
2337 }else{
2338 /* xxx Avoid */
2339 _CLOEXEC_SET(fd);
2340 emsg = NULL;
2341 elen = 0;
2342 fp = fdopen(fd, "r");
2345 if(fp != NULL){
2346 size_t i;
2348 if((i = UIZ_MAX - elen) <= cap->ca_arg.ca_str.l ||
2349 (i -= cap->ca_arg.ca_str.l) <=
2350 n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +2){
2351 n_err(_("`readctl': failed to create storage for %s\n"),
2352 cap->ca_arg.ca_str.s);
2353 n_pstate_err_no = n_ERR_OVERFLOW;
2354 f = a_ERR;
2355 goto jleave;
2358 grcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_go_readctl_ctx, grc_name) +
2359 cap->ca_arg.ca_str.l +1 + elen +1);
2360 grcp->grc_last = NULL;
2361 if((grcp->grc_next = n_readctl_read_overlay) != NULL)
2362 grcp->grc_next->grc_last = grcp;
2363 n_readctl_read_overlay = grcp;
2364 grcp->grc_fp = fp;
2365 grcp->grc_fd = fd;
2366 memcpy(grcp->grc_name, cap->ca_arg.ca_str.s, cap->ca_arg.ca_str.l +1);
2367 if(elen == 0)
2368 grcp->grc_expand = NULL;
2369 else{
2370 char *cp;
2372 grcp->grc_expand = cp = &grcp->grc_name[cap->ca_arg.ca_str.l +1];
2373 memcpy(cp, emsg, ++elen);
2375 }else{
2376 emsg = N_("`readctl': failed to create file for %s\n");
2377 goto jeinval_quote;
2381 jleave:
2382 NYD_LEAVE;
2383 return (f & a_ERR) ? 1 : 0;
2384 jeinval_quote:
2385 n_err(V_(emsg), n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0));
2386 jeinval:
2387 n_pstate_err_no = n_ERR_INVAL;
2388 f = a_ERR;
2389 goto jleave;
2391 jshow:
2392 if((grcp = n_readctl_read_overlay) == NULL)
2393 fprintf(n_stdout, _("`readctl': no channels registered\n"));
2394 else{
2395 while(grcp->grc_last != NULL)
2396 grcp = grcp->grc_last;
2398 fprintf(n_stdout, _("`readctl': registered channels:\n"));
2399 for(; grcp != NULL; grcp = grcp->grc_next)
2400 fprintf(n_stdout, _("%c%s %s%s%s%s\n"),
2401 (grcp == n_readctl_read_overlay ? '*' : ' '),
2402 (grcp->grc_fd != -1 ? _("descriptor") : _("name")),
2403 n_shexp_quote_cp(grcp->grc_name, FAL0),
2404 (grcp->grc_expand != NULL ? " (" : n_empty),
2405 (grcp->grc_expand != NULL ? grcp->grc_expand : n_empty),
2406 (grcp->grc_expand != NULL ? ")" : n_empty));
2408 f = a_NONE;
2409 goto jleave;
2412 /* s-it-mode */