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 Commands and -aliases deserve a tree. Or so.
6 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
7 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
10 * Copyright (c) 1980, 1993
11 * The Regents of the University of California. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. Neither the name of the University nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #ifndef HAVE_AMALGAMATION
46 a_GO_FREE
= 1u<<0, /* Structure was allocated, n_free() it */
47 a_GO_PIPE
= 1u<<1, /* Open on a pipe */
48 a_GO_FILE
= 1u<<2, /* Loading or sourcing a file */
49 a_GO_MACRO
= 1u<<3, /* Running a macro */
50 a_GO_MACRO_FREE_DATA
= 1u<<4, /* Lines are allocated, n_free() once done */
51 /* TODO For simplicity this is yet _MACRO plus specialization overlay
52 * TODO (_X_OPTION, _CMD) -- these should be types on their own! */
53 a_GO_MACRO_X_OPTION
= 1u<<5, /* Macro indeed command line -X option */
54 a_GO_MACRO_CMD
= 1u<<6, /* Macro indeed single-line: ~:COMMAND */
55 /* TODO a_GO_SPLICE: the right way to support *on-compose-splice(-shell)?*
56 * TODO would be a command_loop object that emits an on_read_line event, and
57 * TODO have a special handler for the compose mode; with that, then,
58 * TODO _event_loop() would not call _evaluate() but CTX->on_read_line,
59 * TODO and _evaluate() would be the standard impl.,
60 * TODO whereas the COMMAND ESCAPE switch in collect.c would be another one.
61 * TODO With this generic accmacvar.c:temporary_compose_mode_hook_call()
62 * TODO could be dropped, and n_go_macro() could become extended,
63 * TODO and/or we would add a n_go_anything(), which would allow special
64 * TODO input handlers, special I/O input and output, special `localopts'
65 * TODO etc., to be glued to the new execution context. And all I/O all
66 * TODO over this software should not use stdin/stdout, but CTX->in/out.
67 * TODO The pstate must be a property of the current execution context, too.
68 * TODO This not today. :( For now we invent a special SPLICE execution
69 * TODO context overlay that at least allows to temporarily modify the
70 * TODO global pstate, and the global stdin and stdout pointers. HACK!
71 * TODO This splice thing is very special and has to go again. HACK!!
72 * TODO a_go_input() will drop it once it sees EOF (HACK!), but care for
73 * TODO jumps must be taken by splice creators. HACK!!! But works. ;} */
75 /* If it is none of those, it must be the outermost, the global one */
76 a_GO_TYPE_MASK
= a_GO_PIPE
| a_GO_FILE
| a_GO_MACRO
|
77 /* a_GO_MACRO_X_OPTION | a_GO_MACRO_CMD | */ a_GO_SPLICE
,
79 a_GO_FORCE_EOF
= 1u<<8, /* go_input() shall return EOF next */
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 /* `xcall' optimization barrier: n_go_macro() has been finished with
89 * a `xcall' request, and `xcall' set this in the parent a_go_input of the
90 * said n_go_macro() to indicate a barrier: we teardown the a_go_input of
91 * the n_go_macro() away after leaving its _event_loop(), but then,
92 * back in n_go_macro(), that enters a for(;;) loop that directly calls
93 * c_call() -- our `xcall' stack avoidance optimization --, yet this call
94 * will itself end up in a new n_go_macro(), and if that again ends up with
95 * `xcall' this should teardown and leave its own n_go_macro(), unrolling the
96 * stack "up to the barrier level", but which effectively still is the
97 * n_go_macro() that lost its a_go_input and is looping the `xcall'
98 * optimization loop. If no `xcall' is desired that loop is simply left and
99 * the _event_loop() of the outer a_go_ctx will perform a loop tick and
100 * clear this bit again OR become teardown itself */
101 a_GO_XCALL_LOOP
= 1u<<24, /* `xcall' optimization barrier level */
102 a_GO_XCALL_LOOP_ERROR
= 1u<<25, /* .. state machine error transporter */
103 a_GO_XCALL_LOOP_MASK
= a_GO_XCALL_LOOP
| a_GO_XCALL_LOOP_ERROR
106 enum a_go_cleanup_mode
{
107 a_GO_CLEANUP_UNWIND
= 1u<<0, /* Teardown all contexts except outermost */
108 a_GO_CLEANUP_TEARDOWN
= 1u<<1, /* Teardown current context */
109 a_GO_CLEANUP_LOOPTICK
= 1u<<2, /* Normal looptick cleanup */
110 a_GO_CLEANUP_MODE_MASK
= n_BITENUM_MASK(0, 2),
112 a_GO_CLEANUP_ERROR
= 1u<<8, /* Error occurred on level */
113 a_GO_CLEANUP_SIGINT
= 1u<<9, /* Interrupt signal received */
114 a_GO_CLEANUP_HOLDALLSIGS
= 1u<<10 /* hold_all_sigs() active TODO */
117 struct a_go_cmd_desc
{
118 char const *gcd_name
; /* Name of command */
119 int (*gcd_func
)(void*); /* Implementor of command */
120 enum n_cmd_arg_flags gcd_caflags
;
121 si16_t gcd_msgflag
; /* Required flags of msgs */
122 si16_t gcd_msgmask
; /* Relevant flags of msgs */
123 #ifdef HAVE_DOCSTRINGS
124 char const *gcd_doc
; /* One line doc for command */
127 /* Yechh, can't initialize unions */
128 #define gcd_minargs gcd_msgflag /* Minimum argcount for WYSH/WYRA/RAWLIST */
129 #define gcd_maxargs gcd_msgmask /* Max argcount for WYSH/WYRA/RAWLIST */
131 struct a_go_alias
{ /* TODO binary search */
132 struct a_go_alias
*ga_next
;
133 struct str ga_cmd
; /* Data follows after .ga_name */
134 char ga_name
[n_VFIELD_SIZE(0)];
137 struct a_go_eval_ctx
{
138 struct str gec_line
; /* The terminated data to _evaluate() */
139 ui32_t gec_line_size
; /* May be used to store line memory size */
141 bool_t gec_ever_seen
; /* Has ever been used (main_loop() only) */
142 bool_t gec_add_history
; /* Add command to history (TRUM1=gabby)? */
145 struct a_go_input_inject
{
146 struct a_go_input_inject
*gii_next
;
149 char gii_dat
[n_VFIELD_SIZE(7)];
153 struct a_go_ctx
*gc_outer
;
154 sigset_t gc_osigmask
;
155 ui32_t gc_flags
; /* enum a_go_flags */
156 ui32_t gc_loff
; /* Pseudo (macro): index in .gc_lines */
157 char **gc_lines
; /* Pseudo content, lines unfolded */
158 FILE *gc_file
; /* File we were in, if applicable */
159 struct a_go_input_inject
*gc_inject
; /* To be consumed first */
160 void (*gc_on_finalize
)(void *);
161 void *gc_finalize_arg
;
162 sigjmp_buf gc_eloop_jmp
; /* TODO one day... for _event_loop() */
163 /* SPLICE hacks: saved stdin/stdout, saved pstate */
164 FILE *gc_splice_stdin
;
165 FILE *gc_splice_stdout
;
166 ui32_t gc_splice_psonce
;
167 ui8_t gc_splice__dummy
[4];
168 struct n_go_data_ctx gc_data
;
169 char gc_name
[n_VFIELD_SIZE(0)]; /* Name of file or macro */
173 struct a_go_ctx
*gx_upto
; /* Unroll stack up to this level */
174 size_t gx_buflen
; /* ARGV requires that much bytes, all in all */
176 struct str gx_argv
[n_VFIELD_SIZE(0)];
179 static sighandler_type a_go_oldpipe
;
180 static struct a_go_alias
*a_go_aliases
;
181 /* a_go_cmd_tab[] after fun protos */
183 /* Our current execution context, and the buffer backing the outermost level */
184 static struct a_go_ctx
*a_go_ctx
;
186 #define a_GO_MAINCTX_NAME "Main event loop"
189 char uf
[n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
) +
190 sizeof(a_GO_MAINCTX_NAME
)];
193 /* `xcall' stack-avoidance bypass optimization */
194 static struct a_go_xcall
*a_go_xcall
;
196 static sigjmp_buf a_go_srbuf
; /* TODO GET RID */
198 /* Isolate the command from the arguments */
199 static char *a_go_isolate(char const *comm
);
202 static int a_go_c_eval(void *vp
);
205 static int a_go_c_xcall(void *vp
);
207 /* `(un)?commandalias' */
208 static int a_go_c_alias(void *vp
);
209 static int a_go_c_unalias(void *vp
);
211 /* Create a multiline info string about all known additional infos for lcp */
212 #ifdef HAVE_DOCSTRINGS
213 static char const *a_go_cmdinfo(struct a_go_cmd_desc
const *gcdp
);
216 /* Print a list of all commands */
217 static int a_go_c_list(void *vp
);
219 static int a_go__pcmd_cmp(void const *s1
, void const *s2
);
221 /* `help' / `?' command */
222 static int a_go_c_help(void *vp
);
224 /* `exit' and `quit' commands */
225 static int a_go_c_exit(void *vp
);
226 static int a_go_c_quit(void *vp
);
228 /* Print the version number of the binary */
229 static int a_go_c_version(void *vp
);
231 static int a_go__version_cmp(void const *s1
, void const *s2
);
233 /* n_PS_STATE_PENDMASK requires some actions */
234 static void a_go_update_pstate(void);
236 /* Evaluate a single command */
237 static bool_t
a_go_evaluate(struct a_go_eval_ctx
*gecp
);
239 /* Get first-fit, or NULL */
240 static struct a_go_cmd_desc
const *a_go__firstfit(char const *comm
);
242 /* Branch here on hangup signal and simulate "exit" */
243 static void a_go_hangup(int s
);
245 /* The following gets called on receipt of an interrupt */
246 static void a_go_onintr(int s
);
248 /* Cleanup current execution context, update the program state.
249 * If _CLEANUP_ERROR is set then we don't alert and error out if the stack
250 * doesn't exist at all, unless _CLEANUP_HOLDALLSIGS we hold_all_sigs() */
251 static void a_go_cleanup(enum a_go_cleanup_mode gcm
);
253 /* `source' and `source_if' (if silent_open_error: no pipes allowed, then).
254 * Returns FAL0 if file is somehow not usable (unless silent_open_error) or
255 * upon evaluation error, and TRU1 on success */
256 static bool_t
a_go_file(char const *file
, bool_t silent_open_error
);
258 /* System resource file load()ing or -X command line option array traversal */
259 static bool_t
a_go_load(struct a_go_ctx
*gcp
);
261 /* A simplified command loop for recursed state machines */
262 static bool_t
a_go_event_loop(struct a_go_ctx
*gcp
, enum n_go_input_flags gif
);
265 static int a_go_c_read(void *vp
);
267 static bool_t
a_go__read_set(char const *cp
, char const *value
);
269 /* List of all commands, and list of commands which are specially treated
270 * and deduced in _evaluate(), but must offer normal descriptions for others */
271 #ifdef HAVE_DOCSTRINGS
276 static struct a_go_cmd_desc
const a_go_cmd_tab
[] = {
279 a_go_special_cmd_tab
[] = {
280 { n_ns
, NULL
, n_CMD_ARG_TYPE_STRING
, 0, 0
281 DS(N_("Comment command: ignore remaining (continuable) line")) },
282 { "-", NULL
, n_CMD_ARG_TYPE_WYSH
, 0, 0
283 DS(N_("Print out the preceding message")) }
288 a_go_isolate(char const *comm
){
290 while(*comm
!= '\0' &&
291 strchr("~|? \t0123456789&%@$^.:/-+*'\",;(`", *comm
) == NULL
)
294 return n_UNCONST(comm
);
298 a_go_c_eval(void *vp
){
299 /* TODO HACK! `eval' should be nothing else but a command prefix, evaluate
300 * TODO ARGV with shell rules, but if that is not possible then simply
301 * TODO adjust argv/argc of "the CmdCtx" that we will have "exec" real cmd */
302 struct n_string s_b
, *sp
;
305 char const **argv
, *cp
;
310 for(j
= i
= 0; (cp
= argv
[i
]) != NULL
; ++i
)
313 sp
= n_string_creat_auto(&s_b
);
314 sp
= n_string_reserve(sp
, j
);
316 for(i
= 0; (cp
= argv
[i
]) != NULL
; ++i
){
318 sp
= n_string_push_c(sp
, ' ');
319 sp
= n_string_push_cp(sp
, cp
);
322 /* TODO HACK! We should inherit the current n_go_input_flags via CmdCtx,
323 * TODO for now we don't have such sort of! n_PS_COMPOSE_MODE is a hack
324 * TODO by itself, since ever: misuse the hack for a hack.
325 * TODO Further more, exit handling is very grazy */
326 (void)/*XXX*/n_go_command((n_pstate
& n_PS_COMPOSE_MODE
327 ? n_GO_INPUT_CTX_COMPOSE
: n_GO_INPUT_CTX_DEFAULT
), n_string_cp(sp
));
329 /* n_pstate_err_no = n_pstate_err_no; */
330 rv
= (a_go_xcall
!= NULL
) ? 0 : n_pstate_ex_no
;
336 a_go_c_xcall(void *vp
){
337 struct a_go_xcall
*gxp
;
340 char const **oargv
, *cp
;
342 struct a_go_ctx
*gcp
;
345 /* The context can only be a macro context, except that possibly a single
346 * level of `eval' (TODO: yet) was used to double-expand our arguments */
347 if((gcp
= a_go_ctx
)->gc_flags
& a_GO_MACRO_CMD
)
349 if((gcp
->gc_flags
& (a_GO_MACRO
| a_GO_MACRO_CMD
)) != a_GO_MACRO
)
352 /* Try to roll up the stack as much as possible.
353 * See a_GO_XCALL_LOOP flag description for more */
354 if(gcp
->gc_outer
!= NULL
){
355 if(gcp
->gc_outer
->gc_flags
& a_GO_XCALL_LOOP
)
358 /* Otherwise this macro is invoked from the top level, in which case we
359 * silently act as if we were `call'... */
361 /* ...which means we must ensure the rest of the macro that was us
362 * doesn't become evaluated! */
363 a_go_xcall
= (struct a_go_xcall
*)-1;
369 for(j
= i
= 0; (cp
= oargv
[i
]) != NULL
; ++i
)
372 gxp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_go_xcall
, gx_argv
) +
373 (sizeof(struct str
) * i
) + ++j
);
375 gxp
->gx_buflen
= (sizeof(char*) * (i
+ 1)) + j
; /* ARGV inc. strings! */
377 xcp
= (char*)&gxp
->gx_argv
[i
];
379 for(i
= 0; (cp
= oargv
[i
]) != NULL
; ++i
){
380 gxp
->gx_argv
[i
].l
= j
= strlen(cp
);
381 gxp
->gx_argv
[i
].s
= xcp
;
382 memcpy(xcp
, cp
, ++j
);
392 n_err(_("`xcall': can only be used inside a macro\n"));
393 n_pstate_err_no
= n_ERR_INVAL
;
399 a_go_c_alias(void *vp
){
400 struct a_go_alias
*lgap
, *gap
;
412 if((fp
= Ftmp(NULL
, "commandalias", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)
416 for(i
= 0, gap
= a_go_aliases
; gap
!= NULL
; gap
= gap
->ga_next
)
417 fprintf(fp
, "commandalias %s %s\n",
418 gap
->ga_name
, n_shexp_quote_cp(gap
->ga_cmd
.s
, TRU1
));
421 page_or_print(fp
, i
);
427 /* Verify the name is a valid one, and not a command modifier */
428 if(*argv
[0] == '\0' || *a_go_isolate(argv
[0]) != '\0' ||
429 !asccasecmp(argv
[0], "ignerr") || !asccasecmp(argv
[0], "wysh") ||
430 !asccasecmp(argv
[0], "vput")){
431 n_err(_("`commandalias': cannot canonicalize %s\n"),
432 n_shexp_quote_cp(argv
[0], FAL0
));
437 /* Show command of single alias? */
439 for(gap
= a_go_aliases
; gap
!= NULL
; gap
= gap
->ga_next
)
440 if(!strcmp(argv
[0], gap
->ga_name
)){
441 fprintf(n_stdout
, "commandalias %s %s\n",
442 gap
->ga_name
, n_shexp_quote_cp(gap
->ga_cmd
.s
, TRU1
));
445 n_err(_("`commandalias': no such alias: %s\n"), argv
[0]);
450 /* Define command for alias: verify command content */
451 for(cl
= 0, i
= 1; (cp
= n_UNCONST(argv
[i
])) != NULL
; ++i
)
453 cl
+= strlen(cp
) +1; /* SP or NUL */
455 n_err(_("`commandalias': empty arguments after %s\n"), argv
[0]);
460 /* If the alias already exists, recreate */
461 for(lgap
= NULL
, gap
= a_go_aliases
; gap
!= NULL
;
462 lgap
= gap
, gap
= gap
->ga_next
)
463 if(!strcmp(gap
->ga_name
, argv
[0])){
465 lgap
->ga_next
= gap
->ga_next
;
467 a_go_aliases
= gap
->ga_next
;
472 nl
= strlen(argv
[0]) +1;
473 gap
= n_alloc(n_VSTRUCT_SIZEOF(struct a_go_alias
, ga_name
) + nl
+ cl
);
474 gap
->ga_next
= a_go_aliases
;
476 memcpy(gap
->ga_name
, argv
[0], nl
);
477 cp
= gap
->ga_cmd
.s
= &gap
->ga_name
[nl
];
478 gap
->ga_cmd
.l
= --cl
;
480 while(*++argv
!= NULL
)
481 if((i
= strlen(*argv
)) > 0){
482 memcpy(cp
, *argv
, i
);
493 a_go_c_unalias(void *vp
){
494 struct a_go_alias
*lgap
, *gap
;
495 char const **argv
, *cp
;
502 while((cp
= *argv
++) != NULL
){
503 if(cp
[0] == '*' && cp
[1] == '\0'){
504 while((gap
= a_go_aliases
) != NULL
){
505 a_go_aliases
= gap
->ga_next
;
509 for(lgap
= NULL
, gap
= a_go_aliases
; gap
!= NULL
;
510 lgap
= gap
, gap
= gap
->ga_next
)
511 if(!strcmp(gap
->ga_name
, cp
)){
513 lgap
->ga_next
= gap
->ga_next
;
515 a_go_aliases
= gap
->ga_next
;
519 n_err(_("`uncommandalias': no such alias: %s\n"),
520 n_shexp_quote_cp(cp
, FAL0
));
529 #ifdef HAVE_DOCSTRINGS
531 a_go_cmdinfo(struct a_go_cmd_desc
const *gcdp
){
532 struct n_string rvb
, *rv
;
536 rv
= n_string_creat_auto(&rvb
);
537 rv
= n_string_reserve(rv
, 80);
539 switch(gcdp
->gcd_caflags
& n_CMD_ARG_TYPE_MASK
){
540 case n_CMD_ARG_TYPE_MSGLIST
:
541 cp
= N_("message-list");
543 case n_CMD_ARG_TYPE_STRING
:
544 case n_CMD_ARG_TYPE_RAWDAT
:
545 cp
= N_("string data");
547 case n_CMD_ARG_TYPE_RAWLIST
:
548 cp
= N_("old-style quoting");
550 case n_CMD_ARG_TYPE_NDMLIST
:
551 cp
= N_("message-list (no default)");
553 case n_CMD_ARG_TYPE_WYRA
:
554 cp
= N_("`wysh' for sh(1)ell-style quoting");
557 case n_CMD_ARG_TYPE_WYSH
:
558 cp
= (gcdp
->gcd_minargs
== 0 && gcdp
->gcd_maxargs
== 0)
559 ? N_("sh(1)ell-style quoting (takes no arguments)")
560 : N_("sh(1)ell-style quoting");
563 rv
= n_string_push_cp(rv
, V_(cp
));
565 if(gcdp
->gcd_caflags
& n_CMD_ARG_V
)
566 rv
= n_string_push_cp(rv
, _(" | vput modifier"));
567 if(gcdp
->gcd_caflags
& n_CMD_ARG_EM
)
568 rv
= n_string_push_cp(rv
, _(" | error in *!*"));
570 if(gcdp
->gcd_caflags
& n_CMD_ARG_A
)
571 rv
= n_string_push_cp(rv
, _(" | needs box"));
572 if(gcdp
->gcd_caflags
& n_CMD_ARG_I
)
573 rv
= n_string_push_cp(rv
, _(" | ok: batch or interactive"));
574 if(gcdp
->gcd_caflags
& n_CMD_ARG_M
)
575 rv
= n_string_push_cp(rv
, _(" | ok: send mode"));
576 if(gcdp
->gcd_caflags
& n_CMD_ARG_R
)
577 rv
= n_string_push_cp(rv
, _(" | not ok: compose mode"));
578 if(gcdp
->gcd_caflags
& n_CMD_ARG_S
)
579 rv
= n_string_push_cp(rv
, _(" | not ok: during startup"));
580 if(gcdp
->gcd_caflags
& n_CMD_ARG_X
)
581 rv
= n_string_push_cp(rv
, _(" | ok: subprocess"));
583 if(gcdp
->gcd_caflags
& n_CMD_ARG_G
)
584 rv
= n_string_push_cp(rv
, _(" | gabby history"));
586 cp
= n_string_cp(rv
);
590 #endif /* HAVE_DOCSTRINGS */
593 a_go_c_list(void *vp
){
595 struct a_go_cmd_desc
const **gcdpa
, *gcdp
, **gcdpa_curr
;
599 i
= n_NELEM(a_go_cmd_tab
) + n_NELEM(a_go_special_cmd_tab
) +1;
600 gcdpa
= n_autorec_alloc(sizeof(gcdp
) * i
);
602 for(i
= 0; i
< n_NELEM(a_go_cmd_tab
); ++i
)
603 gcdpa
[i
] = &a_go_cmd_tab
[i
];
607 for(j
= 0; j
< n_NELEM(a_go_special_cmd_tab
); ++i
, ++j
)
608 gcdpa
[i
] = &a_go_special_cmd_tab
[j
];
612 if(*(void**)vp
== NULL
)
613 qsort(gcdpa
, i
, sizeof(*gcdpa
), &a_go__pcmd_cmp
);
615 if((fp
= Ftmp(NULL
, "list", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
)
618 fprintf(fp
, _("Commands are:\n"));
619 scrwid
= n_SCRNWIDTH_FOR_LISTS
;
621 for(i
= 0, gcdpa_curr
= gcdpa
; (gcdp
= *gcdpa_curr
++) != NULL
;){
622 if(gcdp
->gcd_func
== &c_cmdnotsupp
)
624 if(n_poption
& n_PO_D_V
){
625 fprintf(fp
, "%s\n", gcdp
->gcd_name
);
627 #ifdef HAVE_DOCSTRINGS
628 fprintf(fp
, " : %s\n", V_(gcdp
->gcd_doc
));
630 fprintf(fp
, " : %s\n", a_go_cmdinfo(gcdp
));
636 if((i
+= (j
= strlen(gcdp
->gcd_name
) + 2)) > scrwid
){
641 fprintf(fp
, (*gcdpa_curr
!= NULL
? "%s, " : "%s\n"), gcdp
->gcd_name
);
646 page_or_print(fp
, l
);
654 a_go__pcmd_cmp(void const *s1
, void const *s2
){
655 struct a_go_cmd_desc
const * const *gcdpa1
, * const *gcdpa2
;
661 rv
= strcmp((*gcdpa1
)->gcd_name
, (*gcdpa2
)->gcd_name
);
667 a_go_c_help(void *vp
){
672 /* Help for a single command? */
673 if((arg
= *(char**)vp
) != NULL
){
674 struct a_go_alias
const *gap
;
675 struct a_go_cmd_desc
const *gcdp
, *gcdp_max
;
677 /* Aliases take precedence */
678 for(gap
= a_go_aliases
; gap
!= NULL
; gap
= gap
->ga_next
)
679 if(!strcmp(arg
, gap
->ga_name
)){
680 fprintf(n_stdout
, "%s -> ", arg
);
685 gcdp_max
= &(gcdp
= a_go_cmd_tab
)[n_NELEM(a_go_cmd_tab
)];
687 for(; gcdp
< gcdp_max
; ++gcdp
){
688 if(is_prefix(arg
, gcdp
->gcd_name
)){
689 fputs(arg
, n_stdout
);
690 if(strcmp(arg
, gcdp
->gcd_name
))
691 fprintf(n_stdout
, " (%s)", gcdp
->gcd_name
);
695 #ifdef HAVE_DOCSTRINGS
696 fprintf(n_stdout
, ": %s", V_(gcdp
->gcd_doc
));
697 if(n_poption
& n_PO_D_V
)
698 fprintf(n_stdout
, "\n : %s", a_go_cmdinfo(gcdp
));
700 putc('\n', n_stdout
);
705 if(gcdp_max
== &a_go_cmd_tab
[n_NELEM(a_go_cmd_tab
)]){
707 a_go_special_cmd_tab
)[n_NELEM(a_go_special_cmd_tab
)];
712 fprintf(n_stdout
, "%s\n", n_shexp_quote_cp(arg
, TRU1
));
715 n_err(_("Unknown command: `%s'\n"), arg
);
719 /* Very ugly, but take care for compiler supported string lengths :( */
720 fputs(n_progname
, n_stdout
);
722 " commands -- <msglist> denotes message specifications,\n"
723 "e.g., 1-5, :n or ., separated by spaces:\n"), n_stdout
);
726 "type <msglist> type (`print') messages (honour `headerpick' etc.)\n"
727 "Type <msglist> like `type' but always show all headers\n"
728 "next goto and type next message\n"
729 "from <msglist> (search and) print header summary for the given list\n"
730 "headers header summary for messages surrounding \"dot\"\n"
731 "delete <msglist> delete messages (can be `undelete'd)\n"),
736 "save <msglist> folder append messages to folder and mark as saved\n"
737 "copy <msglist> folder like `save', but don't mark them (`move' moves)\n"
738 "write <msglist> file write message contents to file (prompts for parts)\n"
739 "Reply <msglist> reply to message senders only\n"
740 "reply <msglist> like `Reply', but address all recipients\n"
741 "Lreply <msglist> forced mailing-list `reply' (see `mlist')\n"),
746 "mail <recipients> compose a mail for the given recipients\n"
747 "file folder change to another mailbox\n"
748 "File folder like `file', but open readonly\n"
749 "quit quit and apply changes to the current mailbox\n"
750 "xit or exit like `quit', but discard changes\n"
751 "!shell command shell escape\n"
752 "list [<anything>] all available commands [in search order]\n"),
755 rv
= (ferror(n_stdout
) != 0);
763 a_go_c_exit(void *vp
){
766 n_psonce
|= n_PSO_XIT
;
772 a_go_c_quit(void *vp
){
775 n_psonce
|= n_PSO_QUIT
;
781 a_go_c_version(void *vp
){
784 char const *cp
, **arr
;
789 fprintf(n_stdout
, _("%s version %s\nFeatures included (+) or not (-)\n"),
790 n_uagent
, ok_vlook(version
));
792 /* *features* starts with dummy byte to avoid + -> *folder* expansions */
793 i
= strlen(cp
= &ok_vlook(features
)[1]) +1;
794 iop
= n_autorec_alloc(i
);
797 arr
= n_autorec_alloc(sizeof(cp
) * VAL_FEATURES_CNT
);
798 for(longest
= 0, i
= 0; (cp
= n_strsep(&iop
, ',', TRU1
)) != NULL
; ++i
){
801 longest
= n_MAX(longest
, (int)i2
);
803 qsort(arr
, i
, sizeof(cp
), &a_go__version_cmp
);
805 /* We use aligned columns, so don't use n_SCRNWIDTH_FOR_LISTS */
806 for(++longest
, i2
= 0; i
-- > 0;){
808 fprintf(n_stdout
, "%-*s ", longest
, cp
);
810 if(UICMP(z
, ++i2
+ longest
, >=, n_scrnwidth
) || i
== 0){
812 putc('\n', n_stdout
);
816 if((rv
= ferror(n_stdout
) != 0))
823 a_go__version_cmp(void const *s1
, void const *s2
){
824 char const * const *cp1
, * const *cp2
;
830 rv
= strcmp(&(*cp1
)[1], &(*cp2
)[1]);
836 a_go_update_pstate(void){
840 act
= ((n_pstate
& n_PS_SIGWINCH_PEND
) != 0);
841 n_pstate
&= ~n_PS_PSTATE_PENDMASK
;
846 snprintf(buf
, sizeof buf
, "%d", n_scrnwidth
);
847 ok_vset(COLUMNS
, buf
);
848 snprintf(buf
, sizeof buf
, "%d", n_scrnheight
);
855 a_go_evaluate(struct a_go_eval_ctx
*gecp
){
856 /* xxx old style(9), but also old code */
857 /* TODO a_go_evaluate() should be splitted in multiple subfunctions,
858 * TODO `eval' should be a prefix, etc., a Ctx should be passed along */
860 char _wordbuf
[2], **arglist_base
/*[n_MAXARGC]*/, **arglist
, *cp
, *word
;
861 struct a_go_alias
*gap
;
862 struct a_go_cmd_desc
const *gcdp
;
863 si32_t nerrn
, nexn
; /* TODO n_pstate_ex_no -> si64_t! */
867 a_ALIAS_MASK
= n_BITENUM_MASK(0, 2), /* Alias recursion counter bits */
868 a_NOPREFIX
= 1u<<4, /* Modifier prefix not allowed right now */
869 a_NOALIAS
= 1u<<5, /* "No alias!" expansion modifier */
870 /* New modifier prefixes must be reflected in a_go_c_alias()! */
871 a_IGNERR
= 1u<<6, /* ignerr modifier prefix */
872 a_WYSH
= 1u<<7, /* XXX v15+ drop wysh modifier prefix */
873 a_VPUT
= 1u<<8, /* vput modifier prefix */
874 a_NO_ERRNO
= 1u<<16 /* Don't set n_pstate_err_no */
885 arglist_base
= n_autorec_alloc(sizeof(*arglist_base
) * n_MAXARGC
);
886 line
= gecp
->gec_line
; /* TODO const-ify original (buffer pointer)! */
887 assert(line
.s
[line
.l
] == '\0');
888 gecp
->gec_add_history
= FAL0
;
890 /* Aliases that refer to shell commands or macro expansion restart */
893 /* Strip the white space away from end and beginning of command.
894 * XXX Ideally this will be m_string_trim() */
899 for(cp
= &line
.s
[i
-1]; spacechar(*cp
); --cp
)
902 line
.s
[line
.l
= i
] = '\0';
905 for(cp
= line
.s
; spacechar(*cp
); ++cp
)
907 line
.l
-= PTR2SIZE(cp
- line
.s
);
913 /* Ignore null commands (comments) */
914 if(*(cp
= line
.s
) == '#')
917 /* Handle ! differently to get the correct lexical conventions */
920 /* Isolate the actual command; since it may not necessarily be
921 * separated from the arguments (as in `p1') we need to duplicate it to
922 * be able to create a NUL terminated version.
923 * We must be aware of several special one letter commands here */
924 else if((cp
= a_go_isolate(cp
)) == line
.s
&&
925 (*cp
== '|' || *cp
== '~' || *cp
== '?'))
927 c
= (int)PTR2SIZE(cp
- line
.s
);
929 word
= UICMP(z
, c
, <, sizeof _wordbuf
) ? _wordbuf
: n_autorec_alloc(c
+1);
930 memcpy(word
, arglist
[0] = line
.s
, c
);
934 /* No-expansion modifier? */
935 if(!(flags
& a_NOPREFIX
) && *word
== '\\'){
941 /* It may be a modifier prefix */
942 if(c
== sizeof("ignerr") -1 && !asccasecmp(word
, "ignerr")){
943 flags
|= a_NOPREFIX
| a_IGNERR
;
945 }else if(c
== sizeof("wysh") -1 && !asccasecmp(word
, "wysh")){
946 flags
|= a_NOPREFIX
| a_WYSH
;
948 }else if(c
== sizeof("vput") -1 && !asccasecmp(word
, "vput")){
949 flags
|= a_NOPREFIX
| a_VPUT
;
953 /* Look up the command; if not found, bitch.
954 * Normally, a blank command would map to the first command in the
955 * table; while n_PS_SOURCING, however, we ignore blank lines to eliminate
956 * confusion; act just the same for aliases */
959 if((n_pstate
& n_PS_ROBOT
) || !(n_psonce
& n_PSO_INTERACTIVE
) ||
962 gcdp
= &a_go_cmd_tab
[0];
966 if(!(flags
& a_NOALIAS
) && (flags
& a_ALIAS_MASK
) != a_ALIAS_MASK
){
969 expcnt
= (flags
& a_ALIAS_MASK
);
971 flags
= (flags
& ~(a_ALIAS_MASK
| a_NOPREFIX
)) | expcnt
;
973 /* Avoid self-recursion; yes, the user could use \ no-expansion, but.. */
974 if(gap
!= NULL
&& !strcmp(word
, gap
->ga_name
)){
975 if(n_poption
& n_PO_D_V
)
976 n_err(_("Actively avoiding self-recursion of `commandalias': %s\n"),
978 }else for(gap
= a_go_aliases
; gap
!= NULL
; gap
= gap
->ga_next
)
979 if(!strcmp(word
, gap
->ga_name
)){
984 line
.s
= n_autorec_alloc(i
+ line
.l
+1);
985 memcpy(line
.s
, gap
->ga_cmd
.s
, i
);
986 memcpy(line
.s
+ i
, cp
, line
.l
);
987 line
.s
[i
+= line
.l
] = '\0';
990 line
.s
= gap
->ga_cmd
.s
;
991 line
.l
= gap
->ga_cmd
.l
;
997 if((gcdp
= a_go__firstfit(word
)) == NULL
|| gcdp
->gcd_func
== &c_cmdnotsupp
){
1000 if(!(s
= n_cnd_if_isskip()) || (n_poption
& n_PO_D_V
))
1001 n_err(_("Unknown command%s: `%s'\n"),
1002 (s
? _(" (ignored due to `if' condition)") : n_empty
), word
);
1009 nerrn
= n_ERR_NOSYS
;
1013 /* See if we should execute the command -- if a conditional we always
1014 * execute it, otherwise, check the state of cond */
1016 if(!(gcdp
->gcd_caflags
& n_CMD_ARG_F
) && n_cnd_if_isskip())
1019 nerrn
= n_ERR_INVAL
;
1021 /* Process the arguments to the command, depending on the type it expects */
1022 if((gcdp
->gcd_caflags
& n_CMD_ARG_I
) && !(n_psonce
& n_PSO_INTERACTIVE
) &&
1023 !(n_poption
& n_PO_BATCH_FLAG
)){
1024 n_err(_("May not execute `%s' unless interactive or in batch mode\n"),
1028 if(!(gcdp
->gcd_caflags
& n_CMD_ARG_M
) && (n_psonce
& n_PSO_SENDMODE
)){
1029 n_err(_("May not execute `%s' while sending\n"), gcdp
->gcd_name
);
1032 if(gcdp
->gcd_caflags
& n_CMD_ARG_R
){
1033 if(n_pstate
& n_PS_COMPOSE_MODE
){
1034 /* TODO n_PS_COMPOSE_MODE: should allow `reply': ~:reply! */
1035 n_err(_("Cannot invoke `%s' when in compose mode\n"), gcdp
->gcd_name
);
1038 /* TODO Nothing should prevent n_CMD_ARG_R in conjunction with
1039 * TODO n_PS_ROBOT|_SOURCING; see a.._may_yield_control()! */
1040 if(n_pstate
& (n_PS_ROBOT
| n_PS_SOURCING
)){
1041 n_err(_("Cannot invoke `%s' from a macro or during file inclusion\n"),
1046 if((gcdp
->gcd_caflags
& n_CMD_ARG_S
) && !(n_psonce
& n_PSO_STARTED
)){
1047 n_err(_("May not execute `%s' during startup\n"), gcdp
->gcd_name
);
1050 if(!(gcdp
->gcd_caflags
& n_CMD_ARG_X
) && (n_pstate
& n_PS_COMPOSE_FORKHOOK
)){
1051 n_err(_("Cannot invoke `%s' from a hook running in a child process\n"),
1056 if((gcdp
->gcd_caflags
& n_CMD_ARG_A
) && mb
.mb_type
== MB_VOID
){
1057 n_err(_("Cannot execute `%s' without active mailbox\n"), gcdp
->gcd_name
);
1060 if((gcdp
->gcd_caflags
& n_CMD_ARG_W
) && !(mb
.mb_perm
& MB_DELE
)){
1061 n_err(_("May not execute `%s' -- message file is read only\n"),
1066 if(gcdp
->gcd_caflags
& n_CMD_ARG_O
)
1067 n_OBSOLETE2(_("this command will be removed"), gcdp
->gcd_name
);
1069 /* TODO v15: strip n_PS_ARGLIST_MASK off, just in case the actual command
1070 * TODO doesn't use any of those list commands which strip this mask,
1071 * TODO and for now we misuse bits for checking relation to history;
1072 * TODO argument state should be property of a per-command carrier instead */
1073 n_pstate
&= ~n_PS_ARGLIST_MASK
;
1075 if((flags
& a_WYSH
) &&
1076 (gcdp
->gcd_caflags
& n_CMD_ARG_TYPE_MASK
) != n_CMD_ARG_TYPE_WYRA
){
1077 n_err(_("`wysh' prefix does not affect `%s'\n"), gcdp
->gcd_name
);
1082 if(gcdp
->gcd_caflags
& n_CMD_ARG_V
){
1085 emsg
= line
.s
; /* xxx Cannot pass &char* as char const**, so no cp */
1086 arglist
[0] = n_shexp_parse_token_cp((n_SHEXP_PARSE_TRIMSPACE
|
1087 n_SHEXP_PARSE_LOG
| n_SHEXP_PARSE_META_KEEP
), &emsg
);
1088 line
.l
-= PTR2SIZE(emsg
- line
.s
);
1089 line
.s
= cp
= n_UNCONST(emsg
);
1091 emsg
= N_("could not parse input token");
1092 else if(!n_shexp_is_valid_varname(arglist
[0]))
1093 emsg
= N_("not a valid variable name");
1094 else if(!n_var_is_user_writable(arglist
[0]))
1095 emsg
= N_("either not a user writable, or a boolean variable");
1099 n_err("`%s': vput: %s: %s\n",
1100 gcdp
->gcd_name
, V_(emsg
), n_shexp_quote_cp(arglist
[0], FAL0
));
1101 nerrn
= n_ERR_NOTSUP
;
1105 n_pstate
|= n_PS_ARGMOD_VPUT
; /* TODO YET useless since stripped later
1106 * TODO on in getrawlist() etc., i.e., the argument vector producers,
1107 * TODO therefore yet needs to be set again based on flags&a_VPUT! */
1109 n_err(_("`vput' prefix does not affect `%s'\n"), gcdp
->gcd_name
);
1114 switch(gcdp
->gcd_caflags
& n_CMD_ARG_TYPE_MASK
){
1115 case n_CMD_ARG_TYPE_MSGLIST
:
1116 /* Message list defaulting to nearest forward legal message */
1117 if(n_msgvec
== NULL
)
1119 if((c
= getmsglist(line
.s
, n_msgvec
, gcdp
->gcd_msgflag
)) < 0){
1120 nerrn
= n_ERR_NOMSG
;
1121 flags
|= a_NO_ERRNO
;
1125 if((n_msgvec
[0] = first(gcdp
->gcd_msgflag
, gcdp
->gcd_msgmask
)) != 0)
1128 if(n_msgvec
[0] == 0){
1130 if(!(n_pstate
& n_PS_HOOK_MASK
))
1131 fprintf(n_stdout
, _("No applicable messages\n"));
1132 nerrn
= n_ERR_NOMSG
;
1133 flags
|= a_NO_ERRNO
;
1136 rv
= (*gcdp
->gcd_func
)(n_msgvec
);
1139 case n_CMD_ARG_TYPE_NDMLIST
:
1140 /* Message list with no defaults, but no error if none exist */
1141 if(n_msgvec
== NULL
)
1143 if((c
= getmsglist(line
.s
, n_msgvec
, gcdp
->gcd_msgflag
)) < 0){
1144 nerrn
= n_ERR_NOMSG
;
1145 flags
|= a_NO_ERRNO
;
1148 rv
= (*gcdp
->gcd_func
)(n_msgvec
);
1151 case n_CMD_ARG_TYPE_STRING
:
1152 /* Just the straight string, old style, with leading blanks removed */
1153 for(cp
= line
.s
; spacechar(*cp
);)
1155 rv
= (*gcdp
->gcd_func
)(cp
);
1157 case n_CMD_ARG_TYPE_RAWDAT
:
1158 /* Just the straight string, leading blanks removed, placed in argv[] */
1159 for(cp
= line
.s
; spacechar(*cp
);)
1163 rv
= (*gcdp
->gcd_func
)(arglist_base
);
1166 case n_CMD_ARG_TYPE_WYSH
:
1170 case n_CMD_ARG_TYPE_WYRA
:
1171 c
= (flags
& a_WYSH
) ? 1 : 0;
1173 case n_CMD_ARG_TYPE_RAWLIST
:
1177 if((c
= getrawlist((c
!= 0), arglist
,
1178 n_MAXARGC
- PTR2SIZE(arglist
- arglist_base
), line
.s
, line
.l
)) < 0){
1179 n_err(_("Invalid argument list\n"));
1180 flags
|= a_NO_ERRNO
;
1184 if(c
< gcdp
->gcd_minargs
){
1185 n_err(_("`%s' requires at least %u arg(s)\n"),
1186 gcdp
->gcd_name
, (ui32_t
)gcdp
->gcd_minargs
);
1187 flags
|= a_NO_ERRNO
;
1191 if(c
> gcdp
->gcd_maxargs
){
1192 n_err(_("`%s' takes no more than %u arg(s)\n"),
1193 gcdp
->gcd_name
, (ui32_t
)gcdp
->gcd_maxargs
);
1194 flags
|= a_NO_ERRNO
;
1200 n_pstate
|= n_PS_ARGMOD_VPUT
;
1202 rv
= (*gcdp
->gcd_func
)(arglist_base
);
1203 if(a_go_xcall
!= NULL
)
1208 DBG( n_panic(_("Implementation error: unknown argument type: %d"),
1209 gcdp
->gcd_caflags
& n_CMD_ARG_TYPE_MASK
); )
1210 nerrn
= n_ERR_NOTOBACCO
;
1215 if(!(gcdp
->gcd_caflags
& n_CMD_ARG_H
))
1216 gecp
->gec_add_history
= (((gcdp
->gcd_caflags
& n_CMD_ARG_G
) ||
1217 (n_pstate
& n_PS_MSGLIST_GABBY
)) ? TRUM1
: TRU1
);
1220 if(!(flags
& a_NO_ERRNO
)){
1221 if(gcdp
->gcd_caflags
& n_CMD_ARG_EM
)
1222 flags
|= a_NO_ERRNO
;
1223 else if((nerrn
= n_err_no
) == 0)
1224 nerrn
= n_ERR_INVAL
;
1226 flags
^= a_NO_ERRNO
;
1227 }else if(gcdp
->gcd_caflags
& n_CMD_ARG_EM
)
1228 flags
|= a_NO_ERRNO
;
1234 if(flags
& a_IGNERR
){
1235 n_pstate
&= ~n_PS_ERR_EXIT_MASK
;
1236 n_exit_status
= n_EXIT_OK
;
1240 if((bo
= ok_blook(batch_exit_on_error
))){
1241 n_OBSOLETE(_("please use *errexit*, not *batch-exit-on-error*"));
1242 if(!(n_poption
& n_PO_BATCH_FLAG
))
1245 if(ok_blook(errexit
) || bo
) /* TODO v15: drop bo */
1246 n_pstate
|= n_PS_ERR_QUIT
;
1247 else if(!(n_psonce
& (n_PSO_INTERACTIVE
| n_PSO_STARTED
)) &&
1249 n_pstate
|= n_PS_ERR_XIT
;
1254 if(n_exit_status
== n_EXIT_OK
)
1255 n_exit_status
= n_EXIT_ERR
;
1256 if((n_poption
& n_PO_D_V
) &&
1257 !(n_psonce
& (n_PSO_INTERACTIVE
| n_PSO_STARTED
)))
1258 n_alert(_("Non-interactive, bailing out due to errors "
1259 "in startup load phase"));
1266 if((gcdp
->gcd_caflags
& n_CMD_ARG_P
) && ok_blook(autoprint
))
1268 n_go_input_inject(n_GO_INPUT_INJECT_COMMIT
, "\\type",
1269 sizeof("\\type") -1);
1271 if(!(n_pstate
& (n_PS_SOURCING
| n_PS_HOOK_MASK
)) &&
1272 !(gcdp
->gcd_caflags
& n_CMD_ARG_T
))
1273 n_pstate
|= n_PS_SAW_COMMAND
;
1277 if(!(flags
& a_NO_ERRNO
))
1278 n_pstate_err_no
= nerrn
;
1279 n_pstate_ex_no
= nexn
;
1284 static struct a_go_cmd_desc
const *
1285 a_go__firstfit(char const *comm
){ /* TODO *hashtable*! linear list search!!! */
1286 struct a_go_cmd_desc
const *gcdp
;
1289 for(gcdp
= a_go_cmd_tab
; gcdp
< &a_go_cmd_tab
[n_NELEM(a_go_cmd_tab
)]; ++gcdp
)
1290 if(*comm
== *gcdp
->gcd_name
&& is_prefix(comm
, gcdp
->gcd_name
))
1300 NYD_X
; /* Signal handler */
1302 /* nothing to do? */
1307 a_go_onintr(int s
){ /* TODO block signals while acting */
1308 NYD_X
; /* Signal handler */
1311 safe_signal(SIGINT
, a_go_onintr
);
1313 termios_state_reset();
1315 a_go_cleanup(a_GO_CLEANUP_UNWIND
| /* XXX FAKE */a_GO_CLEANUP_HOLDALLSIGS
);
1318 n_err_sighdl(_("Interrupt\n"));
1319 safe_signal(SIGPIPE
, a_go_oldpipe
);
1320 siglongjmp(a_go_srbuf
, 0); /* FIXME get rid */
1324 a_go_cleanup(enum a_go_cleanup_mode gcm
){
1325 /* Signals blocked */
1326 struct a_go_ctx
*gcp
;
1329 if(!(gcm
& a_GO_CLEANUP_HOLDALLSIGS
))
1334 /* Free input injections of this level first */
1335 if(!(gcm
& a_GO_CLEANUP_LOOPTICK
)){
1336 struct a_go_input_inject
**giipp
, *giip
;
1338 for(giipp
= &gcp
->gc_inject
; (giip
= *giipp
) != NULL
;){
1339 *giipp
= giip
->gii_next
;
1344 /* Cleanup non-crucial external stuff */
1346 if(gcp
->gc_data
.gdc_colour
!= NULL
)
1347 n_colour_stack_del(NULL
);
1350 /* Work the actual context (according to cleanup mode) */
1351 if(gcp
->gc_outer
== NULL
){
1352 if(gcm
& (a_GO_CLEANUP_UNWIND
| a_GO_CLEANUP_SIGINT
)){
1354 gcp
->gc_flags
&= ~a_GO_XCALL_LOOP_MASK
;
1355 n_pstate
&= ~n_PS_ERR_EXIT_MASK
;
1358 if(!(n_pstate
& n_PS_SOURCING
))
1364 n_pstate
&= ~(n_PS_SOURCING
| n_PS_ROBOT
);
1365 assert(a_go_xcall
== NULL
);
1366 assert(!(gcp
->gc_flags
& a_GO_XCALL_LOOP_MASK
));
1367 assert(gcp
->gc_on_finalize
== NULL
);
1368 assert(gcp
->gc_data
.gdc_colour
== NULL
);
1370 }else if(gcm
& a_GO_CLEANUP_LOOPTICK
){
1373 }else if(gcp
->gc_flags
& a_GO_SPLICE
){ /* TODO Temporary hack */
1374 n_stdin
= gcp
->gc_splice_stdin
;
1375 n_stdout
= gcp
->gc_splice_stdout
;
1376 n_psonce
= gcp
->gc_splice_psonce
;
1380 /* Cleanup crucial external stuff */
1381 if(gcp
->gc_data
.gdc_ifcond
!= NULL
){
1382 n_cnd_if_stack_del(gcp
->gc_data
.gdc_ifcond
);
1383 if(!(gcp
->gc_flags
& a_GO_FORCE_EOF
) &&
1384 !(gcm
& (a_GO_CLEANUP_ERROR
| a_GO_CLEANUP_SIGINT
)) &&
1386 n_err(_("Unmatched `if' at end of %s %s\n"),
1387 ((gcp
->gc_flags
& a_GO_MACRO
1388 ? (gcp
->gc_flags
& a_GO_MACRO_CMD
? _("command") : _("macro"))
1389 : _("`source'd file"))),
1391 gcm
|= a_GO_CLEANUP_ERROR
;
1394 /* Teardown context */
1395 if(gcp
->gc_flags
& a_GO_MACRO
){
1396 if(gcp
->gc_flags
& a_GO_MACRO_FREE_DATA
){
1399 while(*(lp
= &gcp
->gc_lines
[gcp
->gc_loff
]) != NULL
){
1403 /* Part of gcp's memory chunk, then */
1404 if(!(gcp
->gc_flags
& a_GO_MACRO_CMD
))
1405 n_free(gcp
->gc_lines
);
1407 }else if(gcp
->gc_flags
& a_GO_PIPE
)
1408 /* XXX command manager should -TERM then -KILL instead of hoping
1409 * XXX for exit of provider due to n_ERR_PIPE / SIGPIPE */
1410 Pclose(gcp
->gc_file
, TRU1
);
1411 else if(gcp
->gc_flags
& a_GO_FILE
)
1412 Fclose(gcp
->gc_file
);
1414 if(!(gcp
->gc_flags
& a_GO_MEMPOOL_INHERITED
)){
1415 if(gcp
->gc_data
.gdc_mempool
!= NULL
)
1416 n_memory_pool_pop(NULL
);
1421 n_go_data
= &(a_go_ctx
= gcp
->gc_outer
)->gc_data
;
1422 if((a_go_ctx
->gc_flags
& (a_GO_MACRO
| a_GO_SUPER_MACRO
)) ==
1423 (a_GO_MACRO
| a_GO_SUPER_MACRO
)){
1424 n_pstate
&= ~n_PS_SOURCING
;
1425 assert(n_pstate
& n_PS_ROBOT
);
1426 }else if(!(a_go_ctx
->gc_flags
& a_GO_TYPE_MASK
))
1427 n_pstate
&= ~(n_PS_SOURCING
| n_PS_ROBOT
);
1429 assert(n_pstate
& n_PS_ROBOT
);
1431 if(gcp
->gc_on_finalize
!= NULL
)
1432 (*gcp
->gc_on_finalize
)(gcp
->gc_finalize_arg
);
1434 if(gcm
& a_GO_CLEANUP_ERROR
){
1435 if(a_go_ctx
->gc_flags
& a_GO_XCALL_LOOP
)
1436 a_go_ctx
->gc_flags
|= a_GO_XCALL_LOOP_ERROR
;
1440 if(gcp
->gc_flags
& a_GO_FREE
)
1443 if(n_UNLIKELY((gcm
& a_GO_CLEANUP_UNWIND
) && gcp
!= a_go_ctx
))
1448 if(!(gcm
& a_GO_CLEANUP_HOLDALLSIGS
))
1453 /* With *posix* we follow what POSIX says:
1454 * Any errors in the start-up file shall either cause mailx to
1455 * terminate with a diagnostic message and a non-zero status or to
1456 * continue after writing a diagnostic message, ignoring the
1457 * remainder of the lines in the start-up file
1458 * Print the diagnostic only for the outermost resource unless the user
1459 * is debugging or in verbose mode */
1460 if((n_poption
& n_PO_D_V
) ||
1461 (!(n_psonce
& n_PSO_STARTED
) &&
1462 !(gcp
->gc_flags
& (a_GO_SPLICE
| a_GO_MACRO
)) &&
1463 !(gcp
->gc_outer
->gc_flags
& a_GO_TYPE_MASK
)))
1464 /* I18N: file inclusion, macro etc. evaluation has been stopped */
1465 n_alert(_("Stopped %s %s due to errors%s"),
1466 (n_psonce
& n_PSO_STARTED
1467 ? (gcp
->gc_flags
& a_GO_SPLICE
? _("spliced in program")
1468 : (gcp
->gc_flags
& a_GO_MACRO
1469 ? (gcp
->gc_flags
& a_GO_MACRO_CMD
1470 ? _("evaluating command") : _("evaluating macro"))
1471 : (gcp
->gc_flags
& a_GO_PIPE
1472 ? _("executing `source'd pipe")
1473 : (gcp
->gc_flags
& a_GO_FILE
1474 ? _("loading `source'd file") : _(a_GO_MAINCTX_NAME
))))
1476 : (gcp
->gc_flags
& a_GO_MACRO
1477 ? (gcp
->gc_flags
& a_GO_MACRO_X_OPTION
1478 ? _("evaluating command line") : _("evaluating macro"))
1479 : _("loading initialization resource"))),
1481 (n_poption
& n_PO_DEBUG
? n_empty
: _(" (enable *debug* for trace)")));
1486 a_go_file(char const *file
, bool_t silent_open_error
){
1487 struct a_go_ctx
*gcp
;
1497 /* Being a command argument file is space-trimmed *//* TODO v15 with
1498 * TODO WYRALIST this is no longer necessary true, and for that we
1499 * TODO don't set _PARSE_TRIMSPACE because we cannot! -> cmd-tab.h!! */
1501 ((ispipe
= (!silent_open_error
&& (nlen
= strlen(file
)) > 0 &&
1502 file
[--nlen
] == '|')))
1505 if(!silent_open_error
){
1506 for(nlen
= strlen(file
); nlen
> 0;){
1512 nbuf
= savestrbuf(file
, nlen
);
1522 if((fip
= Popen(nbuf
/* #if 0 above = savestrbuf(file, nlen)*/, "r",
1523 ok_vlook(SHELL
), NULL
, n_CHILD_FD_NULL
)) == NULL
)
1525 }else if((nbuf
= fexpand(file
, FEXP_LOCAL
)) == NULL
)
1527 else if((fip
= Fopen(nbuf
, "r")) == NULL
){
1529 if(!silent_open_error
|| (n_poption
& n_PO_D_V
))
1531 if(silent_open_error
)
1536 sigprocmask(SIG_BLOCK
, NULL
, &osigmask
);
1538 gcp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
) +
1539 (nlen
= strlen(nbuf
) +1));
1540 memset(gcp
, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
));
1544 gcp
->gc_outer
= a_go_ctx
;
1545 gcp
->gc_osigmask
= osigmask
;
1547 gcp
->gc_flags
= (ispipe
? a_GO_FREE
| a_GO_PIPE
: a_GO_FREE
| a_GO_FILE
) |
1548 (a_go_ctx
->gc_flags
& a_GO_SUPER_MACRO
? a_GO_SUPER_MACRO
: 0);
1549 memcpy(gcp
->gc_name
, nbuf
, nlen
);
1552 n_go_data
= &gcp
->gc_data
;
1553 n_pstate
|= n_PS_SOURCING
| n_PS_ROBOT
;
1554 if(!a_go_event_loop(gcp
, n_GO_INPUT_NONE
| n_GO_INPUT_NL_ESC
))
1558 return (fip
!= NULL
);
1562 a_go_load(struct a_go_ctx
*gcp
){
1565 assert(!(n_psonce
& n_PSO_STARTED
));
1566 assert(!(a_go_ctx
->gc_flags
& a_GO_TYPE_MASK
));
1568 gcp
->gc_flags
|= a_GO_MEMPOOL_INHERITED
;
1569 gcp
->gc_data
.gdc_mempool
= n_go_data
->gdc_mempool
;
1574 * Any errors in the start-up file shall either cause mailx to terminate
1575 * with a diagnostic message and a non-zero status or to continue after
1576 * writing a diagnostic message, ignoring the remainder of the lines in
1577 * the start-up file. */
1578 gcp
->gc_outer
= a_go_ctx
;
1580 n_go_data
= &gcp
->gc_data
;
1581 /* FIXME won't work for now (n_PS_ROBOT needs n_PS_SOURCING sofar)
1582 n_pstate |= n_PS_ROBOT |
1583 (gcp->gc_flags & a_GO_MACRO_X_OPTION ? 0 : n_PS_SOURCING);
1585 n_pstate
|= n_PS_ROBOT
| n_PS_SOURCING
;
1591 return (((n_psonce
& n_PSO_EXIT_MASK
) |
1592 (n_pstate
& n_PS_ERR_EXIT_MASK
)) == 0);
1596 a_go__eloopint(int sig
){ /* TODO one day, we don't need it no more */
1597 NYD_X
; /* Signal handler */
1599 siglongjmp(a_go_ctx
->gc_eloop_jmp
, 1);
1603 a_go_event_loop(struct a_go_ctx
*gcp
, enum n_go_input_flags gif
){
1604 sighandler_type soldhdl
;
1605 struct a_go_eval_ctx gec
;
1606 enum {a_RETOK
= TRU1
, a_TICKED
= 1<<1} volatile f
;
1607 volatile int hadint
; /* TODO get rid of shitty signal stuff (see signal.c) */
1611 memset(&gec
, 0, sizeof gec
);
1612 osigmask
= gcp
->gc_osigmask
;
1616 if((soldhdl
= safe_signal(SIGINT
, SIG_IGN
)) != SIG_IGN
){
1617 safe_signal(SIGINT
, &a_go__eloopint
);
1618 if(sigsetjmp(gcp
->gc_eloop_jmp
, 1)){
1623 gcp
->gc_flags
&= ~a_GO_XCALL_LOOP_MASK
;
1628 for(;; f
|= a_TICKED
){
1634 /* Read a line of commands and handle end of file specially */
1635 gec
.gec_line
.l
= gec
.gec_line_size
;
1637 n
= n_go_input(gif
, NULL
, &gec
.gec_line
.s
, &gec
.gec_line
.l
, NULL
);
1639 gec
.gec_line_size
= (ui32_t
)gec
.gec_line
.l
;
1640 gec
.gec_line
.l
= (ui32_t
)n
;
1646 if(!a_go_evaluate(&gec
))
1650 if(!(f
& a_RETOK
) || a_go_xcall
!= NULL
||
1651 (n_psonce
& n_PSO_EXIT_MASK
) || (n_pstate
& n_PS_ERR_EXIT_MASK
))
1655 jjump
: /* TODO Should be _CLEANUP_UNWIND not _TEARDOWN on signal if DOABLE! */
1656 a_go_cleanup(a_GO_CLEANUP_TEARDOWN
|
1657 (f
& a_RETOK
? 0 : a_GO_CLEANUP_ERROR
) |
1658 (hadint
? a_GO_CLEANUP_SIGINT
: 0) | a_GO_CLEANUP_HOLDALLSIGS
);
1660 if(gec
.gec_line
.s
!= NULL
)
1661 n_free(gec
.gec_line
.s
);
1663 if(soldhdl
!= SIG_IGN
)
1664 safe_signal(SIGINT
, soldhdl
);
1668 sigprocmask(SIG_SETMASK
, &osigmask
, NULL
);
1671 return (f
& a_RETOK
);
1675 a_go_c_read(void *v
){ /* TODO IFS? how? -r */
1677 char const **argv
, *cp
, *cp2
;
1688 n_SIGMAN_ENTER_SWITCH(&sm
, n_SIGMAN_ALL
){
1692 n_pstate_err_no
= n_ERR_INTR
;
1696 rv
= n_go_input(((n_pstate
& n_PS_COMPOSE_MODE
1697 ? n_GO_INPUT_CTX_COMPOSE
: n_GO_INPUT_CTX_DEFAULT
) |
1698 n_GO_INPUT_FORCE_STDIN
| n_GO_INPUT_NL_ESC
|
1699 n_GO_INPUT_PROMPT_NONE
/* XXX POSIX: PS2: yes! */),
1700 NULL
, &linebuf
, &linesize
, NULL
);
1701 n_pstate_err_no
= n_ERR_NONE
; /* TODO I/O error if rv<0! */
1708 for(rv
= 0; *argv
!= NULL
; ++argv
){
1711 while(spacechar(*cp
))
1716 /* The last variable gets the remaining line less trailing IFS */
1717 if(argv
[1] == NULL
){
1718 for(cp2
= cp
; *cp2
!= '\0'; ++cp2
)
1720 for(; cp2
> cp
; --cp2
){
1726 for(cp2
= cp
; (c
= *++cp2
) != '\0';)
1730 /* C99 xxx This is a CC warning workaround (-Wbad-function-cast) */{
1733 vcp
= savestrbuf(cp
, PTR2SIZE(cp2
- cp
));
1734 if(!a_go__read_set(*argv
, vcp
)){
1735 n_pstate_err_no
= n_ERR_NOTSUP
;
1745 /* Set the remains to the empty string */
1746 for(; *argv
!= NULL
; ++argv
)
1747 if(!a_go__read_set(*argv
, n_empty
)){
1748 n_pstate_err_no
= n_ERR_NOTSUP
;
1753 n_sigman_cleanup_ping(&sm
);
1758 n_sigman_leave(&sm
, n_SIGMAN_VIPSIGS_NTTYOUT
);
1763 a_go__read_set(char const *cp
, char const *value
){
1767 if(!n_shexp_is_valid_varname(cp
))
1768 value
= N_("not a valid variable name");
1769 else if(!n_var_is_user_writable(cp
))
1770 value
= N_("variable is read-only");
1771 else if(!n_var_vset(cp
, (uintptr_t)value
))
1772 value
= N_("failed to update variable value");
1777 n_err("`read': %s: %s\n", V_(value
), n_shexp_quote_cp(cp
, FAL0
));
1785 c_cmdnotsupp(void *vp
){
1788 n_err(_("The requested feature is not compiled in\n"));
1795 struct a_go_ctx
*gcp
;
1798 assert(n_stdin
!= NULL
);
1800 gcp
= (void*)a_go__mainctx_b
.uf
;
1801 DBGOR( memset(gcp
, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
)),
1802 memset(&gcp
->gc_data
, 0, sizeof gcp
->gc_data
) );
1803 gcp
->gc_file
= n_stdin
;
1804 memcpy(gcp
->gc_name
, a_GO_MAINCTX_NAME
, sizeof(a_GO_MAINCTX_NAME
));
1806 n_go_data
= &gcp
->gc_data
;
1808 n_child_manager_start();
1813 n_go_main_loop(void){ /* FIXME */
1814 struct a_go_eval_ctx gec
;
1821 if (!(n_pstate
& n_PS_SOURCING
)) {
1822 if (safe_signal(SIGINT
, SIG_IGN
) != SIG_IGN
)
1823 safe_signal(SIGINT
, &a_go_onintr
);
1824 if (safe_signal(SIGHUP
, SIG_IGN
) != SIG_IGN
)
1825 safe_signal(SIGHUP
, &a_go_hangup
);
1827 a_go_oldpipe
= safe_signal(SIGPIPE
, SIG_IGN
);
1828 safe_signal(SIGPIPE
, a_go_oldpipe
);
1830 memset(&gec
, 0, sizeof gec
);
1832 (void)sigsetjmp(a_go_srbuf
, 1); /* FIXME get rid */
1835 for (eofcnt
= 0;; gec
.gec_ever_seen
= TRU1
) {
1838 if(gec
.gec_ever_seen
)
1839 a_go_cleanup(a_GO_CLEANUP_LOOPTICK
| a_GO_CLEANUP_HOLDALLSIGS
);
1841 if (!(n_pstate
& n_PS_SOURCING
)) {
1844 /* TODO Note: this buffer may contain a password. We should redefine
1845 * TODO the code flow which has to do that */
1846 if ((cp
= termios_state
.ts_linebuf
) != NULL
) {
1847 termios_state
.ts_linebuf
= NULL
;
1848 termios_state
.ts_linesize
= 0;
1849 n_free(cp
); /* TODO pool give-back */
1851 if (gec
.gec_line
.l
> LINESIZE
* 3) {
1852 n_free(gec
.gec_line
.s
);
1853 gec
.gec_line
.s
= NULL
;
1854 gec
.gec_line
.l
= gec
.gec_line_size
= 0;
1858 if (!(n_pstate
& n_PS_SOURCING
) && (n_psonce
& n_PSO_INTERACTIVE
)) {
1861 if ((cp
= ok_vlook(newmail
)) != NULL
) {
1864 /* FIXME TEST WITH NOPOLL ETC. !!! */
1865 n
= (cp
!= NULL
&& strcmp(cp
, "nopoll"));
1866 if ((mb
.mb_type
== MB_FILE
&& !stat(mailname
, &st
) &&
1867 st
.st_size
> mailsize
) ||
1868 (mb
.mb_type
== MB_MAILDIR
&& n
!= 0)) {
1869 size_t odot
= PTR2SIZE(dot
- message
);
1870 ui32_t odid
= (n_pstate
& n_PS_DID_PRINT_DOT
);
1874 i
= setfile(mailname
,
1876 ((mb
.mb_perm
& MB_DELE
) ? 0 : FEDIT_RDONLY
));
1879 n_exit_status
|= n_EXIT_ERR
;
1883 dot
= &message
[odot
];
1888 n_exit_status
= n_EXIT_OK
;
1891 /* Read a line of commands and handle end of file specially */
1892 gec
.gec_line
.l
= gec
.gec_line_size
;
1894 n
= n_go_input(n_GO_INPUT_CTX_DEFAULT
| n_GO_INPUT_NL_ESC
, NULL
,
1895 &gec
.gec_line
.s
, &gec
.gec_line
.l
, NULL
);
1897 gec
.gec_line_size
= (ui32_t
)gec
.gec_line
.l
;
1898 gec
.gec_line
.l
= (ui32_t
)n
;
1901 if (!(n_pstate
& n_PS_ROBOT
) &&
1902 (n_psonce
& n_PSO_INTERACTIVE
) && ok_blook(ignoreeof
) &&
1904 fprintf(n_stdout
, _("*ignoreeof* set, use `quit' to quit.\n"));
1905 n_go_input_clearerr();
1911 n_pstate
&= ~n_PS_HOOK_MASK
;
1913 rv
= a_go_evaluate(&gec
);
1916 if(!(n_pstate
& n_PS_SOURCING
) && (n_psonce
& n_PSO_INTERACTIVE
) &&
1917 gec
.gec_add_history
)
1918 n_tty_addhist(gec
.gec_line
.s
, (gec
.gec_add_history
!= TRU1
));
1920 switch(n_pstate
& n_PS_ERR_EXIT_MASK
){
1921 case n_PS_ERR_XIT
: n_psonce
|= n_PSO_XIT
; break;
1922 case n_PS_ERR_QUIT
: n_psonce
|= n_PSO_QUIT
; break;
1925 if(n_psonce
& n_PSO_EXIT_MASK
)
1932 a_go_cleanup(a_GO_CLEANUP_TEARDOWN
| a_GO_CLEANUP_HOLDALLSIGS
|
1933 (rv
? 0 : a_GO_CLEANUP_ERROR
));
1935 if (gec
.gec_line
.s
!= NULL
)
1936 n_free(gec
.gec_line
.s
);
1944 n_go_input_clearerr(void){
1950 if(!(a_go_ctx
->gc_flags
& (a_GO_FORCE_EOF
|
1951 a_GO_PIPE
| a_GO_MACRO
| a_GO_SPLICE
)))
1952 fp
= a_go_ctx
->gc_file
;
1960 n_go_input_force_eof(void){
1962 a_go_ctx
->gc_flags
|= a_GO_FORCE_EOF
;
1967 n_go_input_inject(enum n_go_input_inject_flags giif
, char const *buf
,
1973 if(UIZ_MAX
- n_VSTRUCT_SIZEOF(struct a_go_input_inject
, gii_dat
) -1 > len
&&
1976 struct a_go_input_inject
*giip
, **giipp
;
1980 giip
= n_alloc(n_VSTRUCT_SIZEOF(struct a_go_input_inject
, gii_dat
1982 giipp
= &a_go_ctx
->gc_inject
;
1983 giip
->gii_next
= *giipp
;
1984 giip
->gii_commit
= ((giif
& n_GO_INPUT_INJECT_COMMIT
) != 0);
1985 if(buf
[i
= 0] != ' ' && !(giif
& n_GO_INPUT_INJECT_HISTORY
))
1986 giip
->gii_dat
[i
++] = ' '; /* TODO prim. hack to avoid history put! */
1987 memcpy(&giip
->gii_dat
[i
], buf
, len
);
1989 giip
->gii_dat
[giip
->gii_len
= i
] = '\0';
1998 (n_go_input
)(enum n_go_input_flags gif
, char const *prompt
, char **linebuf
,
1999 size_t *linesize
, char const *string n_MEMORY_DEBUG_ARGS
){
2000 /* TODO readline: linebuf pool!; n_go_input should return si64_t */
2001 struct n_string xprompt
;
2003 bool_t doprompt
, dotty
;
2008 if(!(gif
& n_GO_INPUT_HOLDALLSIGS
))
2011 if(a_go_ctx
->gc_flags
& a_GO_FORCE_EOF
){
2016 if(gif
& n_GO_INPUT_FORCE_STDIN
)
2019 /* Special case macro mode: never need to prompt, lines have always been
2020 * unfolded already */
2021 if(a_go_ctx
->gc_flags
& a_GO_MACRO
){
2022 struct a_go_input_inject
*giip
;
2024 if(*linebuf
!= NULL
)
2027 /* Injection in progress? Don't care about the autocommit state here */
2028 if((giip
= a_go_ctx
->gc_inject
) != NULL
){
2029 a_go_ctx
->gc_inject
= giip
->gii_next
;
2031 *linesize
= giip
->gii_len
;
2032 *linebuf
= (char*)giip
;
2033 memmove(*linebuf
, giip
->gii_dat
, giip
->gii_len
+1);
2034 iftype
= "INJECTION";
2036 if((*linebuf
= a_go_ctx
->gc_lines
[a_go_ctx
->gc_loff
]) == NULL
){
2042 ++a_go_ctx
->gc_loff
;
2043 *linesize
= strlen(*linebuf
);
2044 if(!(a_go_ctx
->gc_flags
& a_GO_MACRO_FREE_DATA
))
2045 *linebuf
= sbufdup(*linebuf
, *linesize
);
2047 iftype
= (a_go_ctx
->gc_flags
& a_GO_MACRO_X_OPTION
)
2049 : (a_go_ctx
->gc_flags
& a_GO_MACRO_CMD
) ? "CMD" : "MACRO";
2052 n_pstate
|= n_PS_READLINE_NL
;
2055 /* Injection in progress? */
2056 struct a_go_input_inject
**giipp
, *giip
;
2058 giipp
= &a_go_ctx
->gc_inject
;
2060 if((giip
= *giipp
) != NULL
){
2061 *giipp
= giip
->gii_next
;
2063 if(giip
->gii_commit
){
2064 if(*linebuf
!= NULL
)
2067 /* Simply reuse the buffer */
2068 n
= (int)(*linesize
= giip
->gii_len
);
2069 *linebuf
= (char*)giip
;
2070 memmove(*linebuf
, giip
->gii_dat
, giip
->gii_len
+1);
2071 iftype
= "INJECTION";
2072 n_pstate
|= n_PS_READLINE_NL
;
2075 string
= savestrbuf(giip
->gii_dat
, giip
->gii_len
);
2082 n_pstate
&= ~n_PS_READLINE_NL
;
2083 iftype
= (!(n_psonce
& n_PSO_STARTED
) ? "LOAD"
2084 : (n_pstate
& n_PS_SOURCING
) ? "SOURCE" : "READ");
2085 doprompt
= ((n_psonce
& (n_PSO_INTERACTIVE
| n_PSO_STARTED
)) ==
2086 (n_PSO_INTERACTIVE
| n_PSO_STARTED
) && !(n_pstate
& n_PS_ROBOT
));
2087 dotty
= (doprompt
&& !ok_blook(line_editor_disable
));
2089 gif
|= n_GO_INPUT_PROMPT_NONE
;
2092 n_string_creat_auto(&xprompt
);
2094 gif
|= n_GO_INPUT_PROMPT_EVAL
;
2097 /* Ensure stdout is flushed first anyway (partial lines, maybe?) */
2098 if(!dotty
&& (gif
& n_GO_INPUT_PROMPT_NONE
))
2101 ifile
= (gif
& n_GO_INPUT_FORCE_STDIN
) ? n_stdin
: a_go_ctx
->gc_file
;
2103 assert((n_pstate
& n_PS_COMPOSE_FORKHOOK
) &&
2104 (a_go_ctx
->gc_flags
& a_GO_MACRO
));
2108 for(nold
= n
= 0;;){
2110 assert(ifile
== n_stdin
);
2111 if(string
!= NULL
&& (n
= (int)strlen(string
)) > 0){
2115 *linesize
= (size_t)n
+ LINESIZE
+1;
2116 *linebuf
= (n_realloc
)(*linebuf
, *linesize n_MEMORY_DEBUG_ARGSCALL
);
2117 memcpy(*linebuf
, string
, (size_t)n
+1);
2123 n
= (n_tty_readline
)(gif
, prompt
, linebuf
, linesize
, n
2124 n_MEMORY_DEBUG_ARGSCALL
);
2128 if(!(gif
& n_GO_INPUT_PROMPT_NONE
))
2129 n_tty_create_prompt(&xprompt
, prompt
, gif
);
2133 if(!(gif
& n_GO_INPUT_PROMPT_NONE
) && xprompt
.s_len
> 0){
2134 fwrite(xprompt
.s_dat
, 1, xprompt
.s_len
, n_stdout
);
2138 n
= (readline_restart
)(ifile
, linebuf
, linesize
, n
2139 n_MEMORY_DEBUG_ARGSCALL
);
2143 if(n
> 0 && nold
> 0){
2148 cp
= &(*linebuf
)[nold
];
2149 while(spacechar(*cp
) && n
- i
>= nold
)
2152 memmove(&(*linebuf
)[nold
], cp
, n
- nold
- i
);
2154 (*linebuf
)[n
] = '\0';
2163 * An unquoted <backslash> at the end of a command line shall
2164 * be discarded and the next line shall continue the command */
2165 if(!(gif
& n_GO_INPUT_NL_ESC
) || (*linebuf
)[n
- 1] != '\\'){
2167 n_pstate
|= n_PS_READLINE_NL
;
2170 /* Definitely outside of quotes, thus the quoting rules are so that an
2171 * uneven number of successive reverse solidus at EOL is a continuation */
2175 for(j
= 1, i
= (size_t)n
- 1; i
-- > 0; ++j
)
2176 if((*linebuf
)[i
] != '\\')
2181 (*linebuf
)[nold
= --n
] = '\0';
2182 gif
|= n_GO_INPUT_NL_FOLLOW
;
2187 (*linebuf
)[*linesize
= n
] = '\0';
2191 if(gif
& n_GO_INPUT_DROP_TRAIL_SPC
){
2195 for(cp
= &(*linebuf
)[i
= (size_t)n
];; --i
){
2200 (*linebuf
)[n
= (int)i
] = '\0';
2203 if(gif
& n_GO_INPUT_DROP_LEAD_SPC
){
2207 for(cp
= &(*linebuf
)[0], j
= (size_t)n
, i
= 0; i
< j
; ++i
){
2213 memmove(&(*linebuf
)[0], &(*linebuf
)[i
], j
-= i
);
2214 (*linebuf
)[n
= (int)j
] = '\0';
2217 #endif /* 0 (notyet - must take care for reverse solidus escaped space) */
2219 if(n_poption
& n_PO_D_VV
)
2220 n_err(_("%s %d bytes <%s>\n"), iftype
, n
, *linebuf
);
2222 if (n_pstate
& n_PS_PSTATE_PENDMASK
)
2223 a_go_update_pstate();
2225 /* TODO We need to special case a_GO_SPLICE, since that is not managed by us
2226 * TODO but only established from the outside and we need to drop this
2227 * TODO overlay context somehow */
2228 if(n
< 0 && (a_go_ctx
->gc_flags
& a_GO_SPLICE
))
2229 a_go_cleanup(a_GO_CLEANUP_TEARDOWN
| a_GO_CLEANUP_HOLDALLSIGS
);
2231 if(!(gif
& n_GO_INPUT_HOLDALLSIGS
))
2238 n_go_input_cp(enum n_go_input_flags gif
, char const *prompt
,
2239 char const *string
){
2242 char *linebuf
, * volatile rv
;
2250 n_SIGMAN_ENTER_SWITCH(&sm
, n_SIGMAN_ALL
){
2257 n
= n_go_input(gif
, prompt
, &linebuf
, &linesize
, string
);
2258 if(n
> 0 && *(rv
= savestrbuf(linebuf
, (size_t)n
)) != '\0' &&
2259 (gif
& n_GO_INPUT_HIST_ADD
) && (n_psonce
& n_PSO_INTERACTIVE
))
2260 n_tty_addhist(rv
, ((gif
& n_GO_INPUT_HIST_GABBY
) != 0));
2262 n_sigman_cleanup_ping(&sm
);
2267 n_sigman_leave(&sm
, n_SIGMAN_VIPSIGS_NTTYOUT
);
2272 n_go_load(char const *name
){
2273 struct a_go_ctx
*gcp
;
2281 if(name
== NULL
|| *name
== '\0')
2283 else if((fip
= Fopen(name
, "r")) == NULL
){
2284 if(n_poption
& n_PO_D_V
)
2285 n_err(_("No such file to load: %s\n"), n_shexp_quote_cp(name
, FAL0
));
2289 i
= strlen(name
) +1;
2290 gcp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
) + i
);
2291 memset(gcp
, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
));
2294 gcp
->gc_flags
= a_GO_FREE
| a_GO_FILE
;
2295 memcpy(gcp
->gc_name
, name
, i
);
2297 if(n_poption
& n_PO_D_V
)
2298 n_err(_("Loading %s\n"), n_shexp_quote_cp(gcp
->gc_name
, FAL0
));
2299 rv
= a_go_load(gcp
);
2306 n_go_Xargs(char const **lines
, size_t cnt
){
2307 static char const name
[] = "-X";
2312 char uf
[n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
) + sizeof(name
)];
2314 char const *srcp
, *xsrcp
;
2316 size_t imax
, i
, len
;
2317 struct a_go_ctx
*gcp
;
2321 memset(gcp
, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
));
2323 gcp
->gc_flags
= a_GO_MACRO
| a_GO_MACRO_X_OPTION
|
2324 a_GO_SUPER_MACRO
| a_GO_MACRO_FREE_DATA
;
2325 memcpy(gcp
->gc_name
, name
, sizeof name
);
2327 /* The problem being that we want to support reverse solidus newline
2328 * escaping also within multiline -X, i.e., POSIX says:
2329 * An unquoted <backslash> at the end of a command line shall
2330 * be discarded and the next line shall continue the command
2331 * Therefore instead of "gcp->gc_lines = n_UNCONST(lines)", duplicate the
2332 * entire lines array and set _MACRO_FREE_DATA */
2334 gcp
->gc_lines
= n_alloc(sizeof(*gcp
->gc_lines
) * imax
);
2336 /* For each of the input lines.. */
2337 for(i
= len
= 0, cp
= NULL
; cnt
> 0;){
2341 if((j
= strlen(srcp
= *lines
)) == 0){
2346 /* Separate one line from a possible multiline input string */
2347 if((xsrcp
= memchr(srcp
, '\n', j
)) != NULL
){
2349 j
= PTR2SIZE(xsrcp
- srcp
);
2353 /* The (separated) string may itself indicate soft newline escaping */
2354 if((keep
= (srcp
[j
- 1] == '\\'))){
2357 /* Need an uneven number of reverse solidus */
2358 for(xk
= 1, xj
= j
- 1; xj
-- > 0; ++xk
)
2359 if(srcp
[xj
] != '\\')
2367 /* Strip any leading WS from follow lines, then */
2369 while(j
> 0 && spacechar(*srcp
))
2373 if(i
+ 2 >= imax
){ /* TODO need a vector (main.c, here, ++) */
2375 gcp
->gc_lines
= n_realloc(gcp
->gc_lines
, sizeof(*gcp
->gc_lines
) *
2378 gcp
->gc_lines
[i
] = cp
= n_realloc(cp
, len
+ j
+1);
2379 memcpy(&cp
[len
], srcp
, j
);
2380 cp
[len
+= j
] = '\0';
2389 assert(i
+ 1 < imax
);
2390 gcp
->gc_lines
[i
++] = cp
;
2392 gcp
->gc_lines
[i
] = NULL
;
2394 b
.rv
= a_go_load(gcp
);
2404 rv
= (a_go_file(*(char**)v
, FAL0
) == TRU1
) ? 0 : 1;
2410 c_source_if(void *v
){ /* XXX obsolete?, support file tests in `if' etc.! */
2414 rv
= (a_go_file(*(char**)v
, TRU1
) == TRU1
) ? 0 : 1;
2420 n_go_macro(enum n_go_input_flags gif
, char const *name
, char **lines
,
2421 void (*on_finalize
)(void*), void *finalize_arg
){
2422 struct a_go_ctx
*gcp
;
2428 sigprocmask(SIG_BLOCK
, NULL
, &osigmask
);
2430 gcp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
) +
2431 (i
= strlen(name
) +1));
2432 memset(gcp
, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
));
2436 gcp
->gc_outer
= a_go_ctx
;
2437 gcp
->gc_osigmask
= osigmask
;
2438 gcp
->gc_flags
= a_GO_FREE
| a_GO_MACRO
| a_GO_MACRO_FREE_DATA
|
2439 ((!(a_go_ctx
->gc_flags
& a_GO_TYPE_MASK
) ||
2440 (a_go_ctx
->gc_flags
& a_GO_SUPER_MACRO
)) ? a_GO_SUPER_MACRO
: 0);
2441 gcp
->gc_lines
= lines
;
2442 gcp
->gc_on_finalize
= on_finalize
;
2443 gcp
->gc_finalize_arg
= finalize_arg
;
2444 memcpy(gcp
->gc_name
, name
, i
);
2447 n_go_data
= &gcp
->gc_data
;
2448 n_pstate
|= n_PS_ROBOT
;
2449 rv
= a_go_event_loop(gcp
, gif
);
2451 /* Shall this enter a `xcall' stack avoidance optimization (loop)? */
2452 if(a_go_xcall
!= NULL
){
2453 if(a_go_xcall
== (struct a_go_xcall
*)-1)
2455 else if(a_go_xcall
->gx_upto
== gcp
){
2456 /* Indicate that "our" (ex-) parent now hosts xcall optimization */
2457 a_go_ctx
->gc_flags
|= a_GO_XCALL_LOOP
;
2458 while(a_go_xcall
!= NULL
){
2460 struct a_go_xcall
*gxp
;
2464 a_go_ctx
->gc_flags
&= ~a_GO_XCALL_LOOP_ERROR
;
2468 /* Recreate the ARGV of this command on the LOFI memory of the
2469 * hosting a_go_ctx, so that it will become auto-reclaimed */
2473 vp
= n_lofi_alloc(gxp
->gx_buflen
);
2477 cp
+= sizeof(*argv
) * (gxp
->gx_argc
+ 1);
2478 for(i
= 0; i
< gxp
->gx_argc
; ++i
){
2480 memcpy(cp
, gxp
->gx_argv
[i
].s
, gxp
->gx_argv
[i
].l
+1);
2481 cp
+= gxp
->gx_argv
[i
].l
+1;
2492 rv
= ((a_go_ctx
->gc_flags
& a_GO_XCALL_LOOP_ERROR
) != 0);
2493 a_go_ctx
->gc_flags
&= ~a_GO_XCALL_LOOP_MASK
;
2501 n_go_command(enum n_go_input_flags gif
, char const *cmd
){
2502 struct a_go_ctx
*gcp
;
2508 sigprocmask(SIG_BLOCK
, NULL
, &osigmask
);
2512 gcp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
) +
2513 ial
+ 2*sizeof(char*));
2514 memset(gcp
, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
));
2518 gcp
->gc_outer
= a_go_ctx
;
2519 gcp
->gc_osigmask
= osigmask
;
2520 gcp
->gc_flags
= a_GO_FREE
| a_GO_MACRO
| a_GO_MACRO_CMD
|
2521 ((!(a_go_ctx
->gc_flags
& a_GO_TYPE_MASK
) ||
2522 (a_go_ctx
->gc_flags
& a_GO_SUPER_MACRO
)) ? a_GO_SUPER_MACRO
: 0);
2523 gcp
->gc_lines
= (void*)&gcp
->gc_name
[ial
];
2524 memcpy(gcp
->gc_lines
[0] = &gcp
->gc_name
[0], cmd
, i
);
2525 gcp
->gc_lines
[1] = NULL
;
2528 n_go_data
= &gcp
->gc_data
;
2529 n_pstate
|= n_PS_ROBOT
;
2530 rv
= a_go_event_loop(gcp
, gif
);
2536 n_go_splice_hack(char const *cmd
, FILE *new_stdin
, FILE *new_stdout
,
2537 ui32_t new_psonce
, void (*on_finalize
)(void*), void *finalize_arg
){
2538 struct a_go_ctx
*gcp
;
2543 sigprocmask(SIG_BLOCK
, NULL
, &osigmask
);
2545 gcp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
) +
2546 (i
= strlen(cmd
) +1));
2547 memset(gcp
, 0, n_VSTRUCT_SIZEOF(struct a_go_ctx
, gc_name
));
2551 gcp
->gc_outer
= a_go_ctx
;
2552 gcp
->gc_osigmask
= osigmask
;
2553 gcp
->gc_file
= new_stdin
;
2554 gcp
->gc_flags
= a_GO_FREE
| a_GO_SPLICE
;
2555 gcp
->gc_on_finalize
= on_finalize
;
2556 gcp
->gc_finalize_arg
= finalize_arg
;
2557 gcp
->gc_splice_stdin
= n_stdin
;
2558 gcp
->gc_splice_stdout
= n_stdout
;
2559 gcp
->gc_splice_psonce
= n_psonce
;
2560 memcpy(gcp
->gc_name
, cmd
, i
);
2562 n_stdin
= new_stdin
;
2563 n_stdout
= new_stdout
;
2564 n_psonce
= new_psonce
;
2566 n_pstate
|= n_PS_ROBOT
;
2573 n_go_splice_hack_remove_after_jump(void){
2574 a_go_cleanup(a_GO_CLEANUP_TEARDOWN
);
2578 n_go_may_yield_control(void){ /* TODO this is a terrible hack */
2579 struct a_go_ctx
*gcp
;
2585 /* Only when interactive and startup completed */
2586 if((n_psonce
& (n_PSO_INTERACTIVE
| n_PSO_STARTED
)) !=
2587 (n_PSO_INTERACTIVE
| n_PSO_STARTED
))
2590 /* Not when running any hook */
2591 if(n_pstate
& n_PS_HOOK_MASK
)
2594 /* Traverse up the stack:
2595 * . not when controlled by a child process
2596 * TODO . not when there are pipes involved, we neither handle job control,
2597 * TODO nor process groups, that is, controlling terminal acceptably
2598 * . not when sourcing a file */
2599 for(gcp
= a_go_ctx
; gcp
!= NULL
; gcp
= gcp
->gc_outer
){
2600 if(gcp
->gc_flags
& (a_GO_PIPE
| a_GO_FILE
| a_GO_SPLICE
))